PageSourceSearch

https://ml-owl-faang-robotrader.hf.space/assets/StaticAudio-CWETG3mI.js

js ml-owl-faang-robotrader.hf.space collected 2026-10-03 17:08:22 UTC 1,043,065 bytes, 29,883 lines download raw bytes

1import { p as prop, t as remove_input_defaults, d as bind_this, F as set_value, i as if_block, f as set_style, b as set_class, a as set_attribute } from './i18n-CGlVUOvE.js';
2import { a as append, f as from_svg, ai as delegate, M as push$1, a8 as onMount, ad as user_effect, t as template_effect, O as pop, R as from_html, ab as state, ac as proxy, Z as get, $ as set, Q as child, T as reset, S as sibling, X as set_text, N as first_child, w as untrack, U as event, a2 as user_derived, a3 as comment } from './index-Bq2njFOY.js';
3import { f as format_time, u as uploadToHuggingFace } from './utils.svelte-Bi9ASwv9.js';
4import { I as IconButton } from './ScrollFade.svelte_svelte_type_style_lang-D0JPkGar.js';
5import './MarkdownCode.svelte_svelte_type_style_lang-B29zDiob.js';
6import { B as BlockLabel } from './BlockLabel-VpI11pHf.js';
7import { D as DownloadLink } from './DownloadLink-D6EYxQxh.js';
8import { E as Empty } from './Empty-BrRNpJ3n.js';
9import { S as ShareButton } from './ShareButton-CORLYhd9.js';
10import { D as Download } from './Download-D5ZNT7uE.js';
11import { M as Music } from './Music-Dzd6j5YE.js';
12import { I as IconButtonWrapper } from './IconButtonWrapper-DAsvkfJg.js';
13import { g as get_skip_rewind_amount, u, p as process_audio, s as skip_audio, M as MinimalAudioPlayer } from './MinimalAudioPlayer-BNuZEaxr.js';
14import { V as VolumeLevels, P as Pause, T as Trim } from './VolumeLevels-DLuAAZ7X.js';
15import { P as Play } from './Play-CGGxKNeS.js';
16import { U as Undo } from './Undo-4MCSCpCy.js';
17
18var root$6 = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" stroke-width="1.5" viewBox="0 0 24 24" color="currentColor"><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M21.044 5.704a.6.6 0 0 1 .956.483v11.626a.6.6 0 0 1-.956.483l-7.889-5.813a.6.6 0 0 1 0-.966l7.89-5.813ZM10.044 5.704a.6.6 0 0 1 .956.483v11.626a.6.6 0 0 1-.956.483l-7.888-5.813a.6.6 0 0 1 0-.966l7.888-5.813Z"></path></svg>`);
19
20function Backward($$anchor) {
21	var svg = root$6();
22
23	append($$anchor, svg);
24}
25
26var root$5 = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-closed-caption"><rect x="2" y="6" width="20" height="12" rx="2" ry="2"></rect><text x="12" y="15" font-family="sans-serif" font-size="8" font-weight="bold" fill="currentColor" stroke="none" text-anchor="middle">CC</text></svg>`);
27
28function ClosedCaption($$anchor) {
29	var svg = root$5();
30
31	append($$anchor, svg);
32}
33
34var root$4 = from_svg(`<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px" fill="currentColor" stroke-width="1.5" viewBox="0 0 24 24" color="currentColor"><path stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M2.956 5.704A.6.6 0 0 0 2 6.187v11.626a.6.6 0 0 0 .956.483l7.889-5.813a.6.6 0 0 0 0-.966l-7.89-5.813ZM13.956 5.704a.6.6 0 0 0-.956.483v11.626a.6.6 0 0 0 .956.483l7.889-5.813a.6.6 0 0 0 0-.966l-7.89-5.813Z"></path></svg>`);
35
36function Forward($$anchor) {
37	var svg = root$4();
38
39	append($$anchor, svg);
40}
41
42class t{constructor(){this.listeners={};}on(t,e,i){if(this.listeners[t]||(this.listeners[t]=new Set),null==i?void 0:i.once){const i=(...n)=>{this.un(t,i),e(...n);};return this.listeners[t].add(i),()=>this.un(t,i)}return this.listeners[t].add(e),()=>this.un(t,e)}un(t,e){var i;null===(i=this.listeners[t])||void 0===i||i.delete(e);}once(t,e){return this.on(t,e,{once:true})}unAll(){this.listeners={};}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)));}}class e extends t{constructor(t){super(),this.subscriptions=[],this.isDestroyed=false,this.options=t;}onInit(){}_init(t){this.isDestroyed&&(this.subscriptions=[],this.isDestroyed=false),this.wavesurfer=t,this.onInit();}destroy(){this.emit("destroy"),this.subscriptions.forEach((t=>t())),this.subscriptions=[],this.isDestroyed=true,this.wavesurfer=void 0;}}function i(t,e,i,n,s=3,r=0,o=100){if(!t)return ()=>{};const h=matchMedia("(pointer: coarse)").matches;let a=()=>{};const l=l=>{if(l.button!==r)return;l.preventDefault(),l.stopPropagation();let d=l.clientX,c=l.clientY,u=false;const v=Date.now(),g=n=>{if(n.preventDefault(),n.stopPropagation(),h&&Date.now()-v<o)return;const r=n.clientX,a=n.clientY,l=r-d,g=a-c;if(u||Math.abs(l)>s||Math.abs(g)>s){const n=t.getBoundingClientRect(),{left:s,top:o}=n;u||(null==i||i(d-s,c-o),u=true),e(l,g,r-s,a-o),d=r,c=a;}},p=e=>{if(u){const i=e.clientX,s=e.clientY,r=t.getBoundingClientRect(),{left:o,top:h}=r;null==n||n(i-o,s-h);}a();},m=t=>{t.relatedTarget&&t.relatedTarget!==document.documentElement||p(t);},f=t=>{u&&(t.stopPropagation(),t.preventDefault());},b=t=>{u&&t.preventDefault();};document.addEventListener("pointermove",g),document.addEventListener("pointerup",p),document.addEventListener("pointerout",m),document.addEventListener("pointercancel",m),document.addEventListener("touchmove",b,{passive:false}),document.addEventListener("click",f,{capture:true}),a=()=>{document.removeEventListener("pointermove",g),document.removeEventListener("pointerup",p),document.removeEventListener("pointerout",m),document.removeEventListener("pointercancel",m),document.removeEventListener("touchmove",b),setTimeout((()=>{document.removeEventListener("click",f,{capture:true});}),10);};};return t.addEventListener("pointerdown",l),()=>{a(),t.removeEventListener("pointerdown",l);}}function n(t,e){const i=e.xmlns?document.createElementNS(e.xmlns,t):document.createElement(t);for(const[t,s]of Object.entries(e))if("children"===t&&s)for(const[t,e]of Object.entries(s))e instanceof Node?i.appendChild(e):"string"==typeof e?i.appendChild(document.createTextNode(e)):i.appendChild(n(t,e));else "style"===t?Object.assign(i.style,s):"textContent"===t?i.textContent=s:i.setAttribute(t,s.toString());return i}function s(t,e,i){const s=n(t,e||{});return null==i||i.appendChild(s),s}class r extends t{constructor(t,e,i=0){var n,s,r,o,h,a,l,d,c,u;super(),this.totalDuration=e,this.numberOfChannels=i,this.element=null,this.minLength=0,this.maxLength=1/0,this.contentEditable=false,this.subscriptions=[],this.updatingSide=void 0,this.isRemoved=false,this.subscriptions=[],this.id=t.id||`region-${Math.random().toString(32).slice(2)}`,this.start=this.clampPosition(t.start),this.end=this.clampPosition(null!==(n=t.end)&&void 0!==n?n:t.start),this.drag=null===(s=t.drag)||void 0===s||s,this.resize=null===(r=t.resize)||void 0===r||r,this.resizeStart=null===(o=t.resizeStart)||void 0===o||o,this.resizeEnd=null===(h=t.resizeEnd)||void 0===h||h,this.color=null!==(a=t.color)&&void 0!==a?a:"rgba(0, 0, 0, 0.1)",this.minLength=null!==(l=t.minLength)&&void 0!==l?l:this.minLength,this.maxLength=null!==(d=t.maxLength)&&void 0!==d?d:this.maxLength,this.channelIdx=null!==(c=t.channelIdx)&&void 0!==c?c:-1,this.contentEditable=null!==(u=t.contentEditable)&&void 0!==u?u:this.contentEditable,this.element=this.initElement(),this.setContent(t.content),this.setPart(),this.renderPosition(),this.initMouseEvents();}clampPosition(t){return Math.max(0,Math.min(this.totalDuration,t))}setPart(){var t;const e=this.start===this.end;null===(t=this.element)||void 0===t||t.setAttribute("part",`${e?"marker":"region"} ${this.id}`);}addResizeHandles(t){const e={position:"absolute",zIndex:"2",width:"6px",height:"100%",top:"0",cursor:"ew-resize",wordBreak:"keep-all"},n=s("div",{part:"region-handle region-handle-left",style:Object.assign(Object.assign({}
42,e),{left:"0",borderLeft:"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"2px 0 0 2px"})},t),r=s("div",{part:"region-handle region-handle-right",style:Object.assign(Object.assign({},e),{right:"0",borderRight:"2px solid rgba(0, 0, 0, 0.5)",borderRadius:"0 2px 2px 0"})},t);this.subscriptions.push(i(n,(t=>this.onResize(t,"start")),(()=>null),(()=>this.onEndResizing("start")),1),i(r,(t=>this.onResize(t,"end")),(()=>null),(()=>this.onEndResizing("end")),1));}removeResizeHandles(t){const e=t.querySelector('[part*="region-handle-left"]'),i=t.querySelector('[part*="region-handle-right"]');e&&t.removeChild(e),i&&t.removeChild(i);}initElement(){if(this.isRemoved)return null;const t=this.start===this.end;let e=0,i=100;this.channelIdx>=0&&this.numberOfChannels>0&&this.channelIdx<this.numberOfChannels&&(i=100/this.numberOfChannels,e=i*this.channelIdx);const n=s("div",{style:{position:"absolute",top:`${e}%`,height:`${i}%`,backgroundColor:t?"none":this.color,borderLeft:t?"2px solid "+this.color:"none",borderRadius:"2px",boxSizing:"border-box",transition:"background-color 0.2s ease",cursor:this.drag?"grab":"default",pointerEvents:"all"}});return !t&&this.resize&&this.addResizeHandles(n),n}renderPosition(){if(!this.element)return;const t=this.start/this.totalDuration,e=(this.totalDuration-this.end)/this.totalDuration;this.element.style.left=100*t+"%",this.element.style.right=100*e+"%";}toggleCursor(t){var e;this.drag&&(null===(e=this.element)||void 0===e?void 0:e.style)&&(this.element.style.cursor=t?"grabbing":"grab");}initMouseEvents(){const{element:t}=this;t&&(t.addEventListener("click",(t=>this.emit("click",t))),t.addEventListener("mouseenter",(t=>this.emit("over",t))),t.addEventListener("mouseleave",(t=>this.emit("leave",t))),t.addEventListener("dblclick",(t=>this.emit("dblclick",t))),t.addEventListener("pointerdown",(()=>this.toggleCursor(true))),t.addEventListener("pointerup",(()=>this.toggleCursor(false))),this.subscriptions.push(i(t,(t=>this.onMove(t)),(()=>this.toggleCursor(true)),(()=>{this.toggleCursor(false),this.drag&&this.emit("update-end");}))),this.contentEditable&&this.content&&(this.contentClickListener=t=>this.onContentClick(t),this.contentBlurListener=()=>this.onContentBlur(),this.content.addEventListener("click",this.contentClickListener),this.content.addEventListener("blur",this.contentBlurListener)));}_onUpdate(t,e,i){var n;if(!(null===(n=this.element)||void 0===n?void 0:n.parentElement))return;const{width:s}=this.element.parentElement.getBoundingClientRect(),r=t/s*this.totalDuration;let o=e&&"start"!==e?this.start:this.start+r,h=e&&"end"!==e?this.end:this.end+r;const a=void 0!==i;a&&this.updatingSide&&this.updatingSide!==e&&("start"===this.updatingSide?o=i:h=i),o=Math.max(0,o),h=Math.min(this.totalDuration,h);const l=h-o;this.updatingSide=e;const d=l>=this.minLength&&l<=this.maxLength;o<=h&&(d||a)&&(this.start=o,this.end=h,this.renderPosition(),this.emit("update",e));}onMove(t){this.drag&&this._onUpdate(t);}onResize(t,e){this.resize&&(this.resizeStart||"start"!==e)&&(this.resizeEnd||"end"!==e)&&this._onUpdate(t,e);}
42onEndResizing(t){this.resize&&(this.emit("update-end",t),this.updatingSide=void 0);}onContentClick(t){t.stopPropagation();t.target.focus(),this.emit("click",t);}onContentBlur(){this.emit("update-end");}_setTotalDuration(t){this.totalDuration=t,this.renderPosition();}play(t){this.emit("play",t&&this.end!==this.start?this.end:void 0);}getContent(t=false){var e;return t?this.content||void 0:this.element instanceof HTMLElement?(null===(e=this.content)||void 0===e?void 0:e.innerHTML)||void 0:""}setContent(t){var e;if(this.element)if(this.content&&this.contentEditable&&(this.contentClickListener&&this.content.removeEventListener("click",this.contentClickListener),this.contentBlurListener&&this.content.removeEventListener("blur",this.contentBlurListener)),null===(e=this.content)||void 0===e||e.remove(),t){if("string"==typeof t){const e=this.start===this.end;this.content=s("div",{style:{padding:`0.2em ${e?.2:.4}em`,display:"inline-block"},textContent:t});}else this.content=t;this.contentEditable&&(this.content.contentEditable="true",this.contentClickListener=t=>this.onContentClick(t),this.contentBlurListener=()=>this.onContentBlur(),this.content.addEventListener("click",this.contentClickListener),this.content.addEventListener("blur",this.contentBlurListener)),this.content.setAttribute("part","region-content"),this.element.appendChild(this.content),this.emit("content-changed");}else this.content=void 0;}setOptions(t){var e,i;if(this.element){if(t.color&&(this.color=t.color,this.element.style.backgroundColor=this.color),void 0!==t.drag&&(this.drag=t.drag,this.element.style.cursor=this.drag?"grab":"default"),void 0!==t.start||void 0!==t.end){const n=this.start===this.end;this.start=this.clampPosition(null!==(e=t.start)&&void 0!==e?e:this.start),this.end=this.clampPosition(null!==(i=t.end)&&void 0!==i?i:n?this.start:this.end),this.renderPosition(),this.setPart();}if(t.content&&this.setContent(t.content),t.id&&(this.id=t.id,this.setPart()),void 0!==t.resize&&t.resize!==this.resize){const e=this.start===this.end;this.resize=t.resize,this.resize&&!e?this.addResizeHandles(this.element):this.removeResizeHandles(this.element);} void 0!==t.resizeStart&&(this.resizeStart=t.resizeStart),void 0!==t.resizeEnd&&(this.resizeEnd=t.resizeEnd);}}remove(){this.isRemoved=true,this.emit("remove"),this.subscriptions.forEach((t=>t())),this.element&&(this.element.remove(),this.element=null);}}class o extends e{constructor(t){super(t),this.regions=[],this.regionsContainer=this.initRegionsContainer();}static create(t){return new o(t)}onInit(){if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");this.wavesurfer.getWrapper().appendChild(this.regionsContainer),this.subscriptions.push(this.wavesurfer.on("ready",(t=>{this.regions.forEach((e=>e._setTotalDuration(t)));})));let t=[];this.subscriptions.push(this.wavesurfer.on("timeupdate",(e=>{const i=this.regions.filter((t=>t.start<=e&&(t.end===t.start?t.start+.05:t.end)>=e));i.forEach((e=>{t.includes(e)||this.emit("region-in",e);})),t.forEach((t=>{i.includes(t)||this.emit("region-out",t);})),t=i;})));}initRegionsContainer(){return s("div",{part:"regions-container",style:{position:"absolute",top:"0",left:"0",width:"100%",height:"100%",zIndex:"5",pointerEvents:"none"}})}getRegions(){return this.regions}avoidOverlapping(t){t.content&&setTimeout((()=>{const e=t.content,i=e.getBoundingClientRect(),n=this.regions.map((e=>{if(e===t||!e.content)return 0;const n=e.content.getBoundingClientRect();return i.left<n.left+n.width&&n.left<i.left+i.width?n.height:0})).reduce(((t,e)=>t+e),0);e.style.marginTop=`${n}px`;}),10);}adjustScroll(t){var e,i;if(!t.element)return;const n=null===(i=null===(e=this.wavesurfer)||void 0===e?void 0:e.getWrapper())||void 0===i?void 0:i.parentElement;if(!n)return;const{clientWidth:s,scrollWidth:r}=n;if(r<=s)return;const o=n.getBoundingClientRect(),h=t.element.getBoundingClientRect(),a=h.left-o.left,l=h.right-o.left;a<0?n.scrollLeft+=a:l>s&&(n.scrollLeft+=l-s);}virtualAppend(t,e,i){const n=()=>
42{if(!this.wavesurfer)return;const n=this.wavesurfer.getWidth(),s=this.wavesurfer.getScroll(),r=e.clientWidth,o=this.wavesurfer.getDuration(),h=Math.round(t.start/o*r),a=h+(Math.round((t.end-t.start)/o*r)||1)>s&&h<s+n;a&&!i.parentElement?e.appendChild(i):!a&&i.parentElement&&i.remove();};setTimeout((()=>{if(!this.wavesurfer||!t.element)return;n();const e=this.wavesurfer.on("scroll",n),i=this.wavesurfer.on("zoom",n);this.subscriptions.push(e,i),t.once("remove",(()=>{e(),i();}));}),0);}saveRegion(t){if(!t.element)return;this.virtualAppend(t,this.regionsContainer,t.element),this.avoidOverlapping(t),this.regions.push(t);const e=[t.on("update",(e=>{e||this.adjustScroll(t),this.emit("region-update",t,e);})),t.on("update-end",(e=>{this.avoidOverlapping(t),this.emit("region-updated",t,e);})),t.on("play",(e=>{var i;null===(i=this.wavesurfer)||void 0===i||i.play(t.start,e);})),t.on("click",(e=>{this.emit("region-clicked",t,e);})),t.on("dblclick",(e=>{this.emit("region-double-clicked",t,e);})),t.on("content-changed",(()=>{this.emit("region-content-changed",t);})),t.once("remove",(()=>{e.forEach((t=>t())),this.regions=this.regions.filter((e=>e!==t)),this.emit("region-removed",t);}))];this.subscriptions.push(...e),this.emit("region-created",t);}addRegion(t){var e,i;if(!this.wavesurfer)throw Error("WaveSurfer is not initialized");const n=this.wavesurfer.getDuration(),s=null===(i=null===(e=this.wavesurfer)||void 0===e?void 0:e.getDecodedData())||void 0===i?void 0:i.numberOfChannels,o=new r(t,n,s);return this.emit("region-initialized",o),n?this.saveRegion(o):this.subscriptions.push(this.wavesurfer.once("ready",(t=>{o._setTotalDuration(t),this.saveRegion(o);}))),o}enableDragSelection(t,e=3){var n;const s=null===(n=this.wavesurfer)||void 0===n?void 0:n.getWrapper();if(!(s&&s instanceof HTMLElement))return ()=>{};let o=null,h=0,a=0;return i(s,((t,e,i)=>{o&&o._onUpdate(t,i>h?"end":"start",a);}),(e=>{var i,n;if(h=e,!this.wavesurfer)return;const s=this.wavesurfer.getDuration(),l=null===(n=null===(i=this.wavesurfer)||void 0===i?void 0:i.getDecodedData())||void 0===n?void 0:n.numberOfChannels,{width:d}=this.wavesurfer.getWrapper().getBoundingClientRect();a=h/d*s;const c=e/d*s,u=(e+5)/d*s;o=new r(Object.assign(Object.assign({},t),{start:c,end:u}),s,l),this.emit("region-initialized",o),o.element&&this.regionsContainer.appendChild(o.element);}),(()=>{o&&(this.saveRegion(o),o.updatingSide=void 0,o=null);}),e)}clearRegions(){this.regions.slice().forEach((t=>t.remove())),this.regions=[];}destroy(){this.clearRegions(),super.destroy(),this.regionsContainer.remove();}}
43
44var root$3 = from_html(`<input id="volume" class="volume-slider svelte-c0ird4" type="range" min="0" max="1" step="0.01"/>`);
45
46function VolumeControl($$anchor, $$props) {
47	push$1($$props, true);
48
49	let currentVolume = prop($$props, 'currentVolume', 15),
50		show_volume_slider = prop($$props, 'show_volume_slider', 15);
51
52	let volumeElement;
53
54	onMount(() => {
55		adjustSlider();
56	});
57
58	const adjustSlider = () => {
59		let slider = volumeElement;
60
61		if (!slider) return;
62
63		slider.style.background = `linear-gradient(to right, var(--color-accent) ${currentVolume() * 100}%, var(--neutral-400) ${currentVolume() * 100}%)`;
64	};
65
66	user_effect(() => {
67		currentVolume();
68		adjustSlider();
69	});
70
71	var input = root$3();
72
73	remove_input_defaults(input);
74	input.__focusout = () => show_volume_slider(false);
75
76	input.__input = (e) => {
77		if (e.target instanceof HTMLInputElement) {
78			currentVolume(parseFloat(e.target.value));
79			$$props.waveform?.setVolume(currentVolume());
80		}
81	};
82
83	bind_this(input, ($$value) => volumeElement = $$value, () => volumeElement);
84	template_effect(() => set_value(input, currentVolume()));
85	append($$anchor, input);
86	pop();
87}
88
89delegate(['focusout', 'input']);
90
91var root_4$2 = from_html(`<button class="action icon cc-button svelte-72dh9g" data-testid="subtitles-toggle"><!></button>`);
92var root_6 = from_html(`<button class="action icon svelte-72dh9g" aria-label="Reset audio"><!></button>`);
93var root_7 = from_html(`<button class="action icon svelte-72dh9g" aria-label="Trim audio to selection"><!></button>`);
94var root_8 = from_html(`<button class="text-button svelte-72dh9g">Trim</button> <button class="text-button svelte-72dh9g">Cancel</button>`, 1);
95var root_5$1 = from_html(`<!> <!>`, 1);
96var root$2 = from_html(`<div class="controls svelte-72dh9g" data-testid="waveform-control
96s"><div class="control-wrapper svelte-72dh9g"><button class="action icon volume svelte-72dh9g" aria-label="Adjust volume"><!></button> <!> <button><span> </span></button></div> <div class="play-pause-wrapper svelte-72dh9g"><button class="rewind icon svelte-72dh9g"><!></button> <button class="play-pause-button icon svelte-72dh9g"><!></button> <button class="skip icon svelte-72dh9g"><!></button></div> <div class="settings-wrapper svelte-72dh9g"><!> <!></div></div>`);
97
98function WaveformControls($$anchor, $$props) {
99	push$1($$props, true);
100
101	let show_redo = prop($$props, 'show_redo', 3, false),
102		interactive = prop($$props, 'interactive', 3, false),
103		mode = prop($$props, 'mode', 15),
104		waveform_options = prop($$props, 'waveform_options', 19, () => ({})),
105		trim_region_settings = prop($$props, 'trim_region_settings', 19, () => ({})),
106		show_volume_slider = prop($$props, 'show_volume_slider', 15),
107		editable = prop($$props, 'editable', 3, true),
108		subtitles_toggle = prop($$props, 'subtitles_toggle', 15),
109		show_subtitles = prop($$props, 'show_subtitles', 3, false),
110		trimDuration = prop($$props, 'trimDuration', 15);
111
112	let playbackSpeeds = [0.5, 1, 1.5, 2];
113	let playbackSpeed = state(proxy(playbackSpeeds[1]));
114	let trimRegion = state(null);
115	let activeRegion = state(null);
116	let leftRegionHandle = state(null);
117	let rightRegionHandle = state(null);
118	let activeHandle = state("");
119	let currentVolume = state(1);
120
121	function ensureTrimRegion() {
122		if ($$props.container && $$props.waveform && !get(trimRegion)) {
123			set(trimRegion, $$props.waveform.registerPlugin(o.create()), true);
124		}
125	}
126
127	onMount(() => {
128		ensureTrimRegion();
129	});
130
131	user_effect(() => {
132		if (!get(trimRegion)) return;
133
134		const handler = (region) => {
135			region.play();
136		};
137
138		get(trimRegion).on("region-out", handler);
139
140		return () => {
141			get(trimRegion)?.un("region-out", handler);
142		};
143	});
144
145	user_effect(() => {
146		if (!get(trimRegion)) return;
147
148		const handler = (region) => {
149			trimDuration(region.end - region.start);
150		};
151
152		get(trimRegion).on("region-updated", handler);
153
154		return () => {
155			get(trimRegion)?.un("region-updated", handler);
156		};
157	});
158
159	user_effect(() => {
160		if (!get(trimRegion)) return;
161
162		const handler = (region, e) => {
163			e.stopPropagation();
164			set(activeRegion, region, true);
165			region.play();
166		};
167
168		get(trimRegion).on("region-clicked", handler);
169
170		return () => {
171			get(trimRegion)?.un("region-clicked", handler);
172		};
173	});
174
175	const addTrimRegion = () => {
176		if (!get(trimRegion)) return;
177
178		set(
179			activeRegion,
180			get(trimRegion)?.addRegion({
181				start: $$props.audio_duration / 4,
182				end: $$props.audio_duration / 2,
183				...trim_region_settings()
184			}),
185			true
186		);
187
188		trimDuration(get(activeRegion).end - get(activeRegion).start);
189	};
190
191	user_effect(() => {
192		if (get(activeRegion)) {
193			const shadowRoot = $$props.container.children[0].shadowRoot;
194
195			set(rightRegionHandle, shadowRoot.querySelector('[data-resize="right"]'), true);
196			set(leftRegionHandle, shadowRoot.querySelector('[data-resize="left"]'), true);
197
198			if (get(leftRegionHandle) && get(rightRegionHandle)) {
199				get(leftRegionHandle).setAttribute("role", "button");
200				get(rightRegionHandle).setAttribute("role", "button");
201				get(leftRegionHandle)?.setAttribute("aria-label", "Drag to adjust start time");
202				get(rightRegionHandle)?.setAttribute("aria-label", "Drag to adjust end time");
203				get(leftRegionHandle)?.setAttribute("tabindex", "0");
204				get(rightRegionHandle)?.setAttribute("tabindex", "0");
205
206				get(leftRegionHandle).addEventListener("focus", () => {
207					if (get(trimRegion)) set(activeHandle, "left");
208				});
209
210				get(rightRegionHandle).addEventListener("focus", () => {
211					if (get(trimRegion)) set(activeHandle, "right");
212				});
213			}
214		}
215	});
216
217	const trimAudio = () => {
218		if ($$props.waveform && get(trimRegion)) {
219			if (get(activeRegion)) {
220				const start = get(activeRegion).start;
221				const end = get(activeRegion).end;
222
223				$$props.handle_trim_audio(start, end);
224				mode("");
225				set(activeRegion, null);
226			}
227		}
228	};
229
230	const clearRegions = () => {
231		get(trimRegion)?.getRegions().forEach((region) => {
232			region.remove();
233		});
234
235		get(trimRegion)?.clearRegions();
236	};
237
238	const toggleTrimmingMode = () => {
239		clearRegions();
240
241		if (mode()) {
242			mode("");
243		} else {
244			mode("edit");
245			ensureTrimRegion();
246			addTrimRegion();
247		}
248	};
249
250	const toggleSubtitles = () => {
251		subtitles_toggle(!subtitles_toggle());
252	};
253
254	const adjustRegionHandles = (handle, key) => {
255		let newStart;
256		let newEnd;
257
258		if (!get(activeRegion)) return;
259
260		if (handle === "left") {
261			if (key === "ArrowLeft") {
262				newStart = get(activeRegion).start - 0.05;
263				newEnd = get(activeRegion).end;
264			} else {
265				newStart = get(activeRegion).start + 0.05;
266				newEnd = get(activeRegion).end;
267			}
268		} else {
269			if (key === "ArrowLeft") {
270				newStart = get(activeRegion).start;
271				newEnd = get(activeRegion).end - 0.05;
272			} else {
273				newStart = get(activeRegion).start;
274				newEnd = get(activeRegion).end + 0.05;
275			}
276		}
277
278		get(activeRegion).setOptions({ start: newStart, end: newEnd });
279		trimDuration(get(activeRegion).end - get(activeRegion).start);
280	};
281
282	user_effect(() => {
283		if (get(trimRegion)) {
284			const handleKeydown = (e) => {
285				if (e.key === "ArrowLeft") {
286					adjustRegionHandles(get(activeHandle), "ArrowLeft");
287				} else if (e.key === "ArrowRight") {
288					adjustRegionHandles(get(activeHandle), "ArrowRight");
289				}
290			};
291
292			window.addEventListener("keydown", handleKeydown);
293
294			return () => {
295				window.removeEventListener("keydown", handleKeydown);
296			};
297		}
298	});
299
300	var div = root$2();
301	var div_1 = child(div);
302	var button = child(div_1);
303
304	button.__click = () => show_volume_slider(!show_volume_slider());
305
306	let styles;
307	var node = child(button);
308
309	VolumeLevels(node, {
310		get currentVolume() {
311			return get(currentVolume);
312		}
313	});
314
315	reset(button);
316
317	var node_1 = sibling(button, 2);
318
319	{
320		var consequent = ($$anchor) => {
321			VolumeControl($$anchor, {
322				get waveform() {
323					return $$props.waveform;
324				},
325
326				get currentVolume() {
327					return get(currentVolume);
328				},
329
330				set currentVolume($$value) {
331					set(currentVolume, $$value, true);
332				},
333
334				get show_volume_slider() {
335					return show_volume_slider();
336				},
337
338				set show_volume_slider($$value) {
339					show_volume_slider($$value);
340				}
341			});
342		};
343
344		if_block(node_1, ($$render) => {
345			if (show_volume_slider()) $$render(consequent);
346		});
347	}
348
349	var button_1 = sibling(node_1, 2);
350	let classes;
351
352	button_1.__click = () => {
353		set(playbackSpeed, playbackSpeeds[(playbackSpeeds.indexOf(get(playbackSpeed)) + 1) % playbackSpeeds.length], true);
354		$$props.waveform?.setPlaybackRate(get(playbackSpeed));
355	};
356
357	var span = child(button_1);
358	var text = child(span);
359
360	reset(span);
361	reset(button_1);
362	reset(div_1);
363
364	var div_2 = sibling(div_1, 2);
365	var button_2 = child(div_2);
366
367	button_2.__click = () => $$props.waveform?.skip(get_skip_rewind_amount($$props.audio_duration, waveform_options().skip_length) * -1);
368
369	var node_2 = child(button_2);
370
371	Backward(node_2);
372	reset(button_2);
373
374	var button_3 = sibling(button_2, 2);
375
376	button_3.__click = () => $$props.waveform?.playPause();
377
378	var node_3 = child(button_3);
379
380	{
381		var consequent_1 = ($$anchor) => {
382			Pause($$anchor);
383		};
384
385		var alternate = ($$anchor) => {
386			Play($$anchor);
387		};
388
389		if_block(node_3, ($$render) => {
390			if ($$props.playing) $$render(consequent_1); else $$render(alternate, false);
391		});
392	}
393
394	reset(button_3);
395
396	var button_4 = sibling(button_3, 2);
397
398	button_4.__click = () => $$props.waveform?.skip(get_skip_rewind_amount($$props.audio_duration, waveform_options().skip_length));
399
400	var node_4 = child(button_4);
401
402	Forward(node_4);
403	reset(button_4);
404	reset(div_2);
405
406	var div_3 = sibling(div_2, 2);
407	var node_5 = child(div_3);
408
409	{
410		var consequent_2 = ($$anchor) => {
411			var button_5 = root_4$2();
412
413			button_5.__click = toggleSubtitles;
414
415			var node_6 = child(button_5);
416
417			ClosedCaption(node_6);
418			reset(button_5);
419			template_effect(() => set_style(button_5, `color: ${subtitles_toggle() ? 'var(--color-accent)' : 'var(--neutral-400)'}`));
420			append($$anchor, button_5);
421		};
422
423		if_block(node_5, ($$render) => {
424			if (show_subtitles()) $$render(consequent_2);
425		});
426	}
427
428	var node_7 = sibling(node_5, 2);
429
430	{
431		var consequent_5 = ($$anchor) => {
432			var fragment_3 = root_5$1();
433			var node_8 = first_child(fragment_3);
434
435			{
436				var consequent_3 = ($$anchor) => {
437					var button_6 = root_6();
438
439					button_6.__click = () => {
440						$$props.handle_reset_value();
441						clearRegions();
442						mode("");
443					};
444
445					var node_9 = child(button_6);
446
447					Undo(node_9);
448					reset(button_6);
449					append($$anchor, button_6);
450				};
451
452				if_block(node_8, ($$render) => {
453					if (show_redo() && !mode()) $$render(consequent_3);
454				});
455			}
456
457			var node_10 = sibling(node_8, 2);
458
459			{
460				var consequent_4 = ($$anchor) => {
461					var button_7 = root_7();
462
463					button_7.__click = toggleTrimmingMode;
464
465					var node_11 = child(button_7);
466
467					Trim(node_11);
468					reset(button_7);
469					append($$anchor, button_7);
470				};
471
472				var alternate_1 = ($$anchor) => {
473					var fragment_4 = root_8();
474					var button_8 = first_child(fragment_4);
475
476					button_8.__click = trimAudio;
477
478					var button_9 = sibling(button_8, 2);
479
480					button_9.__click = toggleTrimmingMode;
481					append($$anchor, fragment_4);
482				};
483
484				if_block(node_10, ($$render) => {
485					if (!mode()) $$render(consequent_4); else $$render(alternate_1, false);
486				});
487			}
488
489			append($$anchor, fragment_3);
490		};
491
492		if_block(node_7, ($$render) => {
493			if (editable() && interactive()) $$render(consequent_5);
494		});
495	}
496
497	reset(div_3);
498	reset(div);
499
500	template_effect(
501		($0, $1, $2, $3) => {
502			styles = set_style(button, '', styles, {
503				color: show_volume_slider() ? "var(--color-accent)" : "var(--neutral-400)"
504			});
505
506			classes = set_class(button_1, 1, 'playback icon svelte-72dh9g', null, classes, { hidden: show_volume_slider() });
507			set_attribute(button_1, 'aria-label', $0);
508			set_text(text, `${get(playbackSpeed) ?? ''}x`);
509			set_attribute(button_2, 'aria-label', $1);
510			set_attribute(button_3, 'aria-label', $2);
511			set_attribute(button_4, 'aria-label', `Skip forward by ${$3 ?? ''} seconds`);
512		},
513		[
514			() => `Adjust playback speed to ${playbackSpeeds[(playbackSpeeds.indexOf(get(playbackSpeed)) + 1) % playbackSpeeds.length]}x`,
515			() => `Skip backwards by ${get_skip_rewind_amount($$props.audio_duration, waveform_options().skip_length)} seconds`,
516			() => $$props.playing
517				? $$props.i18n("audio.pause")
518				: $$props.i18n("audio.play"),
519			() => get_skip_rewind_amount($$props.audio_duration, waveform_options().skip_length)
520		]
521	);
522
523	append($$anchor, div);
524	pop();
525}
526
527delegate(['click']);
528
529function getDefaultExportFromCjs (x) {
530	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
531}
532
533var urlToolkit = {exports: {}};
534
535(function (module, exports$1) {
536	// see https://tools.ietf.org/html/rfc1808
537
538	(function (root) {
539	  var URL_REGEX =
540	    /^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/;
541	  var FIRST_SEGMENT_REGEX = /^(?=([^\/?#]*))\1([^]*)$/;
542	  var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
543	  var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g;
544
545	  var URLToolkit = {
546	    // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
547	    // E.g
548	    // With opts.alwaysNormalize = false (default, spec compliant)
549	    // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
550	    // With opts.alwaysNormalize = true (not spec compliant)
551	    // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
552	    buildAbsoluteURL: function (baseURL, relativeURL, opts) {
553	      opts = opts || {};
554	      // remove any remaining space and CRLF
555	      baseURL = baseURL.trim();
556	      relativeURL = relativeURL.trim();
557	      if (!relativeURL) {
558	        // 2a) If the embedded URL is entirely empty, it inherits the
559	        // entire base URL (i.e., is set equal to the base URL)
560	        // and we are done.
561	        if (!opts.alwaysNormalize) {
562	          return baseURL;
563	        }
564	        var basePartsForNormalise = URLToolkit.parseURL(baseURL);
565	        if (!basePartsForNormalise) {
566	          throw new Error('Error trying to parse base URL.');
567	        }
568	        basePartsForNormalise.path = URLToolkit.normalizePath(
569	          basePartsForNormalise.path
570	        );
571	        return URLToolkit.buildURLFromParts(basePartsForNormalise);
572	      }
573	      var relativeParts = URLToolkit.parseURL(relativeURL);
574	      if (!relativeParts) {
575	        throw new Error('Error trying to parse relative URL.');
576	      }
577	      if (relativeParts.scheme) {
578	        // 2b) If the embedded URL starts with a scheme name, it is
579	        // interpreted as an absolute URL and we are done.
vendor: 2,415 bytes, lines 580-629
580	        if (!opts.alwaysNormalize) {
581	          return relativeURL;
582	        }
583	        relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
584	        return URLToolkit.buildURLFromParts(relativeParts);
585	      }
586	      var baseParts = URLToolkit.parseURL(baseURL);
587	      if (!baseParts) {
588	        throw new Error('Error trying to parse base URL.');
589	      }
590	      if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
591	        // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
592	        // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
593	        var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
594	        baseParts.netLoc = pathParts[1];
595	        baseParts.path = pathParts[2];
596	      }
597	      if (baseParts.netLoc && !baseParts.path) {
598	        baseParts.path = '/';
599	      }
600	      var builtParts = {
601	        // 2c) Otherwise, the embedded URL inherits the scheme of
602	        // the base URL.
603	        scheme: baseParts.scheme,
604	        netLoc: relativeParts.netLoc,
605	        path: null,
606	        params: relativeParts.params,
607	        query: relativeParts.query,
608	        fragment: relativeParts.fragment,
609	      };
610	      if (!relativeParts.netLoc) {
611	        // 3) If the embedded URL's <net_loc> is non-empty, we skip to
612	        // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
613	        // (if any) of the base URL.
614	        builtParts.netLoc = baseParts.netLoc;
615	        // 4) If the embedded URL path is preceded by a slash "/", the
616	        // path is not relative and we skip to Step 7.
617	        if (relativeParts.path[0] !== '/') {
618	          if (!relativeParts.path) {
619	            // 5) If the embedded URL path is empty (and not preceded by a
620	            // slash), then the embedded URL inherits the base URL path
621	            builtParts.path = baseParts.path;
622	            // 5a) if the embedded URL's <params> is non-empty, we skip to
623	            // step 7; otherwise, it inherits the <params> of the base
624	            // URL (if any) and
625	            if (!relativeParts.params) {
626	              builtParts.params = baseParts.params;
627	              // 5b) if the embedded URL's <query> is non-empty, we skip to
628	              // step 7; otherwise, it inherits the <query> of the base
629	              // URL (if any) and we skip to step 7.
630	              if (!relativeParts.query) {
631	                builtParts.query = baseParts.query;
632	              }
633	            }
634	          } else {
635	            // 6) The last segment of the base URL's path (anything
636	            // following the rightmost slash "/", or the entire path if no
637	            // slash is present) is removed and the embedded URL's path is
638	            // appended in its place.
639	            var baseURLPath = baseParts.path;
640	            var newPath =
641	              baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) +
642	              relativeParts.path;
643	            builtParts.path = URLToolkit.normalizePath(newPath);
644	          }
645	        }
646	      }
647	      if (builtParts.path === null) {
648	        builtParts.path = opts.alwaysNormalize
649	          ? URLToolkit.normalizePath(relativeParts.path)
650	          : relativeParts.path;
651	      }
652	      return URLToolkit.buildURLFromParts(builtParts);
653	    },
654	    parseURL: function (url) {
655	      var parts = URL_REGEX.exec(url);
656	      if (!parts) {
657	        return null;
658	      }
659	      return {
660	        scheme: parts[1] || '',
661	        netLoc: parts[2] || '',
662	        path: parts[3] || '',
663	        params: parts[4] || '',
664	        query: parts[5] || '',
665	        fragment: parts[6] || '',
666	      };
667	    },
668	    normalizePath: function (path) {
669	      // The following operations are
670	      // then applied, in order, to the new path:
671	      // 6a) All occurrences of "./", where "." is a complete path
672	      // segment, are removed.
673	      // 6b) If the path ends with "." as a complete path segment,
674	      // that "." is removed.
675	      path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
676	      // 6c) All occurrences of "<segment>/../", where <segment> is a
677	      // complete path segment not equal to "..", are removed.
678	      // Removal of these path segments is performed iteratively,
679	      // removing the leftmost matching pattern on each iteration,
680	      // until no matching pattern remains.
681	      // 6d) If the path ends with "<segment>/..", where <segment> is a
682	      // complete path segment not equal to "..", that
683	      // "<segment>/.." is removed.
684	      while (
685	        path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length
686	      ) {}
687	      return path.split('').reverse().join('');
688	    },
689	    buildURLFromParts: function (parts) {
690	      return (
691	        parts.scheme +
692	        parts.netLoc +
693	        parts.path +
694	        parts.params +
695	        parts.query +
696	        parts.fragment
697	      );
698	    },
699	  };
700
701	  module.exports = URLToolkit;
702	})(); 
703} (urlToolkit));
704
705var urlToolkitExports = urlToolkit.exports;
706
707function ownKeys(e, r) {
708  var t = Object.keys(e);
709  if (Object.getOwnPropertySymbols) {
710    var o = Object.getOwnPropertySymbols(e);
711    r && (o = o.filter(function (r) {
712      return Object.getOwnPropertyDescriptor(e, r).enumerable;
713    })), t.push.apply(t, o);
714  }
715  return t;
716}
717function _objectSpread2(e) {
718  for (var r = 1; r < arguments.length; r++) {
719    var t = null != arguments[r] ? arguments[r] : {};
720    r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
721      _defineProperty(e, r, t[r]);
722    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
723      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
724    });
725  }
726  return e;
727}
728function _toPrimitive(t, r) {
729  if ("object" != typeof t || !t) return t;
730  var e = t[Symbol.toPrimitive];
731  if (void 0 !== e) {
732    var i = e.call(t, r);
733    if ("object" != typeof i) return i;
734    throw new TypeError("@@toPrimitive must return a primitive value.");
735  }
736  return ("string" === r ? String : Number)(t);
737}
738function _toPropertyKey(t) {
739  var i = _toPrimitive(t, "string");
740  return "symbol" == typeof i ? i : String(i);
741}
742function _defineProperty(obj, key, value) {
743  key = _toPropertyKey(key);
744  if (key in obj) {
745    Object.defineProperty(obj, key, {
746      value: value,
747      enumerable: true,
748      configurable: true,
749      writable: true
750    });
751  } else {
752    obj[key] = value;
753  }
754  return obj;
755}
756function _extends() {
757  _extends = Object.assign ? Object.assign.bind() : function (target) {
758    for (var i = 1; i < arguments.length; i++) {
759      var source = arguments[i];
760      for (var key in source) {
761        if (Object.prototype.hasOwnProperty.call(source, key)) {
762          target[key] = source[key];
763        }
764      }
765    }
766    return target;
767  };
768  return _extends.apply(this, arguments);
769}
770
771// https://caniuse.com/mdn-javascript_builtins_number_isfinite
772const isFiniteNumber = Number.isFinite || function (value) {
773  return typeof value === 'number' && isFinite(value);
774};
775
776// https://caniuse.com/mdn-javascript_builtins_number_issafeinteger
777const isSafeInteger = Number.isSafeInteger || function (value) {
778  return typeof value === 'number' && Math.abs(value) <= MAX_SAFE_INTEGER;
779};
780const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
781
782let Events = /*#__PURE__*/function (Events) {
783  Events["MEDIA_ATTACHING"] = "hlsMediaAttaching";
784  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
785  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
786  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
787  Events["BUFFER_RESET"] = "hlsBufferReset";
788  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
789  Events["BUFFER_CREATED"] = "hlsBufferCreated";
790  Events["BUFFER_APPENDING"] = "hlsBufferAppending";
791  Events["BUFFER_APPENDED"] = "hlsBufferAppended";
792  Events["BUFFER_EOS"] = "hlsBufferEos";
793  Events["BUFFER_FLUSHING"] = "hlsBufferFlushing";
794  Events["BUFFER_FLUSHED"] = "hlsBufferFlushed";
795  Events["MANIFEST_LOADING"] = "hlsManifestLoading";
796  Events["MANIFEST_LOADED"] = "hlsManifestLoaded";
797  Events["MANIFEST_PARSED"] = "hlsManifestParsed";
798  Events["LEVEL_SWITCHING"] = "hlsLevelSwitching";
799  Events["LEVEL_SWITCHED"] = "hlsLevelSwitched";
800  Events["LEVEL_LOADING"] = "hlsLevelLoading";
801  Events["LEVEL_LOADED"] = "hlsLevelLoaded";
802  Events["LEVEL_UPDATED"] = "hlsLevelUpdated";
803  Events["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated";
804  Events["LEVELS_UPDATED"] = "hlsLevelsUpdated";
805  Events["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated";
806  Events["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching";
807  Events["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched";
808  Events["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading";
809  Events["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded";
810  Events["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated";
811  Events["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared";
812  Events["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch";
813  Events["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading";
814  Events["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded";
815  Events["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed";
816  Events["CUES_PARSED"] = "hlsCuesParsed";
817  Events["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound";
818  Events["INIT_PTS_FOUND"] = "hlsInitPtsFound";
819  Events["FRAG_LOADING"] = "hlsFragLoading";
820  Events["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted";
821  Events["FRAG_LOADED"] = "hlsFragLoaded";
822  Events["FRAG_DECRYPTED"] = "hlsFragDecrypted";
823  Events["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment";
824  Events["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata";
825  Events["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata";
826  Events["FRAG_PARSED"] = "hlsFragParsed";
827  Events["FRAG_BUFFERED"] = "hlsFragBuffered";
828  Events["FRAG_CHANGED"] = "hlsFragChanged";
829  Events["FPS_DROP"] = "hlsFpsDrop";
830  Events["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping";
831  Events["MAX_AUTO_LEVEL_UPDATED"] = "hlsMaxAutoLevelUpdated";
832  Events["ERROR"] = "hlsError";
833  Events["DESTROYING"] = "hlsDestroying";
834  Events["KEY_LOADING"] = "hlsKeyLoading";
835  Events["KEY_LOADED"] = "hlsKeyLoaded";
836  Events["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached";
837  Events["BACK_BUFFER_REACHED"] = "hlsBackBufferReached";
838  Events["STEERING_MANIFEST_LOADED"] = "hlsSteeringManifestLoaded";
839  return Events;
840}({});
841
842/**
843 * Defines each Event type and payload by Event name. Used in {@link hls.js#HlsEventEmitter} to strongly type the event listener API.
844 */
845
846let ErrorTypes = /*#__PURE__*/function (ErrorTypes) {
847  ErrorTypes["NETWORK_ERROR"] = "networkError";
848  ErrorTypes["MEDIA_ERROR"] = "mediaError";
849  ErrorTypes["KEY_SYSTEM_ERROR"] = "keySystemError";
850  ErrorTypes["MUX_ERROR"] = "muxError";
851  ErrorTypes["OTHER_ERROR"] = "otherError";
852  return ErrorTypes;
853}({});
854let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
855  ErrorDetails["KEY_SYSTEM_NO_KEYS"] = "keySystemNoKeys";
856  ErrorDetails["KEY_SYSTEM_NO_ACCESS"] = "keySystemNoAccess";
857  ErrorDetails["KEY_SYSTEM_NO_SESSION"] = "keySystemNoSession";
858  ErrorDetails["KEY_SYSTEM_NO_CONFIGURED_LICENSE"] = "keySystemNoConfiguredLicense";
859  ErrorDetails["KEY_SYSTEM_LICENSE_REQUEST_FAILED"] = "keySystemLicenseRequestFailed";
860  ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED"] = "keySystemServerCertificateRequestFailed";
861  ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED"] = "keySystemServerCertificateUpdateFailed";
862  ErrorDetails["KEY_SYSTEM_SESSION_UPDATE_FAILED"] = "keySystemSessionUpdateFailed";
863  ErrorDetails["KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED"] = "keySystemStatusOutputRestricted";
864  ErrorDetails["KEY_SYSTEM_STATUS_INTERNAL_ERROR"] = "keySystemStatusInternalError";
865  ErrorDetails["MANIFEST_LOAD_ERROR"] = "manifestLoadError";
866  ErrorDetails["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut";
867  ErrorDetails["MANIFEST_PARSING_ERROR"] = "manifestParsingError";
868  ErrorDetails["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError";
869  ErrorDetails["LEVEL_EMPTY_ERROR"] = "levelEmptyError";
870  ErrorDetails["LEVEL_LOAD_ERROR"] = "levelLoadError";
871  ErrorDetails["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut";
872  ErrorDetails["LEVEL_PARSING_ERROR"] = "levelParsingError";
873  ErrorDetails["LEVEL_SWITCH_ERROR"] = "levelSwitchError";
874  ErrorDetails["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError";
875  ErrorDetails["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut";
876  ErrorDetails["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError";
877  ErrorDetails["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut";
878  ErrorDetails["FRAG_LOAD_ERROR"] = "fragLoadError";
879  ErrorDetails["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut";
880  ErrorDetails["FRAG_DECRYPT_ERROR"] = "fragDecryptError";
881  ErrorDetails["FRAG_PARSING_ERROR"] = "fragParsingError";
882  ErrorDetails["FRAG_GAP"] = "fragGap";
883  ErrorDetails["REMUX_ALLOC_ERROR"] = "remuxAllocError";
884  ErrorDetails["KEY_LOAD_ERROR"] = "keyLoadError";
885  ErrorDetails["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut";
886  ErrorDetails["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError";
887  ErrorDetails["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError";
888  ErrorDetails["BUFFER_APPEND_ERROR"] = "bufferAppendError";
889  ErrorDetails["BUFFER_APPENDING_ERROR"] = "bufferAppendingError";
890  ErrorDetails["BUFFER_STALLED_ERROR"] = "bufferStalledError";
891  ErrorDetails["BUFFER_FULL_ERROR"] = "bufferFullError";
892  ErrorDetails["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole";
893  ErrorDetails["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall";
894  ErrorDetails["INTERNAL_EXCEPTION"] = "internalException";
895  ErrorDetails["INTERNAL_ABORTED"] = "aborted";
896  ErrorDetails["UNKNOWN"] = "unknown";
897  return ErrorDetails;
898}({});
899
900const noop = function noop() {};
901const fakeLogger = {
902  trace: noop,
903  debug: noop,
904  log: noop,
905  warn: noop,
906  info: noop,
907  error: noop
908};
909let exportedLogger = fakeLogger;
910
911// let lastCallTime;
912// function formatMsgWithTimeInfo(type, msg) {
913//   const now = Date.now();
914//   const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
915//   lastCallTime = now;
916//   msg = (new Date(now)).toISOString() + ' | [' +  type + '] > ' + msg + ' ( ' + diff + ' ms )';
917//   return msg;
918// }
919
920function consolePrintFn(type) {
921  const func = self.console[type];
922  if (func) {
923    return func.bind(self.console, `[${type}] >`);
924  }
925  return noop;
926}
927function exportLoggerFunctions(debugConfig, ...functions) {
928  functions.forEach(function (type) {
929    exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
930  });
931}
932function enableLogs(debugConfig, id) {
933  // check that console is available
934  if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
935    exportLoggerFunctions(debugConfig,
936    // Remove out from list here to hard-disable a log-level
937    // 'trace',
938    'debug', 'log', 'info', 'warn', 'error');
939    // Some browsers don't allow to use bind on console object anyway
940    // fallback to default if needed
941    try {
942      exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.13"}`);
943    } catch (e) {
944      exportedLogger = fakeLogger;
945    }
946  } else {
947    exportedLogger = fakeLogger;
948  }
949}
950const logger = exportedLogger;
951
952const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
953const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
954
955// adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js
956class AttrList {
957  constructor(attrs) {
958    if (typeof attrs === 'string') {
959      attrs = AttrList.parseAttrList(attrs);
960    }
961    _extends(this, attrs);
962  }
963  get clientAttrs() {
964    return Object.keys(this).filter(attr => attr.substring(0, 2) === 'X-');
965  }
966  decimalInteger(attrName) {
967    const intValue = parseInt(this[attrName], 10);
968    if (intValue > Number.MAX_SAFE_INTEGER) {
969      return Infinity;
970    }
971    return intValue;
972  }
973  hexadecimalInteger(attrName) {
974    if (this[attrName]) {
975      let stringValue = (this[attrName] || '0x').slice(2);
976      stringValue = (stringValue.length & 1 ? '0' : '') + stringValue;
977      const value = new Uint8Array(stringValue.length / 2);
978      for (let i = 0; i < stringValue.length / 2; i++) {
979        value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
980      }
981      return value;
982    } else {
983      return null;
984    }
985  }
986  hexadecimalIntegerAsNumber(attrName) {
987    const intValue = parseInt(this[attrName], 16);
988    if (intValue > Number.MAX_SAFE_INTEGER) {
989      return Infinity;
990    }
991    return intValue;
992  }
993  decimalFloatingPoint(attrName) {
994    return parseFloat(this[attrName]);
995  }
996  optionalFloat(attrName, defaultValue) {
997    const value = this[attrName];
998    return value ? parseFloat(value) : defaultValue;
999  }
1000  enumeratedString(attrName) {
1001    return this[attrName];
1002  }
1003  bool(attrName) {
1004    return this[attrName] === 'YES';
1005  }
1006  decimalResolution(attrName) {
1007    const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
1008    if (res === null) {
1009      return undefined;
1010    }
1011    return {
1012      width: parseInt(res[1], 10),
1013      height: parseInt(res[2], 10)
1014    };
1015  }
1016  static parseAttrList(input) {
1017    let match;
1018    const attrs = {};
1019    const quote = '"';
1020    ATTR_LIST_REGEX.lastIndex = 0;
1021    while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
1022      let value = match[2];
1023      if (value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1) {
1024        value = value.slice(1, -1);
1025      }
1026      const name = match[1].trim();
1027      attrs[name] = value;
1028    }
1029    return attrs;
1030  }
1031}
1032
1033// Avoid exporting const enum so that these values can be inlined
1034
1035function isDateRangeCueAttribute(attrName) {
1036  return attrName !== "ID" && attrName !== "CLASS" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
1037}
1038function isSCTE35Attribute(attrName) {
1039  return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN";
1040}
1041class DateRange {
1042  constructor(dateRangeAttr, dateRangeWithSameId) {
1043    this.attr = void 0;
1044    this._startDate = void 0;
1045    this._endDate = void 0;
1046    this._badValueForSameId = void 0;
1047    if (dateRangeWithSameId) {
1048      const previousAttr = dateRangeWithSameId.attr;
1049      for (const key in previousAttr) {
1050        if (Object.prototype.hasOwnProperty.call(dateRangeAttr, key) && dateRangeAttr[key] !== previousAttr[key]) {
1051          logger.warn(`DATERANGE tag attribute: "${key}" does not match for tags with ID: "${dateRangeAttr.ID}"`);
1052          this._badValueForSameId = key;
1053          break;
1054        }
1055      }
1056      // Merge DateRange tags with the same ID
1057      dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr);
1058    }
1059    this.attr = dateRangeAttr;
1060    this._startDate = new Date(dateRangeAttr["START-DATE"]);
1061    if ("END-DATE" in this.attr) {
1062      const endDate = new Date(this.attr["END-DATE"]);
1063      if (isFiniteNumber(endDate.getTime())) {
1064        this._endDate = endDate;
1065      }
1066    }
1067  }
1068  get id() {
1069    return this.attr.ID;
1070  }
1071  get class() {
1072    return this.attr.CLASS;
1073  }
1074  get startDate() {
1075    return this._startDate;
1076  }
1077  get endDate() {
1078    if (this._endDate) {
1079      return this._endDate;
1080    }
1081    const duration = this.duration;
1082    if (duration !== null) {
1083      return new Date(this._startDate.getTime() + duration * 1000);
1084    }
1085    return null;
1086  }
1087  get duration() {
1088    if ("DURATION" in this.attr) {
1089      const duration = this.attr.decimalFloatingPoint("DURATION");
1090      if (isFiniteNumber(duration)) {
1091        return duration;
1092      }
1093    } else if (this._endDate) {
1094      return (this._endDate.getTime() - this._startDate.getTime()) / 1000;
1095    }
1096    return null;
1097  }
1098  get plannedDuration() {
1099    if ("PLANNED-DURATION" in this.attr) {
1100      return this.attr.decimalFloatingPoint("PLANNED-DURATION");
1101    }
1102    return null;
1103  }
1104  get endOnNext() {
1105    return this.attr.bool("END-ON-NEXT");
1106  }
1107  get isValid() {
1108    return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class);
1109  }
1110}
1111
1112class LoadStats {
1113  constructor() {
1114    this.aborted = false;
1115    this.loaded = 0;
1116    this.retry = 0;
1117    this.total = 0;
1118    this.chunkCount = 0;
1119    this.bwEstimate = 0;
1120    this.loading = {
1121      start: 0,
1122      first: 0,
1123      end: 0
1124    };
1125    this.parsing = {
1126      start: 0,
1127      end: 0
1128    };
1129    this.buffering = {
1130      start: 0,
1131      first: 0,
1132      end: 0
1133    };
1134  }
1135}
1136
1137var ElementaryStreamTypes = {
1138  AUDIO: "audio",
1139  VIDEO: "video",
1140  AUDIOVIDEO: "audiovideo"
1141};
1142class BaseSegment {
1143  constructor(baseurl) {
1144    this._byteRange = null;
1145    this._url = null;
1146    // baseurl is the URL to the playlist
1147    this.baseurl = void 0;
1148    // relurl is the portion of the URL that comes from inside the playlist.
1149    this.relurl = void 0;
1150    // Holds the types of data this fragment supports
1151    this.elementaryStreams = {
1152      [ElementaryStreamTypes.AUDIO]: null,
1153      [ElementaryStreamTypes.VIDEO]: null,
1154      [ElementaryStreamTypes.AUDIOVIDEO]: null
1155    };
1156    this.baseurl = baseurl;
1157  }
1158
1159  // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
1160  setByteRange(value, previous) {
1161    const params = value.split('@', 2);
1162    let start;
1163    if (params.length === 1) {
1164      start = (previous == null ? void 0 : previous.byteRangeEndOffset) || 0;
1165    } else {
1166      start = parseInt(params[1]);
1167    }
1168    this._byteRange = [start, parseInt(params[0]) + start];
1169  }
1170  get byteRange() {
1171    if (!this._byteRange) {
1172      return [];
1173    }
1174    return this._byteRange;
1175  }
1176  get byteRangeStartOffset() {
1177    return this.byteRange[0];
1178  }
1179  get byteRangeEndOffset() {
1180    return this.byteRange[1];
1181  }
1182  get url() {
1183    if (!this._url && this.baseurl && this.relurl) {
1184      this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, {
1185        alwaysNormalize: true
1186      });
1187    }
1188    return this._url || '';
1189  }
1190  set url(value) {
1191    this._url = value;
1192  }
1193}
1194
1195/**
1196 * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
1197 */
1198class Fragment extends BaseSegment {
1199  constructor(type, baseurl) {
1200    super(baseurl);
1201    this._decryptdata = null;
1202    this.rawProgramDateTime = null;
1203    this.programDateTime = null;
1204    this.tagList = [];
1205    // EXTINF has to be present for a m3u8 to be considered valid
1206    this.duration = 0;
1207    // sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
1208    this.sn = 0;
1209    // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption
1210    // core difference from the private field _decryptdata is the lack of the initialized IV
1211    // _decryptdata will set the IV for this segment based on the segment number in the fragment
1212    this.levelkeys = void 0;
1213    // A string representing the fragment type
1214    this.type = void 0;
1215    // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading
1216    this.loader = null;
1217    // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading
1218    this.keyLoader = null;
1219    // The level/track index to which the fragment belongs
1220    this.level = -1;
1221    // The continuity counter of the fragment
1222    this.cc = 0;
1223    // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
1224    this.startPTS = void 0;
1225    // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
1226    this.endPTS = void 0;
1227    // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
1228    this.startDTS = void 0;
1229    // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
1230    this.endDTS = void 0;
1231    // The start time of the fragment, as listed in the manifest. Updated after transmux complete.
1232    this.start = 0;
1233    // Set by `updateFragPTSDTS` in level-helper
1234    this.deltaPTS = void 0;
1235    // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
1236    this.maxStartPTS = void 0;
1237    // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
1238    this.minEndPTS = void 0;
1239    // Load/parse timing information
1240    this.stats = new LoadStats();
1241    // Init Segment bytes (unset for media segments)
1242    this.data = void 0;
1243    // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered
1244    this.bitrateTest = false;
1245    // #EXTINF  segment title
1246    this.title = null;
1247    // The Media Initialization Section for this segment
1248    this.initSegment = null;
1249    // Fragment is the last fragment in the media playlist
1250    this.endList = void 0;
1251    // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded
1252    this.gap = void 0;
1253    // Deprecated
1254    this.urlId = 0;
1255    this.type = type;
1256  }
1257  get decryptdata() {
1258    const {
1259      levelkeys
1260    } = this;
1261    if (!levelkeys && !this._decryptdata) {
1262      return null;
1263    }
1264    if (!this._decryptdata && this.levelkeys && !this.levelkeys.NONE) {
1265      const key = this.levelkeys.identity;
1266      if (key) {
1267        this._decryptdata = key.getDecryptData(this.sn);
1268      } else {
1269        const keyFormats = Object.keys(this.levelkeys);
1270        if (keyFormats.length === 1) {
1271          return this._decryptdata = this.levelkeys[keyFormats[0]].getDecryptData(this.sn);
1272        }
1273      }
1274    }
1275    return this._decryptdata;
1276  }
1277  get end() {
1278    return this.start + this.duration;
1279  }
1280  get endProgramDateTime() {
1281    if (this.programDateTime === null) {
1282      return null;
1283    }
1284    if (!isFiniteNumber(this.programDateTime)) {
1285      return null;
1286    }
1287    const duration = !isFiniteNumber(this.duration) ? 0 : this.duration;
1288    return this.programDateTime + duration * 1000;
1289  }
1290  get encrypted() {
1291    var _this$_decryptdata;
1292    // At the m3u8-parser level we need to add support for manifest signalled keyformats
1293    // when we want the fragment to start reporting that it is encrypted.
1294    // Currently, keyFormat will only be set for identity keys
1295    if ((_this$_decryptdata = this._decryptdata) != null && _this$_decryptdata.encrypted) {
1296      return true;
1297    } else if (this.levelkeys) {
1298      const keyFormats = Object.keys(this.levelkeys);
1299      const len = keyFormats.length;
1300      if (len > 1 || len === 1 && this.levelkeys[keyFormats[0]].encrypted) {
1301        return true;
1302      }
1303    }
1304    return false;
1305  }
1306  setKeyFormat(keyFormat) {
1307    if (this.levelkeys) {
1308      const key = this.levelkeys[keyFormat];
1309      if (key && !this._decryptdata) {
1310        this._decryptdata = key.getDecryptData(this.sn);
1311      }
1312    }
1313  }
1314  abortRequests() {
1315    var _this$loader, _this$keyLoader;
1316    (_this$loader = this.loader) == null ? void 0 : _this$loader.abort();
1317    (_this$keyLoader = this.keyLoader) == null ? void 0 : _this$keyLoader.abort();
1318  }
1319  setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial = false) {
1320    const {
1321      elementaryStreams
1322    } = this;
1323    const info = elementaryStreams[type];
1324    if (!info) {
1325      elementaryStreams[type] = {
1326        startPTS,
1327        endPTS,
1328        startDTS,
1329        endDTS,
1330        partial
1331      };
1332      return;
1333    }
1334    info.startPTS = Math.min(info.startPTS, startPTS);
1335    info.endPTS = Math.max(info.endPTS, endPTS);
1336    info.startDTS = Math.min(info.startDTS, startDTS);
1337    info.endDTS = Math.max(info.endDTS, endDTS);
1338  }
1339  clearElementaryStreamInfo() {
1340    const {
1341      elementaryStreams
1342    } = this;
1343    elementaryStreams[ElementaryStreamTypes.AUDIO] = null;
1344    elementaryStreams[ElementaryStreamTypes.VIDEO] = null;
1345    elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null;
1346  }
1347}
1348
1349/**
1350 * Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}.
1351 */
1352class Part extends BaseSegment {
1353  constructor(partAttrs, frag, baseurl, index, previous) {
1354    super(baseurl);
1355    this.fragOffset = 0;
1356    this.duration = 0;
1357    this.gap = false;
1358    this.independent = false;
1359    this.relurl = void 0;
1360    this.fragment = void 0;
1361    this.index = void 0;
1362    this.stats = new LoadStats();
1363    this.duration = partAttrs.decimalFloatingPoint('DURATION');
1364    this.gap = partAttrs.bool('GAP');
1365    this.independent = partAttrs.bool('INDEPENDENT');
1366    this.relurl = partAttrs.enumeratedString('URI');
1367    this.fragment = frag;
1368    this.index = index;
1369    const byteRange = partAttrs.enumeratedString('BYTERANGE');
1370    if (byteRange) {
1371      this.setByteRange(byteRange, previous);
1372    }
1373    if (previous) {
1374      this.fragOffset = previous.fragOffset + previous.duration;
1375    }
1376  }
1377  get start() {
1378    return this.fragment.start + this.fragOffset;
1379  }
1380  get end() {
1381    return this.start + this.duration;
1382  }
1383  get loaded() {
1384    const {
1385      elementaryStreams
1386    } = this;
1387    return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo);
1388  }
1389}
1390
1391const DEFAULT_TARGET_DURATION = 10;
1392
1393/**
1394 * Object representing parsed data from an HLS Media Playlist. Found in {@link hls.js#Level.details}.
1395 */
1396class LevelDetails {
1397  constructor(baseUrl) {
1398    this.PTSKnown = false;
1399    this.alignedSliding = false;
1400    this.averagetargetduration = void 0;
1401    this.endCC = 0;
1402    this.endSN = 0;
1403    this.fragments = void 0;
1404    this.fragmentHint = void 0;
1405    this.partList = null;
1406    this.dateRanges = void 0;
1407    this.live = true;
1408    this.ageHeader = 0;
1409    this.advancedDateTime = void 0;
1410    this.updated = true;
1411    this.advanced = true;
1412    this.availabilityDelay = void 0;
1413    // Manifest reload synchronization
1414    this.misses = 0;
1415    this.startCC = 0;
1416    this.startSN = 0;
1417    this.startTimeOffset = null;
1418    this.targetduration = 0;
1419    this.totalduration = 0;
1420    this.type = null;
1421    this.url = void 0;
1422    this.m3u8 = '';
1423    this.version = null;
1424    this.canBlockReload = false;
1425    this.canSkipUntil = 0;
1426    this.canSkipDateRanges = false;
1427    this.skippedSegments = 0;
1428    this.recentlyRemovedDateranges = void 0;
1429    this.partHoldBack = 0;
1430    this.holdBack = 0;
1431    this.partTarget = 0;
1432    this.preloadHint = void 0;
1433    this.renditionReports = void 0;
1434    this.tuneInGoal = 0;
1435    this.deltaUpdateFailed = void 0;
1436    this.driftStartTime = 0;
1437    this.driftEndTime = 0;
1438    this.driftStart = 0;
1439    this.driftEnd = 0;
1440    this.encryptedFragments = void 0;
1441    this.playlistParsingError = null;
1442    this.variableList = null;
1443    this.hasVariableRefs = false;
vendor: 4,705 bytes, lines 1444-1594
1444    this.fragments = [];
1445    this.encryptedFragments = [];
1446    this.dateRanges = {};
1447    this.url = baseUrl;
1448  }
1449  reloaded(previous) {
1450    if (!previous) {
1451      this.advanced = true;
1452      this.updated = true;
1453      return;
1454    }
1455    const partSnDiff = this.lastPartSn - previous.lastPartSn;
1456    const partIndexDiff = this.lastPartIndex - previous.lastPartIndex;
1457    this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff || !this.live;
1458    this.advanced = this.endSN > previous.endSN || partSnDiff > 0 || partSnDiff === 0 && partIndexDiff > 0;
1459    if (this.updated || this.advanced) {
1460      this.misses = Math.floor(previous.misses * 0.6);
1461    } else {
1462      this.misses = previous.misses + 1;
1463    }
1464    this.availabilityDelay = previous.availabilityDelay;
1465  }
1466  get hasProgramDateTime() {
1467    if (this.fragments.length) {
1468      return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime);
1469    }
1470    return false;
1471  }
1472  get levelTargetDuration() {
1473    return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION;
1474  }
1475  get drift() {
1476    const runTime = this.driftEndTime - this.driftStartTime;
1477    if (runTime > 0) {
1478      const runDuration = this.driftEnd - this.driftStart;
1479      return runDuration * 1000 / runTime;
1480    }
1481    return 1;
1482  }
1483  get edge() {
1484    return this.partEnd || this.fragmentEnd;
1485  }
1486  get partEnd() {
1487    var _this$partList;
1488    if ((_this$partList = this.partList) != null && _this$partList.length) {
1489      return this.partList[this.partList.length - 1].end;
1490    }
1491    return this.fragmentEnd;
1492  }
1493  get fragmentEnd() {
1494    var _this$fragments;
1495    if ((_this$fragments = this.fragments) != null && _this$fragments.length) {
1496      return this.fragments[this.fragments.length - 1].end;
1497    }
1498    return 0;
1499  }
1500  get age() {
1501    if (this.advancedDateTime) {
1502      return Math.max(Date.now() - this.advancedDateTime, 0) / 1000;
1503    }
1504    return 0;
1505  }
1506  get lastPartIndex() {
1507    var _this$partList2;
1508    if ((_this$partList2 = this.partList) != null && _this$partList2.length) {
1509      return this.partList[this.partList.length - 1].index;
1510    }
1511    return -1;
1512  }
1513  get lastPartSn() {
1514    var _this$partList3;
1515    if ((_this$partList3 = this.partList) != null && _this$partList3.length) {
1516      return this.partList[this.partList.length - 1].fragment.sn;
1517    }
1518    return this.endSN;
1519  }
1520}
1521
1522function base64Decode(base64encodedStr) {
1523  return Uint8Array.from(atob(base64encodedStr), c => c.charCodeAt(0));
1524}
1525
1526function getKeyIdBytes(str) {
1527  const keyIdbytes = strToUtf8array(str).subarray(0, 16);
1528  const paddedkeyIdbytes = new Uint8Array(16);
1529  paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length);
1530  return paddedkeyIdbytes;
1531}
1532function changeEndianness(keyId) {
1533  const swap = function swap(array, from, to) {
1534    const cur = array[from];
1535    array[from] = array[to];
1536    array[to] = cur;
1537  };
1538  swap(keyId, 0, 3);
1539  swap(keyId, 1, 2);
1540  swap(keyId, 4, 5);
1541  swap(keyId, 6, 7);
1542}
1543function convertDataUriToArrayBytes(uri) {
1544  // data:[<media type][;attribute=value][;base64],<data>
1545  const colonsplit = uri.split(':');
1546  let keydata = null;
1547  if (colonsplit[0] === 'data' && colonsplit.length === 2) {
1548    const semicolonsplit = colonsplit[1].split(';');
1549    const commasplit = semicolonsplit[semicolonsplit.length - 1].split(',');
1550    if (commasplit.length === 2) {
1551      const isbase64 = commasplit[0] === 'base64';
1552      const data = commasplit[1];
1553      if (isbase64) {
1554        semicolonsplit.splice(-1, 1); // remove from processing
1555        keydata = base64Decode(data);
1556      } else {
1557        keydata = getKeyIdBytes(data);
1558      }
1559    }
1560  }
1561  return keydata;
1562}
1563function strToUtf8array(str) {
1564  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
1565}
1566
1567/** returns `undefined` is `self` is missing, e.g. in node */
1568const optionalSelf = typeof self !== 'undefined' ? self : undefined;
1569
1570/**
1571 * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess
1572 */
1573var KeySystems = {
1574  CLEARKEY: "org.w3.clearkey",
1575  FAIRPLAY: "com.apple.fps",
1576  PLAYREADY: "com.microsoft.playready",
1577  WIDEVINE: "com.widevine.alpha"
1578};
1579
1580// Playlist #EXT-X-KEY KEYFORMAT values
1581var KeySystemFormats = {
1582  CLEARKEY: "org.w3.clearkey",
1583  FAIRPLAY: "com.apple.streamingkeydelivery",
1584  PLAYREADY: "com.microsoft.playready",
1585  WIDEVINE: "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
1586};
1587function keySystemFormatToKeySystemDomain(format) {
1588  switch (format) {
1589    case KeySystemFormats.FAIRPLAY:
1590      return KeySystems.FAIRPLAY;
1591    case KeySystemFormats.PLAYREADY:
1592      return KeySystems.PLAYREADY;
1593    case KeySystemFormats.WIDEVINE:
1594      return KeySystems.WIDEVINE;
vendor: 4,139 bytes, lines 1595-1707
1595    case KeySystemFormats.CLEARKEY:
1596      return KeySystems.CLEARKEY;
1597  }
1598}
1599
1600// System IDs for which we can extract a key ID from "encrypted" event PSSH
1601var KeySystemIds = {
1602  WIDEVINE: "edef8ba979d64acea3c827dcd51d21ed"
1603};
1604function keySystemIdToKeySystemDomain(systemId) {
1605  if (systemId === KeySystemIds.WIDEVINE) {
1606    return KeySystems.WIDEVINE;
1607    // } else if (systemId === KeySystemIds.PLAYREADY) {
1608    //   return KeySystems.PLAYREADY;
1609    // } else if (systemId === KeySystemIds.CENC || systemId === KeySystemIds.CLEARKEY) {
1610    //   return KeySystems.CLEARKEY;
1611  }
1612}
1613function keySystemDomainToKeySystemFormat(keySystem) {
1614  switch (keySystem) {
1615    case KeySystems.FAIRPLAY:
1616      return KeySystemFormats.FAIRPLAY;
1617    case KeySystems.PLAYREADY:
1618      return KeySystemFormats.PLAYREADY;
1619    case KeySystems.WIDEVINE:
1620      return KeySystemFormats.WIDEVINE;
1621    case KeySystems.CLEARKEY:
1622      return KeySystemFormats.CLEARKEY;
1623  }
1624}
1625function getKeySystemsForConfig(config) {
1626  const {
1627    drmSystems,
1628    widevineLicenseUrl
1629  } = config;
1630  const keySystemsToAttempt = drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter(keySystem => !!drmSystems[keySystem]) : [];
1631  if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) {
1632    keySystemsToAttempt.push(KeySystems.WIDEVINE);
1633  }
1634  return keySystemsToAttempt;
1635}
1636const requestMediaKeySystemAccess = function (_optionalSelf$navigat) {
1637  if (optionalSelf != null && (_optionalSelf$navigat = optionalSelf.navigator) != null && _optionalSelf$navigat.requestMediaKeySystemAccess) {
1638    return self.navigator.requestMediaKeySystemAccess.bind(self.navigator);
1639  } else {
1640    return null;
1641  }
1642}();
1643
1644/**
1645 * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaKeySystemConfiguration
1646 */
1647function getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, drmSystemOptions) {
1648  let initDataTypes;
1649  switch (keySystem) {
1650    case KeySystems.FAIRPLAY:
1651      initDataTypes = ['cenc', 'sinf'];
1652      break;
1653    case KeySystems.WIDEVINE:
1654    case KeySystems.PLAYREADY:
1655      initDataTypes = ['cenc'];
1656      break;
1657    case KeySystems.CLEARKEY:
1658      initDataTypes = ['cenc', 'keyids'];
1659      break;
1660    default:
1661      throw new Error(`Unknown key-system: ${keySystem}`);
1662  }
1663  return createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions);
1664}
1665function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions) {
1666  const baseConfig = {
1667    initDataTypes: initDataTypes,
1668    persistentState: drmSystemOptions.persistentState || 'optional',
1669    distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'optional',
1670    sessionTypes: drmSystemOptions.sessionTypes || [drmSystemOptions.sessionType || 'temporary'],
1671    audioCapabilities: audioCodecs.map(codec => ({
1672      contentType: `audio/mp4; codecs="${codec}"`,
1673      robustness: drmSystemOptions.audioRobustness || '',
1674      encryptionScheme: drmSystemOptions.audioEncryptionScheme || null
1675    })),
1676    videoCapabilities: videoCodecs.map(codec => ({
1677      contentType: `video/mp4; codecs="${codec}"`,
1678      robustness: drmSystemOptions.videoRobustness || '',
1679      encryptionScheme: drmSystemOptions.videoEncryptionScheme || null
1680    }))
1681  };
1682  return [baseConfig];
1683}
1684
1685function sliceUint8(array, start, end) {
1686  // @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
1687  // It always exists in the TypeScript definition so fails, but it fails at runtime on IE11.
1688  return Uint8Array.prototype.slice ? array.slice(start, end) : new Uint8Array(Array.prototype.slice.call(array, start, end));
1689}
1690
1691// breaking up those two types in order to clarify what is happening in the decoding path.
1692
1693/**
1694 * Returns true if an ID3 header can be found at offset in data
1695 * @param data - The data to search
1696 * @param offset - The offset at which to start searching
1697 */
1698const isHeader$2 = (data, offset) => {
1699  /*
1700   * http://id3.org/id3v2.3.0
1701   * [0]     = 'I'
1702   * [1]     = 'D'
1703   * [2]     = '3'
1704   * [3,4]   = {Version}
1705   * [5]     = {Flags}
1706   * [6-9]   = {ID3 Size}
1707   *
1708   * An ID3v2 tag can be detected with the following pattern:
1709   *  $49 44 33 yy yy xx zz zz zz zz
1710   * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80
1711   */
1712  if (offset + 10 <= data.length) {
1713    // look for 'ID3' identifier
1714    if (data[offset] === 0x49 && data[offset + 1] === 0x44 && data[offset + 2] === 0x33) {
1715      // check version is within range
1716      if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
1717        // check size is within range
1718        if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
1719          return true;
1720        }
1721      }
1722    }
1723  }
1724  return false;
1725};
1726
1727/**
1728 * Returns true if an ID3 footer can be found at offset in data
1729 * @param data - The data to search
1730 * @param offset - The offset at which to start searching
1731 */
1732const isFooter = (data, offset) => {
1733  /*
1734   * The footer is a copy of the header, but with a different identifier
1735   */
1736  if (offset + 10 <= data.length) {
1737    // look for '3DI' identifier
1738    if (data[offset] === 0x33 && data[offset + 1] === 0x44 && data[offset + 2] === 0x49) {
1739      // check version is within range
1740      if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
1741        // check size is within range
1742        if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
1743          return true;
1744        }
1745      }
1746    }
1747  }
1748  return false;
1749};
1750
1751/**
1752 * Returns any adjacent ID3 tags found in data starting at offset, as one block of data
1753 * @param data - The data to search in
1754 * @param offset - The offset at which to start searching
1755 * @returns the block of data containing any ID3 tags found
1756 * or *undefined* if no header is found at the starting offset
1757 */
1758const getID3Data = (data, offset) => {
1759  const front = offset;
1760  let length = 0;
1761  while (isHeader$2(data, offset)) {
1762    // ID3 header is 10 bytes
1763    length += 10;
1764    const size = readSize(data, offset + 6);
1765    length += size;
1766    if (isFooter(data, offset + 10)) {
1767      // ID3 footer is 10 bytes
1768      length += 10;
1769    }
1770    offset += length;
1771  }
1772  if (length > 0) {
1773    return data.subarray(front, front + length);
1774  }
1775  return undefined;
1776};
1777const readSize = (data, offset) => {
1778  let size = 0;
1779  size = (data[offset] & 0x7f) << 21;
1780  size |= (data[offset + 1] & 0x7f) << 14;
1781  size |= (data[offset + 2] & 0x7f) << 7;
1782  size |= data[offset + 3] & 0x7f;
1783  return size;
1784};
1785const canParse$2 = (data, offset) => {
1786  return isHeader$2(data, offset) && readSize(data, offset + 6) + 10 <= data.length - offset;
1787};
1788
1789/**
1790 * Searches for the Elementary Stream timestamp found in the ID3 data chunk
1791 * @param data - Block of data containing one or more ID3 tags
1792 */
1793const getTimeStamp = data => {
1794  const frames = getID3Frames(data);
1795  for (let i = 0; i < frames.length; i++) {
1796    const frame = frames[i];
1797    if (isTimeStampFrame(frame)) {
1798      return readTimeStamp(frame);
1799    }
1800  }
1801  return undefined;
1802};
1803
1804/**
1805 * Returns true if the ID3 frame is an Elementary Stream timestamp frame
1806 */
1807const isTimeStampFrame = frame => {
1808  return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp';
1809};
1810const getFrameData = data => {
1811  /*
1812  Frame ID       $xx xx xx xx (four characters)
1813  Size           $xx xx xx xx
1814  Flags          $xx xx
1815  */
1816  const type = String.fromCharCode(data[0], data[1], data[2], data[3]);
1817  const size = readSize(data, 4);
1818
1819  // skip frame id, size, and flags
1820  const offset = 10;
1821  return {
1822    type,
1823    size,
1824    data: data.subarray(offset, offset + size)
1825  };
1826};
1827
1828/**
1829 * Returns an array of ID3 frames found in all the ID3 tags in the id3Data
1830 * @param id3Data - The ID3 data containing one or more ID3 tags
1831 */
1832const getID3Frames = id3Data => {
1833  let offset = 0;
1834  const frames = [];
1835  while (isHeader$2(id3Data, offset)) {
1836    const size = readSize(id3Data, offset + 6);
1837    // skip past ID3 header
1838    offset += 10;
1839    const end = offset + size;
1840    // loop through frames in the ID3 tag
1841    while (offset + 8 < end) {
1842      const frameData = getFrameData(id3Data.subarray(offset));
1843      const frame = decodeFrame(frameData);
1844      if (frame) {
1845        frames.push(frame);
1846      }
1847
1848      // skip frame header and frame data
1849      offset += frameData.size + 10;
1850    }
1851    if (isFooter(id3Data, offset)) {
1852      offset += 10;
1853    }
1854  }
1855  return frames;
1856};
1857const decodeFrame = frame => {
1858  if (frame.type === 'PRIV') {
1859    return decodePrivFrame(frame);
1860  } else if (frame.type[0] === 'W') {
1861    return decodeURLFrame(frame);
1862  }
1863  return decodeTextFrame(frame);
1864};
1865const decodePrivFrame = frame => {
1866  /*
1867  Format: <text string>\0<binary data>
1868  */
1869  if (frame.size < 2) {
1870    return undefined;
1871  }
1872  const owner = utf8ArrayToStr(frame.data, true);
1873  const privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
1874  return {
1875    key: frame.type,
1876    info: owner,
1877    data: privateData.buffer
1878  };
1879};
1880const decodeTextFrame = frame => {
1881  if (frame.size < 2) {
1882    return undefined;
1883  }
1884  if (frame.type === 'TXXX') {
1885    /*
1886    Format:
1887    [0]   = {Text Encoding}
1888    [1-?] = {Description}\0{Value}
1889    */
1890    let index = 1;
1891    const description = utf8ArrayToStr(frame.data.subarray(index), true);
1892    index += description.length + 1;
1893    const value = utf8ArrayToStr(frame.data.subarray(index));
1894    return {
1895      key: frame.type,
1896      info: description,
1897      data: value
1898    };
1899  }
1900  /*
1901  Format:
1902  [0]   = {Text Encoding}
1903  [1-?] = {Value}
1904  */
1905  const text = utf8ArrayToStr(frame.data.subarray(1));
1906  return {
1907    key: frame.type,
1908    data: text
1909  };
1910};
1911const decodeURLFrame = frame => {
1912  if (frame.type === 'WXXX') {
1913    /*
1914    Format:
1915    [0]   = {Text Encoding}
1916    [1-?] = {Description}\0{URL}
1917    */
1918    if (frame.size < 2) {
1919      return undefined;
1920    }
1921    let index = 1;
1922    const description = utf8ArrayToStr(frame.data.subarray(index), true);
1923    index += description.length + 1;
1924    const value = utf8ArrayToStr(frame.data.subarray(index));
1925    return {
1926      key: frame.type,
1927      info: description,
1928      data: value
1929    };
1930  }
1931  /*
1932  Format:
1933  [0-?] = {URL}
1934  */
1935  const url = utf8ArrayToStr(frame.data);
1936  return {
1937    key: frame.type,
1938    data: url
1939  };
1940};
1941const readTimeStamp = timeStampFrame => {
1942  if (timeStampFrame.data.byteLength === 8) {
1943    const data = new Uint8Array(timeStampFrame.data);
1944    // timestamp is 33 bit expressed as a big-endian eight-octet number,
1945    // with the upper 31 bits set to zero.
1946    const pts33Bit = data[3] & 0x1;
1947    let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
1948    timestamp /= 45;
1949    if (pts33Bit) {
1950      timestamp += 47721858.84;
1951    } // 2^32 / 90
1952
1953    return Math.round(timestamp);
1954  }
1955  return undefined;
1956};
1957
1958// http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197
1959// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
1960/* utf.js - UTF-8 <=> UTF-16 convertion
1961 *
1962 * Copyright (C) 1999 Masanao Izumo <[email protected]>
1963 * Version: 1.0
1964 * LastModified: Dec 25 1999
1965 * This library is free.  You can redistribute it and/or modify it.
1966 */
1967const utf8ArrayToStr = (array, exitOnNull = false) => {
1968  const decoder = getTextDecoder();
1969  if (decoder) {
1970    const decoded = decoder.decode(array);
1971    if (exitOnNull) {
1972      // grab up to the first null
1973      const idx = decoded.indexOf('\0');
1974      return idx !== -1 ? decoded.substring(0, idx) : decoded;
1975    }
1976
1977    // remove any null characters
1978    return decoded.replace(/\0/g, '');
1979  }
1980  const len = array.length;
1981  let c;
1982  let char2;
1983  let char3;
1984  let out = '';
1985  let i = 0;
1986  while (i < len) {
1987    c = array[i++];
1988    if (c === 0x00 && exitOnNull) {
1989      return out;
1990    } else if (c === 0x00 || c === 0x03) {
1991      // If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it
1992      continue;
1993    }
1994    switch (c >> 4) {
1995      case 0:
1996      case 1:
1997      case 2:
1998      case 3:
1999      case 4:
2000      case 5:
2001      case 6:
2002      case 7:
2003        // 0xxxxxxx
2004        out += String.fromCharCode(c);
2005        break;
2006      case 12:
2007      case 13:
2008        // 110x xxxx   10xx xxxx
2009        char2 = array[i++];
2010        out += String.fromCharCode((c & 0x1f) << 6 | char2 & 0x3f);
2011        break;
2012      case 14:
2013        // 1110 xxxx  10xx xxxx  10xx xxxx
2014        char2 = array[i++];
2015        char3 = array[i++];
2016        out += String.fromCharCode((c & 0x0f) << 12 | (char2 & 0x3f) << 6 | (char3 & 0x3f) << 0);
2017        break;
2018    }
2019  }
2020  return out;
2021};
2022let decoder;
2023function getTextDecoder() {
2024  // On Play Station 4, TextDecoder is defined but partially implemented.
2025  // Manual decoding option is preferable
2026  if (navigator.userAgent.includes('PlayStation 4')) {
2027    return;
2028  }
2029  if (!decoder && typeof self.TextDecoder !== 'undefined') {
2030    decoder = new self.TextDecoder('utf-8');
2031  }
2032  return decoder;
2033}
2034
2035/**
2036 *  hex dump helper class
2037 */
2038
2039const Hex = {
2040  hexDump: function (array) {
2041    let str = '';
2042    for (let i = 0; i < array.length; i++) {
2043      let h = array[i].toString(16);
2044      if (h.length < 2) {
2045        h = '0' + h;
2046      }
2047      str += h;
2048    }
2049    return str;
2050  }
2051};
2052
2053const UINT32_MAX$1 = Math.pow(2, 32) - 1;
2054const push = [].push;
2055
2056// We are using fixed track IDs for driving the MP4 remuxer
2057// instead of following the TS PIDs.
2058// There is no reason not to do this and some browsers/SourceBuffer-demuxers
2059// may not like if there are TrackID "switches"
2060// See https://github.com/video-dev/hls.js/issues/1331
2061// Here we are mapping our internal track types to constant MP4 track IDs
2062// With MSE currently one can only have one track of each, and we are muxing
2063// whatever video/audio rendition in them.
2064const RemuxerTrackIdConfig = {
2065  video: 1,
2066  audio: 2,
2067  id3: 3,
2068  text: 4
2069};
2070function bin2str(data) {
2071  return String.fromCharCode.apply(null, data);
2072}
2073function readUint16(buffer, offset) {
2074  const val = buffer[offset] << 8 | buffer[offset + 1];
2075  return val < 0 ? 65536 + val : val;
2076}
2077function readUint32(buffer, offset) {
2078  const val = readSint32(buffer, offset);
2079  return val < 0 ? 4294967296 + val : val;
2080}
2081function readUint64(buffer, offset) {
2082  let result = readUint32(buffer, offset);
2083  result *= Math.pow(2, 32);
2084  result += readUint32(buffer, offset + 4);
2085  return result;
2086}
2087function readSint32(buffer, offset) {
2088  return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3];
2089}
2090function writeUint32(buffer, offset, value) {
2091  buffer[offset] = value >> 24;
2092  buffer[offset + 1] = value >> 16 & 0xff;
vendor: 4,988 bytes, lines 2093-2253
2093  buffer[offset + 2] = value >> 8 & 0xff;
2094  buffer[offset + 3] = value & 0xff;
2095}
2096
2097// Find "moof" box
2098function hasMoofData(data) {
2099  const end = data.byteLength;
2100  for (let i = 0; i < end;) {
2101    const size = readUint32(data, i);
2102    if (size > 8 && data[i + 4] === 0x6d && data[i + 5] === 0x6f && data[i + 6] === 0x6f && data[i + 7] === 0x66) {
2103      return true;
2104    }
2105    i = size > 1 ? i + size : end;
2106  }
2107  return false;
2108}
2109
2110// Find the data for a box specified by its path
2111function findBox(data, path) {
2112  const results = [];
2113  if (!path.length) {
2114    // short-circuit the search for empty paths
2115    return results;
2116  }
2117  const end = data.byteLength;
2118  for (let i = 0; i < end;) {
2119    const size = readUint32(data, i);
2120    const type = bin2str(data.subarray(i + 4, i + 8));
2121    const endbox = size > 1 ? i + size : end;
2122    if (type === path[0]) {
2123      if (path.length === 1) {
2124        // this is the end of the path and we've found the box we were
2125        // looking for
2126        results.push(data.subarray(i + 8, endbox));
2127      } else {
2128        // recursively search for the next box along the path
2129        const subresults = findBox(data.subarray(i + 8, endbox), path.slice(1));
2130        if (subresults.length) {
2131          push.apply(results, subresults);
2132        }
2133      }
2134    }
2135    i = endbox;
2136  }
2137
2138  // we've finished searching all of data
2139  return results;
2140}
2141function parseSegmentIndex(sidx) {
2142  const references = [];
2143  const version = sidx[0];
2144
2145  // set initial offset, we skip the reference ID (not needed)
2146  let index = 8;
2147  const timescale = readUint32(sidx, index);
2148  index += 4;
2149  let earliestPresentationTime = 0;
2150  let firstOffset = 0;
2151  if (version === 0) {
2152    earliestPresentationTime = readUint32(sidx, index);
2153    firstOffset = readUint32(sidx, index + 4);
2154    index += 8;
2155  } else {
2156    earliestPresentationTime = readUint64(sidx, index);
2157    firstOffset = readUint64(sidx, index + 8);
2158    index += 16;
2159  }
2160
2161  // skip reserved
2162  index += 2;
2163  let startByte = sidx.length + firstOffset;
2164  const referencesCount = readUint16(sidx, index);
2165  index += 2;
2166  for (let i = 0; i < referencesCount; i++) {
2167    let referenceIndex = index;
2168    const referenceInfo = readUint32(sidx, referenceIndex);
2169    referenceIndex += 4;
2170    const referenceSize = referenceInfo & 0x7fffffff;
2171    const referenceType = (referenceInfo & 0x80000000) >>> 31;
2172    if (referenceType === 1) {
2173      logger.warn('SIDX has hierarchical references (not supported)');
2174      return null;
2175    }
2176    const subsegmentDuration = readUint32(sidx, referenceIndex);
2177    referenceIndex += 4;
2178    references.push({
2179      referenceSize,
2180      subsegmentDuration,
2181      // unscaled
2182      info: {
2183        duration: subsegmentDuration / timescale,
2184        start: startByte,
2185        end: startByte + referenceSize - 1
2186      }
2187    });
2188    startByte += referenceSize;
2189
2190    // Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits
2191    // for |sapDelta|.
2192    referenceIndex += 4;
2193
2194    // skip to next ref
2195    index = referenceIndex;
2196  }
2197  return {
2198    earliestPresentationTime,
2199    timescale,
2200    version,
2201    referencesCount,
2202    references
2203  };
2204}
2205
2206/**
2207 * Parses an MP4 initialization segment and extracts stream type and
2208 * timescale values for any declared tracks. Timescale values indicate the
2209 * number of clock ticks per second to assume for time-based values
2210 * elsewhere in the MP4.
2211 *
2212 * To determine the start time of an MP4, you need two pieces of
2213 * information: the timescale unit and the earliest base media decode
2214 * time. Multiple timescales can be specified within an MP4 but the
2215 * base media decode time is always expressed in the timescale from
2216 * the media header box for the track:
2217 * ```
2218 * moov > trak > mdia > mdhd.timescale
2219 * moov > trak > mdia > hdlr
2220 * ```
2221 * @param initSegment the bytes of the init segment
2222 * @returns a hash of track type to timescale values or null if
2223 * the init segment is malformed.
2224 */
2225
2226function parseInitSegment(initSegment) {
2227  const result = [];
2228  const traks = findBox(initSegment, ['moov', 'trak']);
2229  for (let i = 0; i < traks.length; i++) {
2230    const trak = traks[i];
2231    const tkhd = findBox(trak, ['tkhd'])[0];
2232    if (tkhd) {
2233      let version = tkhd[0];
2234      const trackId = readUint32(tkhd, version === 0 ? 12 : 20);
2235      const mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
2236      if (mdhd) {
2237        version = mdhd[0];
2238        const timescale = readUint32(mdhd, version === 0 ? 12 : 20);
2239        const hdlr = findBox(trak, ['mdia', 'hdlr'])[0];
2240        if (hdlr) {
2241          const hdlrType = bin2str(hdlr.subarray(8, 12));
2242          const type = {
2243            soun: ElementaryStreamTypes.AUDIO,
2244            vide: ElementaryStreamTypes.VIDEO
2245          }[hdlrType];
2246          if (type) {
2247            // Parse codec details
2248            const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
2249            const stsdData = parseStsd(stsd);
2250            result[trackId] = {
2251              timescale,
2252              type
2253            };
2254            result[type] = _objectSpread2({
2255              timescale,
2256              id: trackId
2257            }, stsdData);
2258          }
2259        }
2260      }
2261    }
2262  }
2263  const trex = findBox(initSegment, ['moov', 'mvex', 'trex']);
2264  trex.forEach(trex => {
2265    const trackId = readUint32(trex, 4);
2266    const track = result[trackId];
2267    if (track) {
2268      track.default = {
2269        duration: readUint32(trex, 12),
2270        flags: readUint32(trex, 20)
2271      };
2272    }
2273  });
2274  return result;
2275}
2276function parseStsd(stsd) {
2277  const sampleEntries = stsd.subarray(8);
2278  const sampleEntriesEnd = sampleEntries.subarray(8 + 78);
2279  const fourCC = bin2str(sampleEntries.subarray(4, 8));
2280  let codec = fourCC;
2281  const encrypted = fourCC === 'enca' || fourCC === 'encv';
2282  if (encrypted) {
2283    const encBox = findBox(sampleEntries, [fourCC])[0];
2284    const encBoxChildren = encBox.subarray(fourCC === 'enca' ? 28 : 78);
2285    const sinfs = findBox(encBoxChildren, ['sinf']);
2286    sinfs.forEach(sinf => {
2287      const schm = findBox(sinf, ['schm'])[0];
2288      if (schm) {
2289        const scheme = bin2str(schm.subarray(4, 8));
2290        if (scheme === 'cbcs' || scheme === 'cenc') {
2291          const frma = findBox(sinf, ['frma'])[0];
2292          if (frma) {
2293            // for encrypted content codec fourCC will be in frma
2294            codec = bin2str(frma);
2295          }
2296        }
2297      }
2298    });
2299  }
2300  switch (codec) {
2301    case 'avc1':
2302    case 'avc2':
2303    case 'avc3':
2304    case 'avc4':
2305      {
2306        // extract profile + compatibility + level out of avcC box
2307        const avcCBox = findBox(sampleEntriesEnd, ['avcC'])[0];
2308        codec += '.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]);
2309        break;
2310      }
2311    case 'mp4a':
2312      {
2313        const codecBox = findBox(sampleEntries, [fourCC])[0];
2314        const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
2315        if (esdsBox && esdsBox.length > 12) {
2316          let i = 4;
2317          // ES Descriptor tag
2318          if (esdsBox[i++] !== 0x03) {
2319            break;
2320          }
2321          i = skipBERInteger(esdsBox, i);
2322          i += 2; // skip es_id;
2323          const flags = esdsBox[i++];
2324          if (flags & 0x80) {
2325            i += 2; // skip dependency es_id
2326          }
2327          if (flags & 0x40) {
2328            i += esdsBox[i++]; // skip URL
2329          }
2330          // Decoder config descriptor
2331          if (esdsBox[i++] !== 0x04) {
2332            break;
2333          }
2334          i = skipBERInteger(esdsBox, i);
2335          const objectType = esdsBox[i++];
2336          if (objectType === 0x40) {
2337            codec += '.' + toHex(objectType);
2338          } else {
2339            break;
2340          }
2341          i += 12;
2342          // Decoder specific info
2343          if (esdsBox[i++] !== 0x05) {
2344            break;
2345          }
2346          i = skipBERInteger(esdsBox, i);
2347          const firstByte = esdsBox[i++];
2348          let audioObjectType = (firstByte & 0xf8) >> 3;
2349          if (audioObjectType === 31) {
2350            audioObjectType += 1 + ((firstByte & 0x7) << 3) + ((esdsBox[i] & 0xe0) >> 5);
2351          }
2352          codec += '.' + audioObjectType;
2353        }
2354        break;
2355      }
2356    case 'hvc1':
2357    case 'hev1':
2358      {
2359        const hvcCBox = findBox(sampleEntriesEnd, ['hvcC'])[0];
2360        const profileByte = hvcCBox[1];
2361        const profileSpace = ['', 'A', 'B', 'C'][profileByte >> 6];
2362        const generalProfileIdc = profileByte & 0x1f;
2363        const profileCompat = readUint32(hvcCBox, 2);
2364        const tierFlag = (profileByte & 0x20) >> 5 ? 'H' : 'L';
2365        const levelIDC = hvcCBox[12];
2366        const constraintIndicator = hvcCBox.subarray(6, 12);
2367        codec += '.' + profileSpace + generalProfileIdc;
2368        codec += '.' + profileCompat.toString(16).toUpperCase();
2369        codec += '.' + tierFlag + levelIDC;
2370        let constraintString = '';
2371        for (let i = constraintIndicator.length; i--;) {
2372          const byte = constraintIndicator[i];
2373          if (byte || constraintString) {
2374            const encodedByte = byte.toString(16).toUpperCase();
2375            constraintString = '.' + encodedByte + constraintString;
2376          }
2377        }
2378        codec += constraintString;
2379        break;
2380      }
2381    case 'dvh1':
2382    case 'dvhe':
2383      {
2384        const dvcCBox = findBox(sampleEntriesEnd, ['dvcC'])[0];
2385        const profile = dvcCBox[2] >> 1 & 0x7f;
2386        const level = dvcCBox[2] << 5 & 0x20 | dvcCBox[3] >> 3 & 0x1f;
2387        codec += '.' + addLeadingZero(profile) + '.' + addLeadingZero(level);
2388        break;
2389      }
2390    case 'vp09':
2391      {
2392        const vpcCBox = findBox(sampleEntriesEnd, ['vpcC'])[0];
2393        const profile = vpcCBox[4];
2394        const level = vpcCBox[5];
2395        const bitDepth = vpcCBox[6] >> 4 & 0x0f;
2396        codec += '.' + addLeadingZero(profile) + '.' + addLeadingZero(level) + '.' + addLeadingZero(bitDepth);
2397        break;
2398      }
2399    case 'av01':
2400      {
2401        const av1CBox = findBox(sampleEntriesEnd, ['av1C'])[0];
2402        const profile = av1CBox[1] >>> 5;
2403        const level = av1CBox[1] & 0x1f;
2404        const tierFlag = av1CBox[2] >>> 7 ? 'H' : 'M';
2405        const highBitDepth = (av1CBox[2] & 0x40) >> 6;
2406        const twelveBit = (av1CBox[2] & 0x20) >> 5;
2407        const bitDepth = profile === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8;
2408        const monochrome = (av1CBox[2] & 0x10) >> 4;
2409        const chromaSubsamplingX = (av1CBox[2] & 0x08) >> 3;
2410        const chromaSubsamplingY = (av1CBox[2] & 0x04) >> 2;
2411        const chromaSamplePosition = av1CBox[2] & 0x03;
2412        // TODO: parse color_description_present_flag
2413        // default it to BT.709/limited range for now
2414        // more info https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax
2415        const colorPrimaries = 1;
2416        const transferCharacteristics = 1;
2417        const matrixCoefficients = 1;
2418        const videoFullRangeFlag = 0;
2419        codec += '.' + profile + '.' + addLeadingZero(level) + tierFlag + '.' + addLeadingZero(bitDepth) + '.' + monochrome + '.' + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition + '.' + addLeadingZero(colorPrimaries) + '.' + addLeadingZero(transferCharacteristics) + '.' + addLeadingZero(matrixCoefficients) + '.' + videoFullRangeFlag;
2420        break;
2421      }
2422  }
2423  return {
2424    codec,
2425    encrypted
2426  };
2427}
2428function skipBERInteger(bytes, i) {
2429  const limit = i + 5;
2430  while (bytes[i++] & 0x80 && i < limit) {}
2431  return i;
2432}
2433function toHex(x) {
2434  return ('0' + x.toString(16).toUpperCase()).slice(-2);
2435}
2436function addLeadingZero(num) {
2437  return (num < 10 ? '0' : '') + num;
2438}
2439function patchEncyptionData(initSegment, decryptdata) {
2440  if (!initSegment || !decryptdata) {
2441    return initSegment;
2442  }
2443  const keyId = decryptdata.keyId;
2444  if (keyId && decryptdata.isCommonEncryption) {
2445    const traks = findBox(initSegment, ['moov', 'trak']);
2446    traks.forEach(trak => {
2447      const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
2448
2449      // skip the sample entry count
2450      const sampleEntries = stsd.subarray(8);
2451      let encBoxes = findBox(sampleEntries, ['enca']);
2452      const isAudio = encBoxes.length > 0;
2453      if (!isAudio) {
2454        encBoxes = findBox(sampleEntries, ['encv']);
2455      }
2456      encBoxes.forEach(enc => {
2457        const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78);
2458        const sinfBoxes = findBox(encBoxChildren, ['sinf']);
2459        sinfBoxes.forEach(sinf => {
2460          const tenc = parseSinf(sinf);
2461          if (tenc) {
2462            // Look for default key id (keyID offset is always 8 within the tenc box):
2463            const tencKeyId = tenc.subarray(8, 24);
2464            if (!tencKeyId.some(b => b !== 0)) {
2465              logger.log(`[eme] Patching keyId in 'enc${isAudio ? 'a' : 'v'}>sinf>>tenc' box: ${Hex.hexDump(tencKeyId)} -> ${Hex.hexDump(keyId)}`);
2466              tenc.set(keyId, 8);
2467            }
2468          }
2469        });
2470      });
2471    });
2472  }
2473  return initSegment;
2474}
2475function parseSinf(sinf) {
2476  const schm = findBox(sinf, ['schm'])[0];
2477  if (schm) {
2478    const scheme = bin2str(schm.subarray(4, 8));
2479    if (scheme === 'cbcs' || scheme === 'cenc') {
2480      return findBox(sinf, ['schi', 'tenc'])[0];
2481    }
2482  }
2483  logger.error(`[eme] missing 'schm' box`);
2484  return null;
2485}
2486
2487/**
2488 * Determine the base media decode start time, in seconds, for an MP4
2489 * fragment. If multiple fragments are specified, the earliest time is
2490 * returned.
2491 *
2492 * The base media decode time can be parsed from track fragment
2493 * metadata:
2494 * ```
2495 * moof > traf > tfdt.baseMediaDecodeTime
2496 * ```
2497 * It requires the timescale value from the mdhd to interpret.
2498 *
2499 * @param initData - a hash of track type to timescale values
2500 * @param fmp4 - the bytes of the mp4 fragment
2501 * @returns the earliest base media decode start time for the
2502 * fragment, in seconds
2503 */
2504function getStartDTS(initData, fmp4) {
2505  // we need info from two children of each track fragment box
2506  return findBox(fmp4, ['moof', 'traf']).reduce((result, traf) => {
2507    const tfdt = findBox(traf, ['tfdt'])[0];
2508    const version = tfdt[0];
2509    const start = findBox(traf, ['tfhd']).reduce((result, tfhd) => {
2510      // get the track id from the tfhd
2511      const id = readUint32(tfhd, 4);
2512      const track = initData[id];
2513      if (track) {
2514        let baseTime = readUint32(tfdt, 4);
2515        if (version === 1) {
2516          // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild.
2517          // This prevents large values from being used for initPTS, which can cause playlist sync issues.
2518          // https://github.com/video-dev/hls.js/issues/5303
2519          if (baseTime === UINT32_MAX$1) {
2520            logger.warn(`[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`);
2521            return result;
2522          }
2523          baseTime *= UINT32_MAX$1 + 1;
2524          baseTime += readUint32(tfdt, 8);
2525        }
2526        // assume a 90kHz clock if no timescale was specified
2527        const scale = track.timescale || 90e3;
2528        // convert base time to seconds
2529        const startTime = baseTime / scale;
2530        if (isFiniteNumber(startTime) && (result === null || startTime < result)) {
2531          return startTime;
2532        }
2533      }
2534      return result;
2535    }, null);
2536    if (start !== null && isFiniteNumber(start) && (result === null || start < result)) {
2537      return start;
2538    }
2539    return result;
2540  }, null);
2541}
2542
2543/*
2544  For Reference:
2545  aligned(8) class TrackFragmentHeaderBox
2546           extends FullBox(‘tfhd’, 0, tf_flags){
2547     unsigned int(32)  track_ID;
2548     // all the following are optional fields
2549     unsigned int(64)  base_data_offset;
2550     unsigned int(32)  sample_description_index;
2551     unsigned int(32)  default_sample_duration;
2552     unsigned int(32)  default_sample_size;
2553     unsigned int(32)  default_sample_flags
2554  }
2555 */
2556function getDuration(data, initData) {
2557  let rawDuration = 0;
2558  let videoDuration = 0;
2559  let audioDuration = 0;
2560  const trafs = findBox(data, ['moof', 'traf']);
2561  for (let i = 0; i < trafs.length; i++) {
2562    const traf = trafs[i];
2563    // There is only one tfhd & trun per traf
2564    // This is true for CMAF style content, and we should perhaps check the ftyp
2565    // and only look for a single trun then, but for ISOBMFF we should check
2566    // for multiple track runs.
2567    const tfhd = findBox(traf, ['tfhd'])[0];
2568    // get the track id from the tfhd
2569    const id = readUint32(tfhd, 4);
2570    const track = initData[id];
2571    if (!track) {
2572      continue;
2573    }
2574    const trackDefault = track.default;
2575    const tfhdFlags = readUint32(tfhd, 0) | (trackDefault == null ? void 0 : trackDefault.flags);
2576    let sampleDuration = trackDefault == null ? void 0 : trackDefault.duration;
2577    if (tfhdFlags & 0x000008) {
2578      // 0x000008 indicates the presence of the default_sample_duration field
2579      if (tfhdFlags & 0x000002) {
2580        // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration
2581        // If present, the default_sample_duration exists at byte offset 12
2582        sampleDuration = readUint32(tfhd, 12);
2583      } else {
2584        // Otherwise, the duration is at byte offset 8
2585        sampleDuration = readUint32(tfhd, 8);
2586      }
2587    }
2588    // assume a 90kHz clock if no timescale was specified
2589    const timescale = track.timescale || 90e3;
2590    const truns = findBox(traf, ['trun']);
2591    for (let j = 0; j < truns.length; j++) {
2592      rawDuration = computeRawDurationFromSamples(truns[j]);
2593      if (!rawDuration && sampleDuration) {
2594        const sampleCount = readUint32(truns[j], 4);
2595        rawDuration = sampleDuration * sampleCount;
2596      }
2597      if (track.type === ElementaryStreamTypes.VIDEO) {
2598        videoDuration += rawDuration / timescale;
2599      } else if (track.type === ElementaryStreamTypes.AUDIO) {
2600        audioDuration += rawDuration / timescale;
2601      }
2602    }
2603  }
2604  if (videoDuration === 0 && audioDuration === 0) {
2605    // If duration samples are not available in the traf use sidx subsegment_duration
2606    let sidxMinStart = Infinity;
2607    let sidxMaxEnd = 0;
2608    let sidxDuration = 0;
2609    const sidxs = findBox(data, ['sidx']);
2610    for (let i = 0; i < sidxs.length; i++) {
2611      const sidx = parseSegmentIndex(sidxs[i]);
2612      if (sidx != null && sidx.references) {
2613        sidxMinStart = Math.min(sidxMinStart, sidx.earliestPresentationTime / sidx.timescale);
2614        const subSegmentDuration = sidx.references.reduce((dur, ref) => dur + ref.info.duration || 0, 0);
2615        sidxMaxEnd = Math.max(sidxMaxEnd, subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale);
2616        sidxDuration = sidxMaxEnd - sidxMinStart;
2617      }
2618    }
2619    if (sidxDuration && isFiniteNumber(sidxDuration)) {
2620      return sidxDuration;
2621    }
2622  }
2623  if (videoDuration) {
2624    return videoDuration;
2625  }
2626  return audioDuration;
2627}
2628
2629/*
2630  For Reference:
2631  aligned(8) class TrackRunBox
2632           extends FullBox(‘trun’, version, tr_flags) {
2633     unsigned int(32)  sample_count;
2634     // the following are optional fields
2635     signed int(32) data_offset;
2636     unsigned int(32)  first_sample_flags;
2637     // all fields in the following array are optional
2638     {
2639        unsigned int(32)  sample_duration;
2640        unsigned int(32)  sample_size;
2641        unsigned int(32)  sample_flags
2642        if (version == 0)
2643           { unsigned int(32)
2644        else
2645           { signed int(32)
2646     }[ sample_count ]
2647  }
2648 */
2649function computeRawDurationFromSamples(trun) {
2650  const flags = readUint32(trun, 0);
2651  // Flags are at offset 0, non-optional sample_count is at offset 4. Therefore we start 8 bytes in.
2652  // Each field is an int32, which is 4 bytes
2653  let offset = 8;
2654  // data-offset-present flag
2655  if (flags & 0x000001) {
2656    offset += 4;
2657  }
2658  // first-sample-flags-present flag
2659  if (flags & 0x000004) {
2660    offset += 4;
2661  }
2662  let duration = 0;
2663  const sampleCount = readUint32(trun, 4);
2664  for (let i = 0; i < sampleCount; i++) {
2665    // sample-duration-present flag
2666    if (flags & 0x000100) {
2667      const sampleDuration = readUint32(trun, offset);
2668      duration += sampleDuration;
2669      offset += 4;
2670    }
2671    // sample-size-present flag
2672    if (flags & 0x000200) {
2673      offset += 4;
2674    }
2675    // sample-flags-present flag
2676    if (flags & 0x000400) {
2677      offset += 4;
2678    }
2679    // sample-composition-time-offsets-present flag
2680    if (flags & 0x000800) {
2681      offset += 4;
2682    }
2683  }
2684  return duration;
2685}
2686function offsetStartDTS(initData, fmp4, timeOffset) {
2687  findBox(fmp4, ['moof', 'traf']).forEach(traf => {
2688    findBox(traf, ['tfhd']).forEach(tfhd => {
2689      // get the track id from the tfhd
2690      const id = readUint32(tfhd, 4);
2691      const track = initData[id];
2692      if (!track) {
2693        return;
2694      }
2695      // assume a 90kHz clock if no timescale was specified
2696      const timescale = track.timescale || 90e3;
2697      // get the base media decode time from the tfdt
2698      findBox(traf, ['tfdt']).forEach(tfdt => {
2699        const version = tfdt[0];
2700        const offset = timeOffset * timescale;
2701        if (offset) {
2702          let baseMediaDecodeTime = readUint32(tfdt, 4);
2703          if (version === 0) {
2704            baseMediaDecodeTime -= offset;
2705            baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0);
2706            writeUint32(tfdt, 4, baseMediaDecodeTime);
2707          } else {
2708            baseMediaDecodeTime *= Math.pow(2, 32);
2709            baseMediaDecodeTime += readUint32(tfdt, 8);
2710            baseMediaDecodeTime -= offset;
2711            baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0);
2712            const upper = Math.floor(baseMediaDecodeTime / (UINT32_MAX$1 + 1));
2713            const lower = Math.floor(baseMediaDecodeTime % (UINT32_MAX$1 + 1));
2714            writeUint32(tfdt, 4, upper);
2715            writeUint32(tfdt, 8, lower);
2716          }
2717        }
2718      });
2719    });
2720  });
2721}
2722
2723// TODO: Check if the last moof+mdat pair is part of the valid range
2724function segmentValidRange(data) {
2725  const segmentedRange = {
2726    valid: null,
2727    remainder: null
2728  };
2729  const moofs = findBox(data, ['moof']);
2730  if (moofs.length < 2) {
2731    segmentedRange.remainder = data;
2732    return segmentedRange;
2733  }
2734  const last = moofs[moofs.length - 1];
2735  // Offset by 8 bytes; findBox offsets the start by as much
2736  segmentedRange.valid = sliceUint8(data, 0, last.byteOffset - 8);
2737  segmentedRange.remainder = sliceUint8(data, last.byteOffset - 8);
2738  return segmentedRange;
2739}
2740function appendUint8Array(data1, data2) {
2741  const temp = new Uint8Array(data1.length + data2.length);
2742  temp.set(data1);
2743  temp.set(data2, data1.length);
2744  return temp;
2745}
2746function parseSamples(timeOffset, track) {
2747  const seiSamples = [];
2748  const videoData = track.samples;
2749  const timescale = track.timescale;
2750  const trackId = track.id;
2751  let isHEVCFlavor = false;
2752  const moofs = findBox(videoData, ['moof']);
2753  moofs.map(moof => {
2754    const moofOffset = moof.byteOffset - 8;
2755    const trafs = findBox(moof, ['traf']);
2756    trafs.map(traf => {
2757      // get the base media decode time from the tfdt
2758      const baseTime = findBox(traf, ['tfdt']).map(tfdt => {
2759        const version = tfdt[0];
2760        let result = readUint32(tfdt, 4);
2761        if (version === 1) {
2762          result *= Math.pow(2, 32);
2763          result += readUint32(tfdt, 8);
2764        }
2765        return result / timescale;
2766      })[0];
2767      if (baseTime !== undefined) {
2768        timeOffset = baseTime;
2769      }
2770      return findBox(traf, ['tfhd']).map(tfhd => {
2771        const id = readUint32(tfhd, 4);
2772        const tfhdFlags = readUint32(tfhd, 0) & 0xffffff;
2773        const baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0;
2774        const sampleDescriptionIndexPresent = (tfhdFlags & 0x000002) !== 0;
2775        const defaultSampleDurationPresent = (tfhdFlags & 0x000008) !== 0;
2776        let defaultSampleDuration = 0;
2777        const defaultSampleSizePresent = (tfhdFlags & 0x000010) !== 0;
2778        let defaultSampleSize = 0;
2779        const defaultSampleFlagsPresent = (tfhdFlags & 0x000020) !== 0;
2780        let tfhdOffset = 8;
2781        if (id === trackId) {
2782          if (baseDataOffsetPresent) {
2783            tfhdOffset += 8;
2784          }
2785          if (sampleDescriptionIndexPresent) {
2786            tfhdOffset += 4;
2787          }
2788          if (defaultSampleDurationPresent) {
2789            defaultSampleDuration = readUint32(tfhd, tfhdOffset);
2790            tfhdOffset += 4;
2791          }
2792          if (defaultSampleSizePresent) {
2793            defaultSampleSize = readUint32(tfhd, tfhdOffset);
2794            tfhdOffset += 4;
2795          }
2796          if (defaultSampleFlagsPresent) {
2797            tfhdOffset += 4;
2798          }
2799          if (track.type === 'video') {
2800            isHEVCFlavor = isHEVC(track.codec);
2801          }
2802          findBox(traf, ['trun']).map(trun => {
2803            const version = trun[0];
2804            const flags = readUint32(trun, 0) & 0xffffff;
2805            const dataOffsetPresent = (flags & 0x000001) !== 0;
2806            let dataOffset = 0;
2807            const firstSampleFlagsPresent = (flags & 0x000004) !== 0;
2808            const sampleDurationPresent = (flags & 0x000100) !== 0;
2809            let sampleDuration = 0;
2810            const sampleSizePresent = (flags & 0x000200) !== 0;
2811            let sampleSize = 0;
2812            const sampleFlagsPresent = (flags & 0x000400) !== 0;
2813            const sampleCompositionOffsetsPresent = (flags & 0x000800) !== 0;
2814            let compositionOffset = 0;
2815            const sampleCount = readUint32(trun, 4);
2816            let trunOffset = 8; // past version, flags, and sample count
2817
2818            if (dataOffsetPresent) {
2819              dataOffset = readUint32(trun, trunOffset);
2820              trunOffset += 4;
2821            }
2822            if (firstSampleFlagsPresent) {
2823              trunOffset += 4;
2824            }
2825            let sampleOffset = dataOffset + moofOffset;
2826            for (let ix = 0;
vendor: 8,844 bytes, lines 2826-3110
2826 ix < sampleCount; ix++) {
2827              if (sampleDurationPresent) {
2828                sampleDuration = readUint32(trun, trunOffset);
2829                trunOffset += 4;
2830              } else {
2831                sampleDuration = defaultSampleDuration;
2832              }
2833              if (sampleSizePresent) {
2834                sampleSize = readUint32(trun, trunOffset);
2835                trunOffset += 4;
2836              } else {
2837                sampleSize = defaultSampleSize;
2838              }
2839              if (sampleFlagsPresent) {
2840                trunOffset += 4;
2841              }
2842              if (sampleCompositionOffsetsPresent) {
2843                if (version === 0) {
2844                  compositionOffset = readUint32(trun, trunOffset);
2845                } else {
2846                  compositionOffset = readSint32(trun, trunOffset);
2847                }
2848                trunOffset += 4;
2849              }
2850              if (track.type === ElementaryStreamTypes.VIDEO) {
2851                let naluTotalSize = 0;
2852                while (naluTotalSize < sampleSize) {
2853                  const naluSize = readUint32(videoData, sampleOffset);
2854                  sampleOffset += 4;
2855                  if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) {
2856                    const data = videoData.subarray(sampleOffset, sampleOffset + naluSize);
2857                    parseSEIMessageFromNALu(data, isHEVCFlavor ? 2 : 1, timeOffset + compositionOffset / timescale, seiSamples);
2858                  }
2859                  sampleOffset += naluSize;
2860                  naluTotalSize += naluSize + 4;
2861                }
2862              }
2863              timeOffset += sampleDuration / timescale;
2864            }
2865          });
2866        }
2867      });
2868    });
2869  });
2870  return seiSamples;
2871}
2872function isHEVC(codec) {
2873  if (!codec) {
2874    return false;
2875  }
2876  const delimit = codec.indexOf('.');
2877  const baseCodec = delimit < 0 ? codec : codec.substring(0, delimit);
2878  return baseCodec === 'hvc1' || baseCodec === 'hev1' ||
2879  // Dolby Vision
2880  baseCodec === 'dvh1' || baseCodec === 'dvhe';
2881}
2882function isSEIMessage(isHEVCFlavor, naluHeader) {
2883  if (isHEVCFlavor) {
2884    const naluType = naluHeader >> 1 & 0x3f;
2885    return naluType === 39 || naluType === 40;
2886  } else {
2887    const naluType = naluHeader & 0x1f;
2888    return naluType === 6;
2889  }
2890}
2891function parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples) {
2892  const data = discardEPB(unescapedData);
2893  let seiPtr = 0;
2894  // skip nal header
2895  seiPtr += headerSize;
2896  let payloadType = 0;
2897  let payloadSize = 0;
2898  let b = 0;
2899  while (seiPtr < data.length) {
2900    payloadType = 0;
2901    do {
2902      if (seiPtr >= data.length) {
2903        break;
2904      }
2905      b = data[seiPtr++];
2906      payloadType += b;
2907    } while (b === 0xff);
2908
2909    // Parse payload size.
2910    payloadSize = 0;
2911    do {
2912      if (seiPtr >= data.length) {
2913        break;
2914      }
2915      b = data[seiPtr++];
2916      payloadSize += b;
2917    } while (b === 0xff);
2918    const leftOver = data.length - seiPtr;
2919    // Create a variable to process the payload
2920    let payPtr = seiPtr;
2921
2922    // Increment the seiPtr to the end of the payload
2923    if (payloadSize < leftOver) {
2924      seiPtr += payloadSize;
2925    } else if (payloadSize > leftOver) {
2926      // Some type of corruption has happened?
2927      logger.error(`Malformed SEI payload. ${payloadSize} is too small, only ${leftOver} bytes left to parse.`);
2928      // We might be able to parse some data, but let's be safe and ignore it.
2929      break;
2930    }
2931    if (payloadType === 4) {
2932      const countryCode = data[payPtr++];
2933      if (countryCode === 181) {
2934        const providerCode = readUint16(data, payPtr);
2935        payPtr += 2;
2936        if (providerCode === 49) {
2937          const userStructure = readUint32(data, payPtr);
2938          payPtr += 4;
2939          if (userStructure === 0x47413934) {
2940            const userDataType = data[payPtr++];
2941
2942            // Raw CEA-608 bytes wrapped in CEA-708 packet
2943            if (userDataType === 3) {
2944              const firstByte = data[payPtr++];
2945              const totalCCs = 0x1f & firstByte;
2946              const enabled = 0x40 & firstByte;
2947              const totalBytes = enabled ? 2 + totalCCs * 3 : 0;
2948              const byteArray = new Uint8Array(totalBytes);
2949              if (enabled) {
2950                byteArray[0] = firstByte;
2951                for (let i = 1; i < totalBytes; i++) {
2952                  byteArray[i] = data[payPtr++];
2953                }
2954              }
2955              samples.push({
2956                type: userDataType,
2957                payloadType,
2958                pts,
2959                bytes: byteArray
2960              });
2961            }
2962          }
2963        }
2964      }
2965    } else if (payloadType === 5) {
2966      if (payloadSize > 16) {
2967        const uuidStrArray = [];
2968        for (let i = 0; i < 16; i++) {
2969          const _b = data[payPtr++].toString(16);
2970          uuidStrArray.push(_b.length == 1 ? '0' + _b : _b);
2971          if (i === 3 || i === 5 || i === 7 || i === 9) {
2972            uuidStrArray.push('-');
2973          }
2974        }
2975        const length = payloadSize - 16;
2976        const userDataBytes = new Uint8Array(length);
2977        for (let i = 0; i < length; i++) {
2978          userDataBytes[i] = data[payPtr++];
2979        }
2980        samples.push({
2981          payloadType,
2982          pts,
2983          uuid: uuidStrArray.join(''),
2984          userData: utf8ArrayToStr(userDataBytes),
2985          userDataBytes
2986        });
2987      }
2988    }
2989  }
2990}
2991
2992/**
2993 * remove Emulation Prevention bytes from a RBSP
2994 */
2995function discardEPB(data) {
2996  const length = data.byteLength;
2997  const EPBPositions = [];
2998  let i = 1;
2999
3000  // Find all `Emulation Prevention Bytes`
3001  while (i < length - 2) {
3002    if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
3003      EPBPositions.push(i + 2);
3004      i += 2;
3005    } else {
3006      i++;
3007    }
3008  }
3009
3010  // If no Emulation Prevention Bytes were found just return the original
3011  // array
3012  if (EPBPositions.length === 0) {
3013    return data;
3014  }
3015
3016  // Create a new array to hold the NAL unit data
3017  const newLength = length - EPBPositions.length;
3018  const newData = new Uint8Array(newLength);
3019  let sourceIndex = 0;
3020  for (i = 0; i < newLength; sourceIndex++, i++) {
3021    if (sourceIndex === EPBPositions[0]) {
3022      // Skip this byte
3023      sourceIndex++;
3024      // Remove this position index
3025      EPBPositions.shift();
3026    }
3027    newData[i] = data[sourceIndex];
3028  }
3029  return newData;
3030}
3031function parseEmsg(data) {
3032  const version = data[0];
3033  let schemeIdUri = '';
3034  let value = '';
3035  let timeScale = 0;
3036  let presentationTimeDelta = 0;
3037  let presentationTime = 0;
3038  let eventDuration = 0;
3039  let id = 0;
3040  let offset = 0;
3041  if (version === 0) {
3042    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
3043      schemeIdUri += bin2str(data.subarray(offset, offset + 1));
3044      offset += 1;
3045    }
3046    schemeIdUri += bin2str(data.subarray(offset, offset + 1));
3047    offset += 1;
3048    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
3049      value += bin2str(data.subarray(offset, offset + 1));
3050      offset += 1;
3051    }
3052    value += bin2str(data.subarray(offset, offset + 1));
3053    offset += 1;
3054    timeScale = readUint32(data, 12);
3055    presentationTimeDelta = readUint32(data, 16);
3056    eventDuration = readUint32(data, 20);
3057    id = readUint32(data, 24);
3058    offset = 28;
3059  } else if (version === 1) {
3060    offset += 4;
3061    timeScale = readUint32(data, offset);
3062    offset += 4;
3063    const leftPresentationTime = readUint32(data, offset);
3064    offset += 4;
3065    const rightPresentationTime = readUint32(data, offset);
3066    offset += 4;
3067    presentationTime = 2 ** 32 * leftPresentationTime + rightPresentationTime;
3068    if (!isSafeInteger(presentationTime)) {
3069      presentationTime = Number.MAX_SAFE_INTEGER;
3070      logger.warn('Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box');
3071    }
3072    eventDuration = readUint32(data, offset);
3073    offset += 4;
3074    id = readUint32(data, offset);
3075    offset += 4;
3076    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
3077      schemeIdUri += bin2str(data.subarray(offset, offset + 1));
3078      offset += 1;
3079    }
3080    schemeIdUri += bin2str(data.subarray(offset, offset + 1));
3081    offset += 1;
3082    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
3083      value += bin2str(data.subarray(offset, offset + 1));
3084      offset += 1;
3085    }
3086    value += bin2str(data.subarray(offset, offset + 1));
3087    offset += 1;
3088  }
3089  const payload = data.subarray(offset, data.byteLength);
3090  return {
3091    schemeIdUri,
3092    value,
3093    timeScale,
3094    presentationTime,
3095    presentationTimeDelta,
3096    eventDuration,
3097    id,
3098    payload
3099  };
3100}
3101function mp4Box(type, ...payload) {
3102  const len = payload.length;
3103  let size = 8;
3104  let i = len;
3105  while (i--) {
3106    size += payload[i].byteLength;
3107  }
3108  const result = new Uint8Array(size);
3109  result[0] = size >> 24 & 0xff;
3110  result[1] = size >> 16 & 0xff;
3111  result[2] = size >> 8 & 0xff;
3112  result[3] = size & 0xff;
3113  result.set(type, 4);
3114  for (i = 0, size = 8; i < len; i++) {
3115    result.set(payload[i], size);
3116    size += payload[i].byteLength;
3117  }
3118  return result;
3119}
3120function mp4pssh(systemId, keyids, data) {
3121  if (systemId.byteLength !== 16) {
3122    throw new RangeError('Invalid system id');
3123  }
3124  let version;
3125  let kids;
3126  {
3127    version = 0;
3128    kids = new Uint8Array();
3129  }
3130  let kidCount;
3131  if (version > 0) {
3132    kidCount = new Uint8Array(4);
3133    if (keyids.length > 0) {
3134      new DataView(kidCount.buffer).setUint32(0, keyids.length, false);
3135    }
3136  } else {
3137    kidCount = new Uint8Array();
3138  }
3139  const dataSize = new Uint8Array(4);
3140  if (data && data.byteLength > 0) {
3141    new DataView(dataSize.buffer).setUint32(0, data.byteLength, false);
3142  }
3143  return mp4Box([112, 115, 115, 104], new Uint8Array([version, 0x00, 0x00, 0x00 // Flags
3144  ]), systemId,
3145  // 16 bytes
3146  kidCount, kids, dataSize, data || new Uint8Array());
3147}
3148function parsePssh(initData) {
3149  if (!(initData instanceof ArrayBuffer) || initData.byteLength < 32) {
3150    return null;
3151  }
3152  const result = {
3153    version: 0,
3154    systemId: '',
3155    kids: null,
3156    data: null
3157  };
3158  const view = new DataView(initData);
3159  const boxSize = view.getUint32(0);
3160  if (initData.byteLength !== boxSize && boxSize > 44) {
3161    return null;
3162  }
3163  const type = view.getUint32(4);
3164  if (type !== 0x70737368) {
3165    return null;
3166  }
3167  result.version = view.getUint32(8) >>> 24;
3168  if (result.version > 1) {
3169    return null;
3170  }
3171  result.systemId = Hex.hexDump(new Uint8Array(initData, 12, 16));
3172  const dataSizeOrKidCount = view.getUint32(28);
3173  if (result.version === 0) {
3174    if (boxSize - 32 < dataSizeOrKidCount) {
3175      return null;
3176    }
3177    result.data = new Uint8Array(initData, 32, dataSizeOrKidCount);
3178  } else if (result.version === 1) {
3179    result.kids = [];
3180    for (let i = 0; i < dataSizeOrKidCount; i++) {
3181      result.kids.push(new Uint8Array(initData, 32 + i * 16, 16));
3182    }
3183  }
3184  return result;
3185}
3186
3187let keyUriToKeyIdMap = {};
3188class LevelKey {
3189  static clearKeyUriToKeyIdMap() {
3190    keyUriToKeyIdMap = {};
3191  }
3192  constructor(method, uri, format, formatversions = [1], iv = null) {
3193    this.uri = void 0;
3194    this.method = void 0;
3195    this.keyFormat = void 0;
3196    this.keyFormatVersions = void 0;
3197    this.encrypted = void 0;
3198    this.isCommonEncryption = void 0;
3199    this.iv = null;
3200    this.key = null;
3201    this.keyId = null;
3202    this.pssh = null;
3203    this.method = method;
3204    this.uri = uri;
3205    this.keyFormat = format;
3206    this.keyFormatVersions = formatversions;
3207    this.iv = iv;
3208    this.encrypted = method ? method !== 'NONE' : false;
3209    this.isCommonEncryption = this.encrypted && method !== 'AES-128';
3210  }
3211  isSupported() {
3212    // If it's Segment encryption or No encryption, just select that key system
3213    if (this.method) {
3214      if (this.method === 'AES-128' || this.method === 'NONE') {
3215        return true;
3216      }
3217      if (this.keyFormat === 'identity') {
3218        // Maintain support for clear SAMPLE-AES with MPEG-3 TS
3219        return this.method === 'SAMPLE-AES';
3220      } else {
3221        switch (this.keyFormat) {
3222          case KeySystemFormats.FAIRPLAY:
3223          case KeySystemFormats.WIDEVINE:
3224          case KeySystemFormats.PLAYREADY:
3225          case KeySystemFormats.CLEARKEY:
3226            return ['ISO-23001-7', 'SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method) !== -1;
3227        }
3228      }
3229    }
3230    return false;
3231  }
3232  getDecryptData(sn) {
3233    if (!this.encrypted || !this.uri) {
3234      return null;
3235    }
3236    if (this.method === 'AES-128' && this.uri && !this.iv) {
3237      if (typeof sn !== 'number') {
3238        // We are fetching decryption data for a initialization segment
3239        // If the segment was encrypted with AES-128
3240        // It must have an IV defined. We cannot substitute the Segment Number in.
3241        if (this.method === 'AES-128' && !this.iv) {
3242          logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
3243        }
3244        // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
3245        sn = 0;
3246      }
3247      const iv = createInitializationVector(sn);
3248      const decryptdata = new LevelKey(this.method, this.uri, 'identity', this.keyFormatVersions, iv);
3249      return decryptdata;
3250    }
3251
3252    // Initialize keyId if possible
3253    const keyBytes = convertDataUriToArrayBytes(this.uri);
3254    if (keyBytes) {
3255      switch (this.keyFormat) {
3256        case KeySystemFormats.WIDEVINE:
3257          this.pssh = keyBytes;
3258          // In case of widevine keyID is embedded in PSSH box. Read Key ID.
3259          if (keyBytes.length >= 22) {
3260            this.keyId = keyBytes.subarray(keyBytes.length - 22, keyBytes.length - 6);
3261          }
3262          break;
3263        case KeySystemFormats.PLAYREADY:
3264          {
3265            const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
3266            this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
3267            const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
3268            const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
3269
3270            // Parse Playready WRMHeader XML
3271            const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
3272            const parser = new DOMParser();
3273            const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
3274            const keyData = xmlDoc.getElementsByTagName('KID')[0];
3275            if (keyData) {
3276              const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
3277              if (keyId) {
3278                const keyIdArray = base64Decode(keyId).subarray(0, 16);
3279                // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
3280                // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
3281                changeEndianness(keyIdArray);
3282                this.keyId = keyIdArray;
3283              }
3284            }
3285            break;
3286          }
3287        default:
3288          {
3289            let keydata = keyBytes.subarray(0, 16);
3290            if (keydata.length !== 16) {
3291              const padded = new Uint8Array(16);
3292              padded.set(keydata, 16 - keydata.length);
3293              keydata = padded;
3294            }
3295            this.keyId = keydata;
3296            break;
3297          }
3298      }
3299    }
3300
3301    // Default behavior: assign a new keyId for each uri
3302    if (!this.keyId || this.keyId.byteLength !== 16) {
3303      let keyId = keyUriToKeyIdMap[this.uri];
3304      if (!keyId) {
3305        const val = Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER;
3306        keyId = new Uint8Array(16);
3307        const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes
3308        dv.setUint32(0, val);
3309        keyUriToKeyIdMap[this.uri] = keyId;
3310      }
3311      this.keyId = keyId;
3312    }
3313    return this;
3314  }
3315}
3316function createInitializationVector(segmentNumber) {
3317  const uint8View = new Uint8Array(16);
3318  for (let i = 12; i < 16; i++) {
3319    uint8View[i] = segmentNumber >> 8 * (15 - i) & 0xff;
3320  }
3321  return uint8View;
3322}
3323
3324const VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g;
3325function hasVariableReferences(str) {
3326  return VARIABLE_REPLACEMENT_REGEX.test(str);
3327}
3328function substituteVariablesInAttributes(parsed, attr, attributeNames) {
3329  if (parsed.variableList !== null || parsed.hasVariableRefs) {
3330    for (let i = attributeNames.length; i--;) {
3331      const name = attributeNames[i];
3332      const value = attr[name];
3333      if (value) {
3334        attr[name] = substituteVariables(parsed, value);
3335      }
3336    }
3337  }
3338}
3339function substituteVariables(parsed, value) {
3340  if (parsed.variableList !== null || parsed.hasVariableRefs) {
3341    const variableList = parsed.variableList;
3342    return value.replace(VARIABLE_REPLACEMENT_REGEX, variableReference => {
3343      const variableName = variableReference.substring(2, variableReference.length - 1);
3344      const variableValue = variableList == null ? void 0 : variableList[variableName];
3345      if (variableValue === undefined) {
3346        parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`));
3347        return variableReference;
3348      }
3349      return variableValue;
3350    });
3351  }
3352  return value;
3353}
3354function addVariableDefinition(parsed, attr, parentUrl) {
3355  let variableList = parsed.variableList;
3356  if (!variableList) {
3357    parsed.variableList = variableList = {};
3358  }
3359  let NAME;
3360  let VALUE;
3361  if ('QUERYPARAM' in attr) {
3362    NAME = attr.QUERYPARAM;
3363    try {
3364      const searchParams = new self.URL(parentUrl).searchParams;
3365      if (searchParams.has(NAME)) {
3366        VALUE = searchParams.get(NAME);
3367      } else {
3368        throw new Error(`"${NAME}" does not match any query parameter in URI: "${parentUrl}"`);
3369      }
3370    } catch (error) {
3371      parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`));
3372    }
3373  } else {
3374    NAME = attr.NAME;
3375    VALUE = attr.VALUE;
3376  }
3377  if (NAME in variableList) {
3378    parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`));
3379  } else {
3380    variableList[NAME] = VALUE || '';
3381  }
3382}
3383function importVariableDefinition(parsed, attr, sourceVariableList) {
3384  const IMPORT = attr.IMPORT;
3385  if (sourceVariableList && IMPORT in sourceVariableList) {
3386    let variableList = parsed.variableList;
3387    if (!variableList) {
3388      parsed.variableList = variableList = {};
3389    }
3390    variableList[IMPORT] = sourceVariableList[IMPORT];
3391  } else {
3392    parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`));
3393  }
3394}
3395
3396/**
3397 * MediaSource helper
3398 */
3399
3400function getMediaSource(preferManagedMediaSource = true) {
3401  if (typeof self === 'undefined') return undefined;
3402  const mms = (preferManagedMediaSource || !self.MediaSource) && self.ManagedMediaSource;
3403  return mms || self.MediaSource || self.WebKitMediaSource;
3404}
3405function isManagedMediaSource(source) {
3406  return typeof self !== 'undefined' && source === self.ManagedMediaSource;
3407}
3408
3409// from http://mp4ra.org/codecs.html
3410// values indicate codec selection preference (lower is higher priority)
3411const sampleEntryCodesISO = {
3412  audio: {
3413    a3ds: 1,
3414    'ac-3': 0.95,
3415    'ac-4': 1,
3416    alac: 0.9,
3417    alaw: 1,
3418    dra1: 1,
3419    'dts+': 1,
3420    'dts-': 1,
3421    dtsc: 1,
3422    dtse: 1,
3423    dtsh: 1,
3424    'ec-3': 0.9,
3425    enca: 1,
3426    fLaC: 0.9,
3427    // MP4-RA listed codec entry for FLAC
3428    flac: 0.9,
3429    // legacy browser codec name for FLAC
3430    FLAC: 0.9,
3431    // some manifests may list "FLAC" with Apple's tools
3432    g719: 1,
3433    g726: 1,
3434    m4ae: 1,
3435    mha1: 1,
3436    mha2: 1,
3437    mhm1: 1,
3438    mhm2: 1,
3439    mlpa: 1,
3440    mp4a: 1,
3441    'raw ': 1,
3442    Opus: 1,
3443    opus: 1,
3444    // browsers expect this to be lowercase despite MP4RA says 'Opu
3444s'
3445    samr: 1,
3446    sawb: 1,
3447    sawp: 1,
3448    sevc: 1,
3449    sqcp: 1,
3450    ssmv: 1,
3451    twos: 1,
3452    ulaw: 1
3453  },
3454  video: {
3455    avc1: 1,
3456    avc2: 1,
3457    avc3: 1,
3458    avc4: 1,
3459    avcp: 1,
3460    av01: 0.8,
3461    drac: 1,
3462    dva1: 1,
3463    dvav: 1,
3464    dvh1: 0.7,
3465    dvhe: 0.7,
3466    encv: 1,
3467    hev1: 0.75,
3468    hvc1: 0.75,
3469    mjp2: 1,
3470    mp4v: 1,
3471    mvc1: 1,
3472    mvc2: 1,
3473    mvc3: 1,
3474    mvc4: 1,
3475    resv: 1,
3476    rv60: 1,
3477    s263: 1,
3478    svc1: 1,
3479    svc2: 1,
3480    'vc-1': 1,
3481    vp08: 1,
3482    vp09: 0.9
3483  },
3484  text: {
3485    stpp: 1,
3486    wvtt: 1
3487  }
3488};
3489function isCodecType(codec, type) {
3490  const typeCodes = sampleEntryCodesISO[type];
3491  return !!typeCodes && !!typeCodes[codec.slice(0, 4)];
3492}
3493function areCodecsMediaSourceSupported(codecs, type, preferManagedMediaSource = true) {
3494  return !codecs.split(',').some(codec => !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource));
3495}
3496function isCodecMediaSourceSupported(codec, type, preferManagedMediaSource = true) {
3497  var _MediaSource$isTypeSu;
3498  const MediaSource = getMediaSource(preferManagedMediaSource);
3499  return (_MediaSource$isTypeSu = MediaSource == null ? void 0 : MediaSource.isTypeSupported(mimeTypeForCodec(codec, type))) != null ? _MediaSource$isTypeSu : false;
3500}
3501function mimeTypeForCodec(codec, type) {
3502  return `${type}/mp4;codecs="${codec}"`;
3503}
3504function videoCodecPreferenceValue(videoCodec) {
3505  if (videoCodec) {
3506    const fourCC = videoCodec.substring(0, 4);
3507    return sampleEntryCodesISO.video[fourCC];
3508  }
3509  return 2;
3510}
3511function codecsSetSelectionPreferenceValue(codecSet) {
3512  return codecSet.split(',').reduce((num, fourCC) => {
3513    const preferenceValue = sampleEntryCodesISO.video[fourCC];
3514    if (preferenceValue) {
3515      return (preferenceValue * 2 + num) / (num ? 3 : 2);
3516    }
3517    return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2 : 1);
3518  }, 0);
3519}
3520const CODEC_COMPATIBLE_NAMES = {};
3521function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource = true) {
3522  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
3523    return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
3524  }
3525
3526  // Idealy fLaC and Opus would be first (spec-compliant) but
3527  // some browsers will report that fLaC is supported then fail.
3528  // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
3529  const codecsToCheck = {
3530    flac: ['flac', 'fLaC', 'FLAC'],
3531    opus: ['opus', 'Opus']
3532  }[lowerCaseCodec];
3533  for (let i = 0; i < codecsToCheck.length; i++) {
3534    if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
3535      CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
3536      return codecsToCheck[i];
3537    }
3538  }
3539  return lowerCaseCodec;
3540}
3541const AUDIO_CODEC_REGEXP = /flac|opus/i;
3542function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
3543  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
3544}
3545function pickMostCompleteCodecName(parsedCodec, levelCodec) {
3546  // Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a
3547  // so use level codec is parsed codec is unavailable or incomplete
3548  if (parsedCodec && parsedCodec !== 'mp4a') {
3549    return parsedCodec;
3550  }
3551  return levelCodec ? levelCodec.split(',')[0] : levelCodec;
3552}
3553function convertAVC1ToAVCOTI(codec) {
3554  // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported
3555  // Examples: avc1.66.30 to avc1.42001e and avc1.77.30,avc1.66.30 to avc1.4d001e,avc1.42001e.
3556  const codecs = codec.split(',');
3557  for (let i = 0; i < codecs.length; i++) {
3558    const avcdata = codecs[i].split('.');
3559    if (avcdata.length > 2) {
3560      let result = avcdata.shift() + '.';
3561      result += parseInt(avcdata.shift()).toString(16);
3562      result += ('000' + parseInt(avcdata.shift()).toString(16)).slice(-4);
3563      codecs[i] = result;
3564    }
3565  }
3566  return codecs.join(',');
3567}
3568
3569const 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;
3570const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
3571const IS_MEDIA_PLAYLIST = /^#EXT(?:INF|-X-TARGETDURATION):/m; // Handle empty Media Playlist (first EXTINF not signaled, but TARGETDURATION present)
3572
3573const LEVEL_PLAYLIST_REGEX_FAST = new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source,
3574// duration (#EXTINF:<duration>,<title>), group 1 => duration, group 2 => title
3575/(?!#) *(\S[^\r\n]*)/.source,
3576// segment URI, group 3 => the URI (note newline is not eaten)
3577/#EXT-X-BYTERANGE:*(.+)/.source,
3578// next segment's byterange, group 4 => range spec (x@y)
3579/#EXT-X-PROGRAM-DATE-TIME:(.+)/.source,
3580// next segment's program date/time group 5 => the datetime spec
3581/#.*/.source // All other non-segment oriented tags will match with all groups empty
3582].join('|'), 'g');
3583const 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('|'));
3584class M3U8Parser {
3585  static findGroup(groups, mediaGroupId) {
3586    for (let i = 0; i < groups.length; i++) {
3587      const group = groups[i];
3588      if (group.id === mediaGroupId) {
3589        return group;
3590      }
3591    }
3592  }
3593  static resolve(url, baseUrl) {
3594    return urlToolkitExports.buildAbsoluteURL(baseUrl, url, {
3595      alwaysNormalize: true
3596    });
3597  }
3598  static isMediaPlaylist(str) {
3599    return IS_MEDIA_PLAYLIST.test(str);
3600  }
3601  static parseMasterPlaylist(string, baseurl) {
3602    const hasVariableRefs = hasVariableReferences(string) ;
3603    const parsed = {
3604      contentSteering: null,
3605      levels: [],
3606      playlistParsingError: null,
3607      sessionData: null,
3608      sessionKeys: null,
3609      startTimeOffset: null,
3610      variableList: null,
3611      hasVariableRefs
3612    };
3613    const levelsWithKnownCodecs = [];
3614    MASTER_PLAYLIST_REGEX.lastIndex = 0;
3615    let result;
3616    while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
3617      if (result[1]) {
3618        var _level$unknownCodecs;
3619        // '#EXT-X-STREAM-INF' is found, parse level tag  in group 1
3620        const attrs = new AttrList(result[1]);
3621        {
3622          substituteVariablesInAttributes(parsed, attrs, ['CODECS', 'SUPPLEMENTAL-CODECS', 'ALLOWED-CPC', 'PATHWAY-ID', 'STABLE-VARIANT-ID', 'AUDIO', 'VIDEO', 'SUBTITLES', 'CLOSED-CAPTIONS', 'NAME']);
3623        }
3624        const uri = substituteVariables(parsed, result[2]) ;
3625        const level = {
3626          attrs,
3627          bitrate: attrs.decimalInteger('BANDWIDTH') || attrs.decimalInteger('AVERAGE-BANDWIDTH'),
3628          name: attrs.NAME,
3629          url: M3U8Parser.resolve(uri, baseurl)
3630        };
3631        const resolution = attrs.decimalResolution('RESOLUTION');
3632        if (resolution) {
3633          level.width = resolution.width;
3634          level.height = resolution.height;
3635        }
3636        setCodecs(attrs.CODECS, level);
3637        if (!((_level$unknownCodecs = level.unknownCodecs) != null && _level$unknownCodecs.length)) {
3638          levelsWithKnownCodecs.push(level);
3639        }
3640        parsed.levels.push(level);
3641      } else if (result[3]) {
3642        const tag = result[3];
3643        const attributes = result[4];
3644        switch (tag) {
3645          case 'SESSION-DATA':
3646            {
3647              // #EXT-X-SESSION-DATA
3648              const sessionAttrs = new AttrList(attributes);
3649              {
3650                substituteVariablesInAttributes(parsed, sessionAttrs, ['DATA-ID', 'LANGUAGE', 'VALUE', 'URI']);
3651              }
3652              const dataId = sessionAttrs['DATA-ID'];
3653              if (dataId) {
3654                if (parsed.sessionData === null) {
3655                  parsed.sessionData = {};
3656                }
3657                parsed.sessionData[dataId] = sessionAttrs;
3658              }
3659              break;
3660            }
3661          case 'SESSION-KEY':
3662            {
3663              // #EXT-X-SESSION-KEY
3664              const sessionKey = parseKey(attributes, baseurl, parsed);
3665              if (sessionKey.encrypted && sessionKey.isSupported()) {
3666                if (parsed.sessionKeys === null) {
3667                  parsed.sessionKeys = [];
3668                }
3669                parsed.sessionKeys.push(sessionKey);
3670              } else {
3671                logger.warn(`[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "${attributes}"`);
3672              }
3673              break;
3674            }
3675          case 'DEFINE':
3676            {
3677              // #EXT-X-DEFINE
3678              {
3679                const variableAttributes = new AttrList(attributes);
3680                substituteVariablesInAttributes(parsed, variableAttributes, ['NAME', 'VALUE', 'QUERYPARAM']);
3681                addVariableDefinition(parsed, variableAttributes, baseurl);
3682              }
3683              break;
3684            }
3685          case 'CONTENT-STEERING':
3686            {
3687              // #EXT-X-CONTENT-STEERING
3688              const contentSteeringAttributes = new AttrList(attributes);
3689              {
3690                substituteVariablesInAttributes(parsed, contentSteeringAttributes, ['SERVER-URI', 'PATHWAY-ID']);
3691              }
3692              parsed.contentSteering = {
3693                uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl),
3694                pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.'
3695              };
3696              break;
3697            }
3698          case 'START':
3699            {
3700              // #EXT-X-START
3701              parsed.startTimeOffset = parseStartTimeOffset(attributes);
3702              break;
3703            }
3704        }
3705      }
3706    }
3707    // Filter out levels with unknown codecs if it does not remove all levels
3708    const stripUnknownCodecLevels = levelsWithKnownCodecs.length > 0 && levelsWithKnownCodecs.length < parsed.levels.length;
3709    parsed.levels = stripUnknownCodecLevels ? levelsWithKnownCodecs : parsed.levels;
3710    if (parsed.levels.length === 0) {
3711      parsed.playlistParsingError = new Error('no levels found in manifest');
3712    }
3713    return parsed;
3714  }
3715  static parseMasterPlaylistMedia(string, baseurl, parsed) {
3716    let result;
3717    const results = {};
3718    const levels = parsed.levels;
3719    const groupsByType = {
3720      AUDIO: levels.map(level => ({
3721        id: level.attrs.AUDIO,
3722        audioCodec: level.audioCodec
3723      })),
3724      SUBTITLES: levels.map(level => ({
3725        id: level.attrs.SUBTITLES,
3726        textCodec: level.textCodec
3727      })),
3728      'CLOSED-CAPTIONS': []
3729    };
3730    let id = 0;
3731    MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
3732    while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
3733      const attrs = new AttrList(result[1]);
3734      const type = attrs.TYPE;
3735      if (type) {
3736        const groups = groupsByType[type];
3737        const medias = results[type] || [];
3738        results[type] = medias;
3739        {
3740          substituteVariablesInAttributes(parsed, attrs, ['URI', 'GROUP-ID', 'LANGUAGE', 'ASSOC-LANGUAGE', 'STABLE-RENDITION-ID', 'NAME', 'INSTREAM-ID', 'CHARACTERISTICS', 'CHANNELS']);
3741        }
3742        const lang = attrs.LANGUAGE;
3743        const assocLang = attrs['ASSOC-LANGUAGE'];
3744        const channels = attrs.CHANNELS;
3745        const characteristics = attrs.CHARACTERISTICS;
3746        const instreamId = attrs['INSTREAM-ID'];
3747        const media = {
3748          attrs,
3749          bitrate: 0,
3750          id: id++,
3751          groupId: attrs['GROUP-ID'] || '',
3752          name: attrs.NAME || lang || '',
3753          type,
3754          default: attrs.bool('DEFAULT'),
3755          autoselect: attrs.bool('AUTOSELECT'),
3756          forced: attrs.bool('FORCED'),
3757          lang,
3758          url: attrs.URI ? M3U8Parser.resolve(attrs.URI, baseurl) : ''
3759        };
3760        if (assocLang) {
3761          media.assocLang = assocLang;
3762        }
3763        if (channels) {
3764          media.channels = channels;
3765        }
3766        if (characteristics) {
3767          media.characteristics = characteristics;
3768        }
3769        if (instreamId) {
3770          media.instreamId = instreamId;
3771        }
3772        if (groups != null && groups.length) {
3773          // If there are audio or text groups signalled in the manifest, let's look for a matching codec string for this track
3774          // If we don't find the track signalled, lets use the first audio groups codec we have
3775          // Acting as a best guess
3776          const groupCodec = M3U8Parser.findGroup(groups, media.groupId) || groups[0];
3777          assignCodec(media, groupCodec, 'audioCodec');
3778          assignCodec(media, groupCodec, 'textCodec');
3779        }
3780        medias.push(media);
3781      }
3782    }
3783    return results;
3784  }
3785  static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) {
3786    const level = new LevelDetails(baseurl);
3787    const fragments = level.fragments;
3788    // The most recent init segment seen (applies to all subsequent segments)
3789    let currentInitSegment = null;
3790    let currentSN = 0;
3791    let currentPart = 0;
3792    let totalduration = 0;
3793    let discontinuityCounter = 0;
3794    let prevFrag = null;
3795    let frag = new Fragment(type, baseurl);
3796    let result;
3797    let i;
3798    let levelkeys;
3799    let firstPdtIndex = -1;
3800    let createNextFrag = false;
3801    let nextByteRange = null;
3802    LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0;
3803    level.m3u8 = string;
3804    level.hasVariableRefs = hasVariableReferences(string) ;
3805    while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) {
3806      if (createNextFrag) {
3807        createNextFrag = false;
3808        frag = new Fragment(type, baseurl);
3809        // setup the next fragment for part loading
3810        frag.start = totalduration;
3811        frag.sn = currentSN;
3812        frag.cc = discontinuityCounter;
3813        frag.level = id;
3814        if (currentInitSegment) {
3815          frag.initSegment = currentInitSegment;
3816          frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
3817          currentInitSegment.rawProgramDateTime = null;
3818          if (nextByteRange) {
3819            frag.setByteRange(nextByteRange);
3820            nextByteRange = null;
3821          }
3822        }
3823      }
3824      const duration = result[1];
3825      if (duration) {
3826        // INF
3827        frag.duration = parseFloat(duration);
3828        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
3829        const title = (' ' + result[2]).slice(1);
3830        frag.title = title || null;
3831        frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]);
3832      } else if (result[3]) {
3833        // url
3834        if (isFiniteNumber(frag.duration)) {
3835          frag.start = totalduration;
3836          if (levelkeys) {
3837            setFragLevelKeys(frag, levelkeys, level);
3838          }
3839          frag.sn = currentSN;
3840          frag.level = id;
3841          frag.cc = discontinuityCounter;
3842          fragments.push(frag);
3843          // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
3844          const uri = (' ' + result[3]).slice(1);
3845          frag.relurl = substituteVariables(level, uri) ;
3846          assignProgramDateTime(frag, prevFrag);
3847          prevFrag = frag;
3848          totalduration += frag.duration;
3849          currentSN++;
3850          currentPart = 0;
3851          createNextFrag = true;
3852        }
3853      } else if (result[4]) {
3854        // X-BYTERANGE
3855        const data = (' ' + result[4]).slice(1);
3856        if (prevFrag) {
3857          frag.setByteRange(data, prevFrag);
3858        } else {
3859          frag.setByteRange(data);
3860        }
3861      } else if (result[5]) {
3862        // PROGRAM-DATE-TIME
3863        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
3864        frag.rawProgramDateTime = (' ' + result[5]).slice(1);
3865        frag.tagList.push(['PROGRAM-DATE-TIME', frag.rawProgramDateTime]);
3866        if (firstPdtIndex === -1) {
3867          firstPdtIndex = fragments.length;
3868        }
3869      } else {
3870        result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW);
3871        if (!result) {
3872          logger.warn('No matches on slow regex match for level playlist!');
3873          continue;
3874        }
3875        for (i = 1; i < result.length; i++) {
3876          if (typeof result[i] !== 'undefined') {
3877            break;
3878          }
3879        }
3880
3881        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
3882        const tag = (' ' + result[i]).slice(1);
3883        const value1 = (' ' + result[i + 1]).slice(1);
3884        const value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : '';
3885        switch (tag) {
3886          case 'PLAYLIST-TYPE':
3887            level.type = value1.toUpperCase();
3888            break;
3889          case 'MEDIA-SEQUENCE':
3890            currentSN = level.startSN = parseInt(value1);
3891            break;
3892          case 'SKIP':
3893            {
3894              const skipAttrs = new AttrList(value1);
3895              {
3896                substituteVariablesInAttributes(level, skipAttrs, ['RECENTLY-REMOVED-DATERANGES']);
3897              }
3898              const skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS');
3899              if (isFiniteNumber(skippedSegments)) {
3900                level.skippedSegments = skippedSegments;
3901                // This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails`
3902                for (let _i = skippedSegments; _i--;) {
3903                  fragments.unshift(null);
3904                }
3905                currentSN += skippedSegments;
3906              }
3907              const recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES');
3908              if (recentlyRemovedDateranges) {
3909                level.recentlyRemovedDateranges = recentlyRemovedDateranges.split('\t');
3910              }
3911              break;
3912            }
3913          case 'TARGETDURATION':
3914            level.targetduration = Math.max(parseInt(value1), 1);
3915            break;
3916          case 'VERSION':
3917            level.version = parseInt(value1);
3918            break;
3919          case 'INDEPENDENT-SEGMENTS':
3920          case 'EXTM3U':
3921            break;
3922          case 'ENDLIST':
3923            level.live = false;
3924            break;
3925          case '#':
3926            if (value1 || value2) {
3927              frag.tagList.push(value2 ? [value1, value2] : [value1]);
3928            }
3929            break;
3930          case 'DISCONTINUITY':
3931            discontinuityCounter++;
3932            frag.tagList.push(['DIS']);
3933            break;
3934          case 'GAP':
3935            frag.gap = true;
3936            frag.tagList.push([tag]);
3937            break;
3938          case 'BITRATE':
3939            frag.tagList.push([tag, value1]);
3940            break;
3941          case 'DATERANGE':
3942            {
3943              const dateRangeAttr = new AttrList(value1);
3944              {
3945                substituteVariablesInAttributes(level, dateRangeAttr, ['ID', 'CLASS', 'START-DATE', 'END-DATE', 'SCTE35-CMD', 'SCTE35-OUT', 'SCTE35-IN']);
3946                substituteVariablesInAttributes(level, dateRangeAttr, dateRangeAttr.clientAttrs);
3947              }
3948              const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID]);
3949              if (dateRange.isValid || level.skippedSegments) {
3950                level.dateRanges[dateRange.id] = dateRange;
3951              } else {
3952                logger.warn(`Ignoring invalid DATERANGE tag: "${value1}"`);
3953              }
3954              // Add to fragment tag list for backwards compatibility (< v1.2.0)
3955              frag.tagList.push(['EXT-X-DATERANGE', value1]);
3956              break;
3957            }
3958          case 'DEFINE':
3959            {
3960              {
3961                const variableAttributes = new AttrList(value1);
3962                substituteVariablesInAttributes(level, variableAttributes, ['NAME', 'VALUE', 'IMPORT', 'QUERYPARAM']);
3963                if ('IMPORT' in variableAttributes) {
3964                  importVariableDefinition(level, variableAttributes, multivariantVariableList);
3965                } else {
3966                  addVariableDefinition(level, variableAttributes, baseurl);
3967                }
3968              }
3969              break;
3970            }
3971          case 'DISCONTINUITY-SEQUENCE':
3972            discontinuityCounter = parseInt(value1);
3973            break;
3974          case 'KEY':
3975            {
3976              const levelKey = parseKey(value1, baseurl, level);
3977              if (levelKey.isSupported()) {
3978                if (levelKey.method === 'NONE') {
3979                  levelkeys = undefined;
3980                  break;
3981                }
3982                if (!levelkeys) {
3983                  levelkeys = {};
3984                }
3985                if (levelkeys[levelKey.keyFormat]) {
3986                  levelkeys = _extends({}, levelkeys);
3987                }
3988                levelkeys[levelKey.keyFormat] = levelKey;
3989              } else {
3990                logger.warn(`[Keys] Ignoring invalid EXT-X-KEY tag: "${value1}"`);
3991              }
3992              break;
3993            }
3994          case 'START':
3995            level.startTimeOffset = parseStartTimeOffset(value1);
3996            break;
3997          case 'MAP':
3998            {
3999              const mapAttrs = new AttrList(value1);
4000              {
4001                substituteVariablesInAttributes(level, mapAttrs, ['BYTERANGE', 'URI']);
4002              }
4003              if (frag.duration) {
4004                // Initial segment tag is after segment duration tag.
4005                //   #EXTINF: 6.0
4006                //   #EXT-X-MAP:URI="init.mp4
4007                const init = new Fragment(type, baseurl);
4008                setInitSegment(init, mapAttrs, id, levelkeys);
4009                currentInitSegment = init;
4010                frag.initSegment = currentInitSegment;
4011                if (currentInitSegment.rawProgramDateTime && !frag.rawProgramDateTime) {
4012                  frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
4013                }
4014              } else {
4015                // Initial segment tag is before segment duration tag
4016                // Handle case where EXT-X-MAP is declared after EXT-X-BYTERANGE
4017                const end = frag.byteRangeEndOffset;
4018                if (end) {
4019                  const start = frag.byteRangeStartOffset;
4020                  nextByteRange = `${end - start}@${start}`;
4021                } else {
4022                  nextByteRange = null;
4023                }
4024                setInitSegment(frag, mapAttrs, id, levelkeys);
4025                currentInitSegment = frag;
4026                createNextFrag = true;
4027              }
4028              break;
4029            }
4030          case 'SERVER-CONTROL':
4031            {
4032              const serverControlAttrs = new AttrList(value1);
4033              level.canBlockReload = serverControlAttrs.bool('CAN-BLOCK-RELOAD');
4034              level.canSkipUntil = serverControlAttrs.optionalFloat('CAN-SKIP-UNTIL', 0);
4035              level.canSkipDateRanges = level.canSkipUntil > 0 && serverControlAttrs.bool('CAN-SKIP-DATERANGES');
4036              level.partHoldBack = serverControlAttrs.optionalFloat('PART-HOLD-BACK', 0);
4037              level.holdBack = serverControlAttrs.optionalFloat('HOLD-BACK', 0);
4038              break;
4039            }
4040          case 'PART-INF':
4041            {
4042              const partInfAttrs = new AttrList(value1);
4043              level.partTarget = partInfAttrs.decimalFloatingPoint('PART-TARGET');
4044              break;
4045            }
4046          case 'PART':
4047            {
4048              let partList = level.partList;
4049              if (!partList) {
4050                partList = level.partList = [];
4051              }
4052              const previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : undefined;
4053              const index = currentPart++;
4054              const partAttrs = new AttrList(value1);
4055              {
4056                substituteVariablesInAttributes(level, partAttrs, ['BYTERANGE', 'URI']);
4057              }
4058              const part = new Part(partAttrs, frag, baseurl, index, previousFragmentPart);
4059              partList.push(part);
4060              frag.duration += part.duration;
4061              break;
4062            }
4063          case 'PRELOAD-HINT':
4064            {
4065              const preloadHintAttrs = new AttrList(value1);
4066              {
4067                substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']);
4068              }
4069              level.preloadHint = preloadHintAttrs;
4070              break;
4071            }
4072          case 'RENDITION-REPORT':
4073            {
4074              const renditionReportAttrs = new AttrList(value1);
4075              {
4076                substituteVariablesInAttributes(level, renditionReportAttrs, ['URI']);
4077              }
4078              level.renditionReports = level.renditionReports || [];
4079              level.renditionReports.push(renditionReportAttrs);
4080              break;
4081            }
4082          default:
4083            logger.warn(`line parsed but not handled: ${result}`);
4084            break;
4085        }
4086      }
4087    }
4088    if (prevFrag && !prevFrag.relurl) {
4089      fragments.pop();
4090      totalduration -= prevFrag.duration;
4091      if (level.partList) {
4092        level.fragmentHint = prevFrag;
4093      }
4094    } else if (level.partList) {
4095      assignProgramDateTime(frag, prevFrag);
4096      frag.cc = discontinuityCounter;
4097      level.fragmentHint = frag;
4098      if (levelkeys) {
4099        setFragLevelKeys(frag, levelkeys, level);
4100      }
4101    }
4102    const fragmentLength = fragments.length;
4103    const firstFragment = fragments[0];
4104    const lastFragment = fragments[fragmentLength - 1];
4105    totalduration += level.skippedSegments * level.targetduration;
4106    if (totalduration > 0 && fragmentLength && lastFragment) {
4107      level.averagetargetduration = totalduration / fragmentLength;
4108      const lastSn = lastFragment.sn;
4109      level.endSN = lastSn !== 'initSegment' ? lastSn : 0;
4110      if (!level.live) {
4111        lastFragment.endList = true;
4112      }
4113      if (firstFragment) {
4114        level.startCC = firstFragment.cc;
4115      }
4116    } else {
4117      level.endSN = 0;
4118      level.startCC = 0;
4119    }
4120    if (level.fragmentHint) {
4121      totalduration += level.fragmentHint.duration;
4122    }
4123    level.totalduration = totalduration;
4124    level.endCC = discontinuityCounter;
4125
4126    /**
4127     * Backfill any missing PDT values
4128     * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
4129     * one or more Media Segment URIs, the client SHOULD extrapolate
4130     * backward from that tag (using EXTINF durations and/or media
4131     * timestamps) to associate dates with those segments."
4132     * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
4133     * computed.
4134     */
4135    if (firstPdtIndex > 0) {
4136      backfillProgramDateTimes(fragments, firstPdtIndex);
4137    }
4138    return level;
4139  }
4140}
4141function parseKey(keyTagAttributes, baseurl, parsed) {
4142  var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT;
4143  // https://tools.ietf.org/html/rfc8216#section-4.3.2.4
4144  const keyAttrs = new AttrList(keyTagAttributes);
4145  {
4146    substituteVariablesInAttributes(parsed, keyAttrs, ['KEYFORMAT', 'KEYFORMATVERSIONS', 'URI', 'IV', 'URI']);
4147  }
4148  const decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : '';
4149  const decrypturi = keyAttrs.URI;
4150  const decryptiv = keyAttrs.hexadecimalInteger('IV');
4151  const decryptkeyformatversions = keyAttrs.KEYFORMATVERSIONS;
vendor: 7,470 bytes, lines 4152-4409
4152  // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity".
4153  const decryptkeyformat = (_keyAttrs$KEYFORMAT = keyAttrs.KEYFORMAT) != null ? _keyAttrs$KEYFORMAT : 'identity';
4154  if (decrypturi && keyAttrs.IV && !decryptiv) {
4155    logger.error(`Invalid IV: ${keyAttrs.IV}`);
4156  }
4157  // If decrypturi is a URI with a scheme, then baseurl will be ignored
4158  // No uri is allowed when METHOD is NONE
4159  const resolvedUri = decrypturi ? M3U8Parser.resolve(decrypturi, baseurl) : '';
4160  const keyFormatVersions = (decryptkeyformatversions ? decryptkeyformatversions : '1').split('/').map(Number).filter(Number.isFinite);
4161  return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv);
4162}
4163function parseStartTimeOffset(startAttributes) {
4164  const startAttrs = new AttrList(startAttributes);
4165  const startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET');
4166  if (isFiniteNumber(startTimeOffset)) {
4167    return startTimeOffset;
4168  }
4169  return null;
4170}
4171function setCodecs(codecsAttributeValue, level) {
4172  let codecs = (codecsAttributeValue || '').split(/[ ,]+/).filter(c => c);
4173  ['video', 'audio', 'text'].forEach(type => {
4174    const filtered = codecs.filter(codec => isCodecType(codec, type));
4175    if (filtered.length) {
4176      // Comma separated list of all codecs for type
4177      level[`${type}Codec`] = filtered.join(',');
4178      // Remove known codecs so that only unknownCodecs are left after iterating through each type
4179      codecs = codecs.filter(codec => filtered.indexOf(codec) === -1);
4180    }
4181  });
4182  level.unknownCodecs = codecs;
4183}
4184function assignCodec(media, groupItem, codecProperty) {
4185  const codecValue = groupItem[codecProperty];
4186  if (codecValue) {
4187    media[codecProperty] = codecValue;
4188  }
4189}
4190function backfillProgramDateTimes(fragments, firstPdtIndex) {
4191  let fragPrev = fragments[firstPdtIndex];
4192  for (let i = firstPdtIndex; i--;) {
4193    const frag = fragments[i];
4194    // Exit on delta-playlist skipped segments
4195    if (!frag) {
4196      return;
4197    }
4198    frag.programDateTime = fragPrev.programDateTime - frag.duration * 1000;
4199    fragPrev = frag;
4200  }
4201}
4202function assignProgramDateTime(frag, prevFrag) {
4203  if (frag.rawProgramDateTime) {
4204    frag.programDateTime = Date.parse(frag.rawProgramDateTime);
4205  } else if (prevFrag != null && prevFrag.programDateTime) {
4206    frag.programDateTime = prevFrag.endProgramDateTime;
4207  }
4208  if (!isFiniteNumber(frag.programDateTime)) {
4209    frag.programDateTime = null;
4210    frag.rawProgramDateTime = null;
4211  }
4212}
4213function setInitSegment(frag, mapAttrs, id, levelkeys) {
4214  frag.relurl = mapAttrs.URI;
4215  if (mapAttrs.BYTERANGE) {
4216    frag.setByteRange(mapAttrs.BYTERANGE);
4217  }
4218  frag.level = id;
4219  frag.sn = 'initSegment';
4220  if (levelkeys) {
4221    frag.levelkeys = levelkeys;
4222  }
4223  frag.initSegment = null;
4224}
4225function setFragLevelKeys(frag, levelkeys, level) {
4226  frag.levelkeys = levelkeys;
4227  const {
4228    encryptedFragments
4229  } = level;
4230  if ((!encryptedFragments.length || encryptedFragments[encryptedFragments.length - 1].levelkeys !== levelkeys) && Object.keys(levelkeys).some(format => levelkeys[format].isCommonEncryption)) {
4231    encryptedFragments.push(frag);
4232  }
4233}
4234
4235var PlaylistContextType = {
4236  MANIFEST: "manifest",
4237  LEVEL: "level",
4238  AUDIO_TRACK: "audioTrack",
4239  SUBTITLE_TRACK: "subtitleTrack"
4240};
4241var PlaylistLevelType = {
4242  MAIN: "main",
4243  AUDIO: "audio",
4244  SUBTITLE: "subtitle"
4245};
4246
4247function mapContextToLevelType(context) {
4248  const {
4249    type
4250  } = context;
4251  switch (type) {
4252    case PlaylistContextType.AUDIO_TRACK:
4253      return PlaylistLevelType.AUDIO;
4254    case PlaylistContextType.SUBTITLE_TRACK:
4255      return PlaylistLevelType.SUBTITLE;
4256    default:
4257      return PlaylistLevelType.MAIN;
4258  }
4259}
4260function getResponseUrl(response, context) {
4261  let url = response.url;
4262  // responseURL not supported on some browsers (it is used to detect URL redirection)
4263  // data-uri mode also not supported (but no need to detect redirection)
4264  if (url === undefined || url.indexOf('data:') === 0) {
4265    // fallback to initial URL
4266    url = context.url;
4267  }
4268  return url;
4269}
4270class PlaylistLoader {
4271  constructor(hls) {
4272    this.hls = void 0;
4273    this.loaders = Object.create(null);
4274    this.variableList = null;
4275    this.hls = hls;
4276    this.registerListeners();
4277  }
4278  startLoad(startPosition) {}
4279  stopLoad() {
4280    this.destroyInternalLoaders();
4281  }
4282  registerListeners() {
4283    const {
4284      hls
4285    } = this;
4286    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4287    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
4288    hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
4289    hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
4290  }
4291  unregisterListeners() {
4292    const {
4293      hls
4294    } = this;
4295    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4296    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
4297    hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
4298    hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
4299  }
4300
4301  /**
4302   * Returns defaults or configured loader-type overloads (pLoader and loader config params)
4303   */
4304  createInternalLoader(context) {
4305    const config = this.hls.config;
4306    const PLoader = config.pLoader;
4307    const Loader = config.loader;
4308    const InternalLoader = PLoader || Loader;
4309    const loader = new InternalLoader(config);
4310    this.loaders[context.type] = loader;
4311    return loader;
4312  }
4313  getInternalLoader(context) {
4314    return this.loaders[context.type];
4315  }
4316  resetInternalLoader(contextType) {
4317    if (this.loaders[contextType]) {
4318      delete this.loaders[contextType];
4319    }
4320  }
4321
4322  /**
4323   * Call `destroy` on all internal loader instances mapped (one per context type)
4324   */
4325  destroyInternalLoaders() {
4326    for (const contextType in this.loaders) {
4327      const loader = this.loaders[contextType];
4328      if (loader) {
4329        loader.destroy();
4330      }
4331      this.resetInternalLoader(contextType);
4332    }
4333  }
4334  destroy() {
4335    this.variableList = null;
4336    this.unregisterListeners();
4337    this.destroyInternalLoaders();
4338  }
4339  onManifestLoading(event, data) {
4340    const {
4341      url
4342    } = data;
4343    this.variableList = null;
4344    this.load({
4345      id: null,
4346      level: 0,
4347      responseType: 'text',
4348      type: PlaylistContextType.MANIFEST,
4349      url,
4350      deliveryDirectives: null
4351    });
4352  }
4353  onLevelLoading(event, data) {
4354    const {
4355      id,
4356      level,
4357      pathwayId,
4358      url,
4359      deliveryDirectives
4360    } = data;
4361    this.load({
4362      id,
4363      level,
4364      pathwayId,
4365      responseType: 'text',
4366      type: PlaylistContextType.LEVEL,
4367      url,
4368      deliveryDirectives
4369    });
4370  }
4371  onAudioTrackLoading(event, data) {
4372    const {
4373      id,
4374      groupId,
4375      url,
4376      deliveryDirectives
4377    } = data;
4378    this.load({
4379      id,
4380      groupId,
4381      level: null,
4382      responseType: 'text',
4383      type: PlaylistContextType.AUDIO_TRACK,
4384      url,
4385      deliveryDirectives
4386    });
4387  }
4388  onSubtitleTrackLoading(event, data) {
4389    const {
4390      id,
4391      groupId,
4392      url,
4393      deliveryDirectives
4394    } = data;
4395    this.load({
4396      id,
4397      groupId,
4398      level: null,
4399      responseType: 'text',
4400      type: PlaylistContextType.SUBTITLE_TRACK,
4401      url,
4402      deliveryDirectives
4403    });
4404  }
4405  load(context) {
4406    var _context$deliveryDire;
4407    const config = this.hls.config;
4408
4409    // logger.debug(`[playlist-loader]: Loading playlist of type ${context.type}, level: ${context.level}
4409, id: ${context.id}`);
4410
4411    // Check if a loader for this context already exists
4412    let loader = this.getInternalLoader(context);
4413    if (loader) {
4414      const loaderContext = loader.context;
4415      if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
4416        // same URL can't overlap
4417        logger.trace('[playlist-loader]: playlist request ongoing');
4418        return;
4419      }
4420      logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
4421      loader.abort();
4422    }
4423
4424    // apply different configs for retries depending on
4425    // context (manifest, level, audio/subs playlist)
4426    let loadPolicy;
4427    if (context.type === PlaylistContextType.MANIFEST) {
4428      loadPolicy = config.manifestLoadPolicy.default;
4429    } else {
4430      loadPolicy = _extends({}, config.playlistLoadPolicy.default, {
4431        timeoutRetry: null,
4432        errorRetry: null
4433      });
4434    }
4435    loader = this.createInternalLoader(context);
4436
4437    // Override level/track timeout for LL-HLS requests
4438    // (the default of 10000ms is counter productive to blocking playlist reload requests)
4439    if (isFiniteNumber((_context$deliveryDire = context.deliveryDirectives) == null ? void 0 : _context$deliveryDire.part)) {
4440      let levelDetails;
4441      if (context.type === PlaylistContextType.LEVEL && context.level !== null) {
4442        levelDetails = this.hls.levels[context.level].details;
4443      } else if (context.type === PlaylistContextType.AUDIO_TRACK && context.id !== null) {
4444        levelDetails = this.hls.audioTracks[context.id].details;
4445      } else if (context.type === PlaylistContextType.SUBTITLE_TRACK && context.id !== null) {
4446        levelDetails = this.hls.subtitleTracks[context.id].details;
4447      }
4448      if (levelDetails) {
4449        const partTarget = levelDetails.partTarget;
4450        const targetDuration = levelDetails.targetduration;
4451        if (partTarget && targetDuration) {
4452          const maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1000;
4453          loadPolicy = _extends({}, loadPolicy, {
4454            maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs),
4455            maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs)
4456          });
4457        }
4458      }
4459    }
4460    const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
4461    const loaderConfig = {
4462      loadPolicy,
4463      timeout: loadPolicy.maxLoadTimeMs,
4464      maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
4465      retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
4466      maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0
4467    };
4468    const loaderCallbacks = {
4469      onSuccess: (response, stats, context, networkDetails) => {
4470        const loader = this.getInternalLoader(context);
4471        this.resetInternalLoader(context.type);
4472        const string = response.data;
4473
4474        // Validate if it is an M3U8 at all
4475        if (string.indexOf('#EXTM3U') !== 0) {
4476          this.handleManifestParsingError(response, context, new Error('no EXTM3U delimiter'), networkDetails || null, stats);
4477          return;
4478        }
4479        stats.parsing.start = performance.now();
4480        if (M3U8Parser.isMediaPlaylist(string)) {
4481          this.handleTrackOrLevelPlaylist(response, stats, context, networkDetails || null, loader);
4482        } else {
4483          this.handleMasterPlaylist(response, stats, context, networkDetails);
4484        }
4485      },
4486      onError: (response, context, networkDetails, stats) => {
4487        this.handleNetworkError(context, networkDetails, false, response, stats);
4488      },
4489      onTimeout: (stats, context, networkDetails) => {
4490        this.handleNetworkError(context, networkDetails, true, undefined, stats);
4491      }
4492    };
4493
4494    // logger.debug(`[playlist-loader]: Calling internal loader delegate for URL: ${context.url}`);
4495
4496    loader.load(context, loaderConfig, loaderCallbacks);
4497  }
4498  handleMasterPlaylist(response, stats, context, networkDetails) {
4499    const hls = this.hls;
4500    const string = response.data;
4501    const url = getResponseUrl(response, context);
4502    const parsedResult = M3U8Parser.parseMasterPlaylist(string, url);
4503    if (parsedResult.playlistParsingError) {
4504      this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats);
4505      return;
4506    }
4507    const {
4508      contentSteering,
4509      levels,
4510      sessionData,
4511      sessionKeys,
4512      startTimeOffset,
4513      variableList
4514    } = parsedResult;
4515    this.variableList = variableList;
4516    const {
4517      AUDIO: audioTracks = [],
4518      SUBTITLES: subtitles,
4519      'CLOSED-CAPTIONS': captions
4520    } = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult);
4521    if (audioTracks.length) {
4522      // check if we have found an audio track embedded in main playlist (audio track without URI attribute)
4523      const embeddedAudioFound = audioTracks.some(audioTrack => !audioTrack.url);
4524
4525      // if no embedded audio track defined, but audio codec signaled in quality level,
4526      // we need to signal this main audio track this could happen with playlists with
4527      // alt audio rendition in which quality levels (main)
4528      // contains both audio+video. but with mixed audio track not signaled
4529      if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
4530        logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
4531        audioTracks.unshift({
4532          type: 'main',
4533          name: 'main',
4534          groupId: 'main',
4535          default: false,
4536          autoselect: false,
4537          forced: false,
4538          id: -1,
4539          attrs: new AttrList({}),
4540          bitrate: 0,
4541          url: ''
4542        });
4543      }
4544    }
4545    hls.trigger(Events.MANIFEST_LOADED, {
4546      levels,
4547      audioTracks,
4548      subtitles,
4549      captions,
4550      contentSteering,
4551      url,
4552      stats,
4553      networkDetails,
4554      sessionData,
4555      sessionKeys,
4556      startTimeOffset,
4557      variableList
4558    });
4559  }
4560  handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) {
4561    const hls = this.hls;
4562    const {
4563      id,
4564      level,
4565      type
4566    } = context;
4567    const url = getResponseUrl(response, context);
4568    const levelUrlId = 0;
4569    const levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0;
4570    const levelType = mapContextToLevelType(context);
4571    const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, this.variableList);
4572
4573    // We have done our first request (Manifest-type) and receive
4574    // not a master playlist but a chunk-list (track/level)
4575    // We fire the manifest-loaded event anyway with the parsed level-details
4576    // by creating a single-level structure for it.
4577    if (type === PlaylistContextType.MANIFEST) {
4578      const singleLevel = {
4579        attrs: new AttrList({}),
4580        bitrate: 0,
4581        details: levelDetails,
4582        name: '',
4583        url
4584      };
4585      hls.trigger(Events.MANIFEST_LOADED, {
4586        levels: [singleLevel],
4587        audioTracks: [],
4588        url,
4589        stats,
4590        networkDetails,
4591        sessionData: null,
4592        sessionKeys: null,
4593        contentSteering: null,
4594        startTimeOffset: null,
4595        variableList: null
4596      });
4597    }
4598
4599    // save parsing time
4600    stats.parsing.end = performance.now();
4601
4602    // extend the context with the new levelDetails property
4603    context.levelDetails = levelDetails;
4604    this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader);
4605  }
4606  handleManifestParsingError(response, context, error, networkDetails, stats) {
4607    this.hls.trigger(Events.ERROR, {
4608      type: ErrorTypes.NETWORK_ERROR,
4609      details: ErrorDetails.MANIFEST_PARSING_ERROR,
4610      fatal: context.type === PlaylistContextType.MANIFEST,
4611      url: response.url,
4612      err: error,
4613      error,
4614      reason: error.message,
4615      response,
4616      context,
4617      networkDetails,
4618      stats
4619    });
4620  }
4621  handleNetworkError(context, networkDetails, timeout = false, response, stats) {
4622    let message = `A network ${timeout ? 'timeout' : 'error' + (response ? ' (status ' + response.code + ')' : '')} occurred while loading ${context.type}`;
4623    if (context.type === PlaylistContextType.LEVEL) {
4624      message += `: ${context.level} id: ${context.id}`;
4625    } else if (context.type === PlaylistContextType.AUDIO_TRACK || context.type === PlaylistContextType.SUBTITLE_TRACK) {
4626      message += ` id: ${context.id} group-id: "${context.groupId}"`;
4627    }
4628    const error = new Error(message);
4629    logger.warn(`[playlist-loader]: ${message}`);
4630    let details = ErrorDetails.UNKNOWN;
4631    let fatal = false;
4632    const loader = this.getInternalLoader(context);
4633    switch (context.type) {
4634      case PlaylistContextType.MANIFEST:
4635        details = timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR;
4636        fatal = true;
4637        break;
4638      case PlaylistContextType.LEVEL:
4639        details = timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR;
4640        fatal = false;
4641        break;
4642      case PlaylistContextType.AUDIO_TRACK:
4643        details = timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR;
4644        fatal = false;
4645        break;
4646      case PlaylistContextType.SUBTITLE_TRACK:
4647        details = timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT : ErrorDetails.SUBTITLE_LOAD_ERROR;
4648        fatal = false;
4649        break;
4650    }
4651    if (loader) {
4652      this.resetInternalLoader(context.type);
4653    }
4654    const errorData = {
4655      type: ErrorTypes.NETWORK_ERROR,
4656      details,
4657      fatal,
4658      url: context.url,
4659      loader,
4660      context,
4661      error,
4662      networkDetails,
4663      stats
4664    };
4665    if (response) {
4666      const url = (networkDetails == null ? void 0 : networkDetails.url) || context.url;
4667      errorData.response = _objectSpread2({
4668        url,
4669        data: undefined
4670      }, response);
4671    }
4672    this.hls.trigger(Events.ERROR, errorData);
4673  }
4674  handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) {
4675    const hls = this.hls;
4676    const {
4677      type,
4678      level,
4679      id,
4680      groupId,
4681      deliveryDirectives
4682    } = context;
4683    const url = getResponseUrl(response, context);
4684    const parent = mapContextToLevelType(context);
4685    const levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? level : undefined;
4686    if (!levelDetails.fragments.length) {
4687      const _error = new Error('No Segments found in Playlist');
4688      hls.trigger(Events.ERROR, {
4689        type: ErrorTypes.NETWORK_ERROR,
4690        details: ErrorDetails.LEVEL_EMPTY_ERROR,
4691        fatal: false,
4692        url,
4693        error: _error,
4694        reason: _error.message,
4695        response,
4696        context,
4697        level: levelIndex,
4698        parent,
4699        networkDetails,
4700        stats
4701      });
4702      return;
4703    }
4704    if (!levelDetails.targetduration) {
4705      levelDetails.playlistParsingError = new Error('Missing Target Duration');
4706    }
4707    const error = levelDetails.playlistParsingError;
4708    if (error) {
4709      hls.trigger(Events.ERROR, {
4710        type: ErrorTypes.NETWORK_ERROR,
4711        details: ErrorDetails.LEVEL_PARSING_ERROR,
4712        fatal: false,
4713        url,
4714        error,
4715        reason: error.message,
4716        response,
4717        context,
4718        level: levelIndex,
4719        parent,
4720        networkDetails,
4721        stats
4722      });
4723      return;
4724    }
4725    if (levelDetails.live && loader) {
4726      if (loader.getCacheAge) {
4727        levelDetails.ageHeader = loader.getCacheAge() || 0;
4728      }
4729      if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) {
4730        levelDetails.ageHeader = 0;
4731      }
4732    }
4733    switch (type) {
4734      case PlaylistContextType.MANIFEST:
4735      case PlaylistContextType.LEVEL:
4736        hls.trigger(Events.LEVEL_LOADED, {
4737          details: levelDetails,
4738          level: levelIndex || 0,
4739          id: id || 0,
4740          stats,
4741          networkDetails,
4742          deliveryDirectives
4743        });
4744        break;
4745      case PlaylistContextType.AUDIO_TRACK:
4746        hls.trigger(Events.AUDIO_TRACK_LOADED, {
4747          details: levelDetails,
4748          id: id || 0,
4749          groupId: groupId || '',
4750          stats,
4751          networkDetails,
4752          deliveryDirectives
4753        });
4754        break;
4755      case PlaylistContextType.SUBTITLE_TRACK:
4756        hls.trigger(Events.SUBTITLE_TRACK_LOADED, {
4757          details: levelDetails,
4758          id: id || 0,
4759          groupId: groupId || '',
4760          stats,
4761          networkDetails,
4762          deliveryDirectives
4763        });
4764        break;
4765    }
4766  }
4767}
4768
4769function sendAddTrackEvent(track, videoEl) {
4770  let event;
4771  try {
4772    event = new Event('addtrack');
4773  } catch (err) {
4774    // for IE11
4775    event = document.createEvent('Event');
4776    event.initEvent('addtrack', false, false);
4777  }
4778  event.track = track;
4779  videoEl.dispatchEvent(event);
4780}
4781function addCueToTrack(track, cue) {
4782  // Sometimes there are cue overlaps on segmented vtts so the same
4783  // cue can appear more than once in different vtt files.
4784  // This avoid showing duplicated cues with same timecode and text.
4785  const mode = track.mode;
4786  if (mode === 'disabled') {
4787    track.mode = 'hidden';
4788  }
4789  if (track.cues && !track.cues.getCueById(cue.id)) {
4790    try {
4791      track.addCue(cue);
4792      if (!track.cues.getCueById(cue.id)) {
4793        throw new Error(`addCue is failed for: ${cue}`);
4794      }
4795    } catch (err) {
4796      logger.debug(`[texttrack-utils]: ${err}`);
4797      try {
4798        const textTrackCue = new self.TextTrackCue(cue.startTime, cue.endTime, cue.text);
4799        textTrackCue.id = cue.id;
4800        track.addCue(textTrackCue);
4801      } catch (err2) {
4802        logger.debug(`[texttrack-utils]: Legacy TextTrackCue fallback failed: ${err2}`);
4803      }
4804    }
4805  }
4806  if (mode === 'disabled') {
4807    track.mode = mode;
4808  }
4809}
4810function clearCurrentCues(track) {
4811  // When track.mode is disabled, track.cues will be null.
4812  // To guarantee the removal of cues, we need to temporarily
4813  // change the mode to hidden
4814  const mode = track.mode;
4815  if (mode === 'disabled') {
4816    track.mode = 'hidden';
4817  }
4818  if (track.cues) {
4819    for (let i = track.cues.length; i--;) {
4820      track.removeCue(track.cues[i]);
4821    }
4822  }
4823  if (mode === 'disabled') {
4824    track.mode = mode;
4825  }
4826}
4827function removeCuesInRange(track, start, end, predicate) {
4828  const mode = track.mode;
4829  if (mode === 'disabled') {
4830    track.mode = 'hidden';
4831  }
4832  if (track.cues && track.cues.length > 0) {
4833    const cues = getCuesInRange(track.cues, start, end);
4834    for (let i = 0; i < cues.length; i++) {
4835      if (!predicate || predicate(cues[i])) {
4836        track.removeCue(cues[i]);
4837      }
4838    }
4839  }
4840  if (mode === 'disabled') {
4841    track.mode = mode;
4842  }
4843}
4844
4845// Find first cue starting after given time.
4846// Modified version of binary search O(log(n)).
4847function getFirstCueIndexAfterTime(cues, time) {
4848  // If first cue starts after time, start there
4849  if (time < cues[0].startTime) {
4850    return 0;
4851  }
4852  // If the last cue ends before time there is no overlap
4853  const len = cues.length - 1;
4854  if (time > cues[len].endTime) {
4855    return -1;
4856  }
4857  let left = 0;
4858  let right = len;
4859  while (left <= right) {
4860    const mid = Math.floor((right + left) / 2);
4861    if (time < cues[mid].startTime) {
4862      right = mid - 1;
4863    } else if (time > cues[mid].startTime && left < len) {
4864      left = mid + 1;
4865    } else {
4866      // If it's not lower or higher, it must be equal.
4867      return mid;
4868    }
4869  }
4870  // At this point, left and right have swapped.
4871  // No direct match was found, left or right element must be the closest. Check which one has the smallest diff.
4872  return cues[left].startTime - time < time - cues[right].startTime ? left : right;
4873}
4874function getCuesInRange(cues, start, end) {
4875  const cuesFound = [];
4876  const firstCueInRange = getFirstCueIndexAfterTime(cues, start);
4877  if (firstCueInRange > -1) {
4878    for (let i = firstCueInRange, len = cues.length; i < len; i++) {
4879      const cue = cues[i];
4880      if (cue.startTime >= start && cue.endTime <= end) {
4881        cuesFound.push(cue);
4882      } else if (cue.startTime > end) {
4883        return cuesFound;
4884      }
4885    }
4886  }
4887  return cuesFound;
4888}
4889function filterSubtitleTracks(textTrackList) {
4890  const tracks = [];
4891  for (let i = 0; i < textTrackList.length; i++) {
4892    const track = textTrackList[i];
4893    // Edge adds a track without a label; we don't want to use it
4894    if ((track.kind === 'subtitles' || track.kind === 'captions') && track.label) {
4895      tracks.push(textTrackList[i]);
4896    }
4897  }
4898  return tracks;
4899}
4900
4901var MetadataSchema = {
4902  audioId3: "org.id3",
4903  dateRange: "com.apple.quicktime.HLS",
4904  emsg: "https://aomedia.org/emsg/ID3"
4905};
4906
4907const MIN_CUE_DURATION = 0.25;
4908function getCueClass() {
4909  if (typeof self === 'undefined') return undefined;
4910  return self.VTTCue || self.TextTrackCue;
4911}
4912function createCueWithDataFields(Cue, startTime, endTime, data, type) {
4913  let cue = new Cue(startTime, endTime, '');
4914  try {
4915    cue.value = data;
4916    if (type) {
4917      cue.type = type;
4918    }
4919  } catch (e) {
4920    cue = new Cue(startTime, endTime, JSON.stringify(type ? _objectSpread2({
4921      type
4922    }, data) : data));
4923  }
4924  return cue;
4925}
4926
4927// VTTCue latest draft allows an infinite duration, fallback
4928// to MAX_VALUE if necessary
4929const MAX_CUE_ENDTIME = (() => {
4930  const Cue = getCueClass();
4931  try {
4932    Cue && new Cue(0, Number.POSITIVE_INFINITY, '');
4933  } catch (e) {
4934    return Number.MAX_VALUE;
4935  }
4936  return Number.POSITIVE_INFINITY;
4937})();
4938function dateRangeDateToTimelineSeconds(date, offset) {
4939  return date.getTime() / 1000 - offset;
4940}
4941function hexToArrayBuffer(str) {
4942  return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer;
4943}
4944class ID3TrackController {
4945  constructor(hls) {
4946    this.hls = void 0;
4947    this.id3Track = null;
4948    this.media = null;
4949    this.dateRangeCuesAppended = {};
4950    this.hls = hls;
4951    this._registerListeners();
4952  }
4953  destroy() {
4954    this._unregisterListeners();
4955    this.id3Track = null;
4956    this.media = null;
4957    this.dateRangeCuesAppended = {};
4958    // @ts-ignore
4959    this.hls = null;
4960  }
4961  _registerListeners() {
4962    const {
4963      hls
4964    } = this;
4965    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
4966    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
4967    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4968    hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
4969    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
4970    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
4971  }
4972  _unregisterListeners() {
4973    const {
4974      hls
4975    } = this;
4976    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
4977    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
4978    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4979    hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
4980    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
4981    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
4982  }
4983
4984  // Add ID3 metatadata text track.
4985  onMediaAttached(event, data) {
4986    this.media = data.media;
4987  }
4988  onMediaDetaching() {
4989    if (!this.id3Track) {
4990      return;
4991    }
4992    clearCurrentCues(this.id3Track);
4993    this.id3Track = null;
4994    this.media = null;
4995    this.dateRangeCuesAppended = {};
4996  }
4997  onManifestLoading() {
4998    this.dateRangeCuesAppended = {};
4999  }
5000  createTrack(media) {
5001    const track = this.getID3Track(media.textTracks);
5002    track.mode = 'hidden';
5003    return track;
5004  }
5005  getID3Track(textTracks) {
5006    if (!this.media) {
5007      return;
5008    }
5009    for (let i = 0; i < textTracks.length; i++) {
5010      const textTrack = textTracks[i];
5011      if (textTrack.kind === 'metadata' && textTrack.label === 'id3') {
5012        // send 'addtrack' when reusing the textTrack for metadata,
5013        // same as what we do for captions
5014        sendAddTrackEvent(textTrack, this.media);
5015        return textTrack;
5016      }
5017    }
5018    return this.media.addTextTrack('metadata', 'id3');
5019  }
5020  onFragParsingMetadata(event, data) {
5021    if (!this.media) {
5022      return;
5023    }
5024    const {
5025      hls: {
5026        config: {
5027          enableEmsgMetadataCues,
5028          enableID3MetadataCues
5029        }
5030      }
5031    } = this;
5032    if (!enableEmsgMetadataCues && !enableID3MetadataCues) {
5033      return;
5034    }
5035    const {
5036      samples
5037    } = data;
5038
5039    // create track dynamically
5040    if (!this.id3Track) {
5041      this.id3Track = this.createTrack(this.media);
5042    }
5043    const Cue = getCueClass();
5044    if (!Cue) {
5045      return;
5046    }
5047    for (let i = 0; i < samples.length; i++) {
5048      const type = samples[i].type;
5049      if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) {
5050        continue;
5051      }
5052      const frames = getID3Frames(samples[i].data);
5053      if (frames) {
5054        const startTime = samples[i].pts;
5055        let endTime = startTime + samples[i].duration;
5056        if (endTime > MAX_CUE_ENDTIME) {
5057          endTime = MAX_CUE_ENDTIME;
5058        }
5059        const timeDiff = endTime - startTime;
5060        if (timeDiff <= 0) {
5061          endTime = startTime + MIN_CUE_DURATION;
5062        }
5063        for (let j = 0; j < frames.length; j++) {
5064          const frame = frames[j];
5065          // Safari doesn't put the timestamp frame in the TextTrack
5066          if (!isTimeStampFrame(frame)) {
5067            // add a bounds to any unbounded cues
5068            this.updateId3CueEnds(startTime, type);
5069            const cue = createCueWithDataFields(Cue, startTime, endTime, frame, type);
5070            if (cue) {
5071              this.id3Track.addCue(cue);
5072            }
5073          }
5074        }
5075      }
5076    }
5077  }
5078  updateId3CueEnds(startTime, type) {
5079    var _this$id3Track;
5080    const cues = (_this$id3Track = this.id3Track) == null ? void 0 : _this$id3Track.cues;
5081    if (cues) {
5082      for (let i = cues.length; i--;) {
5083        const cue = cues[i];
5084        if (cue.type === type && cue.startTime < startTime && cue.endTime === MAX_CUE_ENDTIME) {
5085          cue.endTime = startTime;
5086        }
5087      }
5088    }
5089  }
5090  onBufferFlushing(event, {
5091    startOffset,
5092    endOffset,
5093    type
5094  }) {
5095    const {
5096      id3Track,
5097      hls
5098    } = this;
5099    if (!hls) {
5100      return;
5101    }
5102    const {
5103      config: {
5104        enableEmsgMetadataCues,
5105        enableID3MetadataCues
5106      }
5107    } = hls;
5108    if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) {
5109      let predicate;
5110      if (type === 'audio') {
5111        predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues;
5112      } else if (type === 'video') {
5113        predicate = cue => cue.type === MetadataSchema.emsg && enableEmsgMetadataCues;
5114      } else {
5115        predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues || cue.type === MetadataSchema.emsg && enableEmsgMetadataCues;
5116      }
5117      removeCuesInRange(id3Track, startOffset, endOffset, predicate);
5118    }
5119  }
5120  onLevelUpdated(event, {
5121    details
5122  }) {
5123    if (!this.media || !details.hasProgramDateTime || !this.hls.config.enableDateRangeMetadataCues) {
5124      return;
5125    }
5126    const {
5127      dateRangeCuesAppended,
5128      id3Track
5129    } = this;
5130    const {
5131      dateRanges
5132    } = details;
5133    const ids = Object.keys(dateRanges);
5134    // Remove cues from track not found in details.dateRanges
5135    if (id3Track) {
5136      const idsToRemove = Object.keys(dateRangeCuesAppended).filter(id => !ids.includes(id));
5137      for (let i = idsToRemove.length; i--;) {
5138        const id = idsToRemove[i];
5139        Object.keys(dateRangeCuesAppended[id].cues).forEach(key => {
5140          id3Track.removeCue(dateRangeCuesAppended[id].cues[key]);
5141        });
5142        delete dateRangeCuesAppended[id];
5143      }
5144    }
5145    // Exit if the playlist does not have Date Ranges or does not have Program Date Time
5146    const lastFragment = details.fragments[details.fragments.length - 1];
5147    if (ids.length === 0 || !isFiniteNumber(lastFragment == null ? void 0 : lastFragment.programDateTime)) {
5148      return;
5149    }
5150    if (!this.id3Track) {
5151      this.id3Track = this.createTrack(this.media);
5152    }
5153    const dateTimeOffset = lastFragment.programDateTime / 1000 - lastFragment.start;
5154    const Cue = getCueClass();
5155    for (let i = 0; i < ids.length; i++) {
5156      const id = ids[i];
5157      const dateRange = dateRanges[id];
5158      const startTime = dateRangeDateToTimelineSeconds(dateRange.startDate, dateTimeOffset);
5159
5160      // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
5161      const appendedDateRangeCues = dateRangeCuesAppended[id];
5162      const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {};
5163      let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false;
5164      let endTime = MAX_CUE_ENDTIME;
5165      const endDate = dateRange.endDate;
5166      if (endDate) {
5167        endTime = dateRangeDateToTimelineSeconds(endDate, dateTimeOffset);
5168        durationKnown = true;
5169      } else if (dateRange.endOnNext && !durationKnown) {
5170        const nextDateRangeWithSameClass = ids.reduce((candidateDateRange, id) => {
5171          if (id !== dateRange.id) {
5172            const otherDateRange = dateRanges[id];
5173            if (otherDateRange.class === dateRange.class && otherDateRange.startDate > dateRange.startDate && (!candidateDateRange || dateRange.startDate < candidateDateRange.startDate)) {
5174              return otherDateRange;
5175            }
5176          }
5177          return candidateDateRange;
5178        }, null);
5179        if (nextDateRangeWithSameClass) {
5180          endTime = dateRangeDateToTimelineSeconds(nextDateRangeWithSameClass.startDate, dateTimeOffset);
5181          durationKnown = true;
5182        }
5183      }
5184
5185      // Create TextTrack Cues for each MetadataGroup Item (select DateRange attribute)
5186      // This is to emulate Safari HLS playback handling of DateRange tags
5187      const attributes = Object.keys(dateRange.attr);
5188      for (let j = 0; j < attributes.length; j++) {
5189        const key = attributes[j];
5190        if (!isDateRangeCueAttribute(key)) {
5191          continue;
5192        }
5193        const cue = cues[key];
5194        if (cue) {
5195          if (durationKnown && !appendedDateRangeCues.durationKnown) {
5196            cue.endTime = endTime;
5197          }
5198        } else if (Cue) {
5199          let data = dateRange.attr[key];
5200          if (isSCTE35Attribute(key)) {
5201            data = hexToArrayBuffer(data);
5202          }
5203          const _cue = createCueWithDataFields(Cue, startTime, endTime, {
5204            key,
5205            data
5206          }, MetadataSchema.dateRange);
5207          if (_cue) {
5208            _cue.id = id;
5209            this.id3Track.addCue(_cue);
5210            cues[key] = _cue;
5211          }
5212        }
5213      }
5214
5215      // Keep track of processed DateRanges by ID for updating cues with new DateRange tag attributes
5216      dateRangeCuesAppended[id] = {
5217        cues,
5218        dateRange,
5219        durationKnown
5220      };
5221    }
5222  }
5223}
5224
5225class LatencyController {
5226  constructor(hls) {
5227    this.hls = void 0;
5228    this.config = void 0;
5229    this.media = null;
5230    this.levelDetails = null;
5231    this.currentTime = 0;
5232    this.stallCount = 0;
5233    this._latency = null;
5234    this.timeupdateHandler = () => this.timeupdate();
5235    this.hls = hls;
5236    this.config = hls.config;
5237    this.registerListeners();
5238  }
5239  get latency() {
5240    return this._latency || 0;
5241  }
5242  get maxLatency() {
5243    const {
5244      config,
5245      levelDetails
5246    } = this;
5247    if (config.liveMaxLatencyDuration !== undefined) {
5248      return config.liveMaxLatencyDuration;
5249    }
5250    return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0;
5251  }
5252  get targetLatency() {
5253    const {
5254      levelDetails
5255    } = this;
5256    if (levelDetails === null) {
5257      return null;
5258    }
5259    const {
5260      holdBack,
5261      partHoldBack,
5262      targetduration
5263    } = levelDetails;
5264    const {
5265      liveSyncDuration,
5266      liveSyncDurationCount,
5267      lowLatencyMode
5268    } = this.config;
5269    const userConfig = this.hls.userConfig;
5270    let targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack;
5271    if (userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) {
5272      targetLatency = liveSyncDuration !== undefined ? liveSyncDuration : liveSyncDurationCount * targetduration;
5273    }
5274    const maxLiveSyncOnStallIncrease = targetduration;
5275    const liveSyncOnStallIncrease = 1.0;
5276    return targetLatency + Math.min(this.stallCount * liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease);
5277  }
5278  get liveSyncPosition() {
5279    const liveEdge = this.estimateLiveEdge();
5280    const targetLatency = this.targetLatency;
5281    const levelDetails = this.levelDetails;
5282    if (liveEdge === null || targetLatency === null || levelDetails === null) {
5283      return null;
5284    }
5285    const edge = levelDetails.edge;
5286    const syncPosition = liveEdge - targetLatency - this.edgeStalled;
5287    const min = edge - levelDetails.totalduration;
5288    const max = edge - (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration);
5289    return Math.min(Math.max(min, syncPosition), max);
5290  }
5291  get drift() {
5292    const {
5293      levelDetails
5294    } = this;
5295    if (levelDetails === null) {
5296      return 1;
5297    }
5298    return levelDetails.drift;
5299  }
5300  get edgeStalled() {
5301    const {
5302      levelDetails
5303    } = this;
5304    if (levelDetails === null) {
5305      return 0;
5306    }
5307    const maxLevelUpdateAge = (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3;
5308    return Math.max(levelDetails.age - maxLevelUpdateAge, 0);
5309  }
5310  get forwardBufferLength() {
5311    const {
5312      media,
5313      levelDetails
5314    } = this;
5315    if (!media || !levelDetails) {
5316      return 0;
5317    }
5318    const bufferedRanges = media.buffered.length;
5319    return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime;
5320  }
5321  destroy() {
5322    this.unregisterListeners();
5323    this.onMediaDetaching();
5324    this.levelDetails = null;
5325    // @ts-ignore
5326    this.hls = this.timeupdateHandler = null;
5327  }
5328  registerListeners() {
vendor: 8,580 bytes, lines 5329-5616
5329    this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
5330    this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
5331    this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
5332    this.hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
5333    this.hls.on(Events.ERROR, this.onError, this);
5334  }
5335  unregisterListeners() {
5336    this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
5337    this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
5338    this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
5339    this.hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
5340    this.hls.off(Events.ERROR, this.onError, this);
5341  }
5342  onMediaAttached(event, data) {
5343    this.media = data.media;
5344    this.media.addEventListener('timeupdate', this.timeupdateHandler);
5345  }
5346  onMediaDetaching() {
5347    if (this.media) {
5348      this.media.removeEventListener('timeupdate', this.timeupdateHandler);
5349      this.media = null;
5350    }
5351  }
5352  onManifestLoading() {
5353    this.levelDetails = null;
5354    this._latency = null;
5355    this.stallCount = 0;
5356  }
5357  onLevelUpdated(event, {
5358    details
5359  }) {
5360    this.levelDetails = details;
5361    if (details.advanced) {
5362      this.timeupdate();
5363    }
5364    if (!details.live && this.media) {
5365      this.media.removeEventListener('timeupdate', this.timeupdateHandler);
5366    }
5367  }
5368  onError(event, data) {
5369    var _this$levelDetails;
5370    if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) {
5371      return;
5372    }
5373    this.stallCount++;
5374    if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
5375      logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
5376    }
5377  }
5378  timeupdate() {
5379    const {
5380      media,
5381      levelDetails
5382    } = this;
5383    if (!media || !levelDetails) {
5384      return;
5385    }
5386    this.currentTime = media.currentTime;
5387    const latency = this.computeLatency();
5388    if (latency === null) {
5389      return;
5390    }
5391    this._latency = latency;
5392
5393    // Adapt playbackRate to meet target latency in low-latency mode
5394    const {
5395      lowLatencyMode,
5396      maxLiveSyncPlaybackRate
5397    } = this.config;
5398    if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
5399      return;
5400    }
5401    const targetLatency = this.targetLatency;
5402    if (targetLatency === null) {
5403      return;
5404    }
5405    const distanceFromTarget = latency - targetLatency;
5406    // Only adjust playbackRate when within one target duration of targetLatency
5407    // and more than one second from under-buffering.
5408    // Playback further than one target duration from target can be considered DVR playback.
5409    const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
5410    const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
5411    if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
5412      const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
5413      const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
5414      media.playbackRate = Math.min(max, Math.max(1, rate));
5415    } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
5416      media.playbackRate = 1;
5417    }
5418  }
5419  estimateLiveEdge() {
5420    const {
5421      levelDetails
5422    } = this;
5423    if (levelDetails === null) {
5424      return null;
5425    }
5426    return levelDetails.edge + levelDetails.age;
5427  }
5428  computeLatency() {
5429    const liveEdge = this.estimateLiveEdge();
5430    if (liveEdge === null) {
5431      return null;
5432    }
5433    return liveEdge - this.currentTime;
5434  }
5435}
5436
5437const HdcpLevels = ['NONE', 'TYPE-0', 'TYPE-1', null];
5438function isHdcpLevel(value) {
5439  return HdcpLevels.indexOf(value) > -1;
5440}
5441const VideoRangeValues = ['SDR', 'PQ', 'HLG'];
5442function isVideoRange(value) {
5443  return !!value && VideoRangeValues.indexOf(value) > -1;
5444}
5445var HlsSkip = {
5446  No: "",
5447  Yes: "YES",
5448  v2: "v2"
5449};
5450function getSkipValue(details) {
5451  const {
5452    canSkipUntil,
5453    canSkipDateRanges,
5454    age
5455  } = details;
5456  // A Client SHOULD NOT request a Playlist Delta Update unless it already
5457  // has a version of the Playlist that is no older than one-half of the Skip Boundary.
5458  // @see: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.3.7
5459  const playlistRecentEnough = age < canSkipUntil / 2;
5460  if (canSkipUntil && playlistRecentEnough) {
5461    if (canSkipDateRanges) {
5462      return HlsSkip.v2;
5463    }
5464    return HlsSkip.Yes;
5465  }
5466  return HlsSkip.No;
5467}
5468class HlsUrlParameters {
5469  constructor(msn, part, skip) {
5470    this.msn = void 0;
5471    this.part = void 0;
5472    this.skip = void 0;
5473    this.msn = msn;
5474    this.part = part;
5475    this.skip = skip;
5476  }
5477  addDirectives(uri) {
5478    const url = new self.URL(uri);
5479    if (this.msn !== undefined) {
5480      url.searchParams.set('_HLS_msn', this.msn.toString());
5481    }
5482    if (this.part !== undefined) {
5483      url.searchParams.set('_HLS_part', this.part.toString());
5484    }
5485    if (this.skip) {
5486      url.searchParams.set('_HLS_skip', this.skip);
5487    }
5488    return url.href;
5489  }
5490}
5491class Level {
5492  constructor(data) {
5493    this._attrs = void 0;
5494    this.audioCodec = void 0;
5495    this.bitrate = void 0;
5496    this.codecSet = void 0;
5497    this.url = void 0;
5498    this.frameRate = void 0;
5499    this.height = void 0;
5500    this.id = void 0;
5501    this.name = void 0;
5502    this.videoCodec = void 0;
5503    this.width = void 0;
5504    this.details = void 0;
5505    this.fragmentError = 0;
5506    this.loadError = 0;
5507    this.loaded = void 0;
5508    this.realBitrate = 0;
5509    this.supportedPromise = void 0;
5510    this.supportedResult = void 0;
5511    this._avgBitrate = 0;
5512    this._audioGroups = void 0;
5513    this._subtitleGroups = void 0;
5514    // Deprecated (retained for backwards compatibility)
5515    this._urlId = 0;
5516    this.url = [data.url];
5517    this._attrs = [data.attrs];
5518    this.bitrate = data.bitrate;
5519    if (data.details) {
5520      this.details = data.details;
5521    }
5522    this.id = data.id || 0;
5523    this.name = data.name;
5524    this.width = data.width || 0;
5525    this.height = data.height || 0;
5526    this.frameRate = data.attrs.optionalFloat('FRAME-RATE', 0);
5527    this._avgBitrate = data.attrs.decimalInteger('AVERAGE-BANDWIDTH');
5528    this.audioCodec = data.audioCodec;
5529    this.videoCodec = data.videoCodec;
5530    this.codecSet = [data.videoCodec, data.audioCodec].filter(c => !!c).map(s => s.substring(0, 4)).join(',');
5531    this.addGroupId('audio', data.attrs.AUDIO);
5532    this.addGroupId('text', data.attrs.SUBTITLES);
5533  }
5534  get maxBitrate() {
5535    return Math.max(this.realBitrate, this.bitrate);
5536  }
5537  get averageBitrate() {
5538    return this._avgBitrate || this.realBitrate || this.bitrate;
5539  }
5540  get attrs() {
5541    return this._attrs[0];
5542  }
5543  get codecs() {
5544    return this.attrs.CODECS || '';
5545  }
5546  get pathwayId() {
5547    return this.attrs['PATHWAY-ID'] || '.';
5548  }
5549  get videoRange() {
5550    return this.attrs['VIDEO-RANGE'] || 'SDR';
5551  }
5552  get score() {
5553    return this.attrs.optionalFloat('SCORE', 0);
5554  }
5555  get uri() {
5556    return this.url[0] || '';
5557  }
5558  hasAudioGroup(groupId) {
5559    return hasGroup(this._audioGroups, groupId);
5560  }
5561  hasSubtitleGroup(groupId) {
5562    return hasGroup(this._subtitleGroups, groupId);
5563  }
5564  get audioGroups() {
5565    return this._audioGroups;
5566  }
5567  get subtitleGroups() {
5568    return this._subtitleGroups;
5569  }
5570  addGroupId(type, groupId) {
5571    if (!groupId) {
5572      return;
5573    }
5574    if (type === 'audio') {
5575      let audioGroups = this._audioGroups;
5576      if (!audioGroups) {
5577        audioGroups = this._audioGroups = [];
5578      }
5579      if (audioGroups.indexOf(groupId) === -1) {
5580        audioGroups.push(groupId);
5581      }
5582    } else if (type === 'text') {
5583      let subtitleGroups = this._subtitleGroups;
5584      if (!subtitleGroups) {
5585        subtitleGroups = this._subtitleGroups = [];
5586      }
5587      if (subtitleGroups.indexOf(groupId) === -1) {
5588        subtitleGroups.push(groupId);
5589      }
5590    }
5591  }
5592
5593  // Deprecated methods (retained for backwards compatibility)
5594  get urlId() {
5595    return 0;
5596  }
5597  set urlId(value) {}
5598  get audioGroupIds() {
5599    return this.audioGroups ? [this.audioGroupId] : undefined;
5600  }
5601  get textGroupIds() {
5602    return this.subtitleGroups ? [this.textGroupId] : undefined;
5603  }
5604  get audioGroupId() {
5605    var _this$audioGroups;
5606    return (_this$audioGroups = this.audioGroups) == null ? void 0 : _this$audioGroups[0];
5607  }
5608  get textGroupId() {
5609    var _this$subtitleGroups;
5610    return (_this$subtitleGroups = this.subtitleGroups) == null ? void 0 : _this$subtitleGroups[0];
5611  }
5612  addFallback() {}
5613}
5614function hasGroup(groups, groupId) {
5615  if (!groupId || !groups) {
5616    return false;
5617  }
5618  return groups.indexOf(groupId) !== -1;
5619}
5620
5621function updateFromToPTS(fragFrom, fragTo) {
5622  const fragToPTS = fragTo.startPTS;
5623  // if we know startPTS[toIdx]
5624  if (isFiniteNumber(fragToPTS)) {
5625    // update fragment duration.
5626    // it helps to fix drifts between playlist reported duration and fragment real duration
5627    let duration = 0;
5628    let frag;
5629    if (fragTo.sn > fragFrom.sn) {
5630      duration = fragToPTS - fragFrom.start;
5631      frag = fragFrom;
5632    } else {
5633      duration = fragFrom.start - fragToPTS;
5634      frag = fragTo;
5635    }
5636    if (frag.duration !== duration) {
5637      frag.duration = duration;
5638    }
5639    // we dont know startPTS[toIdx]
5640  } else if (fragTo.sn > fragFrom.sn) {
5641    const contiguous = fragFrom.cc === fragTo.cc;
5642    // TODO: With part-loading end/durations we need to confirm the whole fragment is loaded before using (or setting) minEndPTS
5643    if (contiguous && fragFrom.minEndPTS) {
5644      fragTo.start = fragFrom.start + (fragFrom.minEndPTS - fragFrom.start);
5645    } else {
5646      fragTo.start = fragFrom.start + fragFrom.duration;
5647    }
5648  } else {
5649    fragTo.start = Math.max(fragFrom.start - fragTo.duration, 0);
5650  }
5651}
5652function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS) {
5653  const parsedMediaDuration = endPTS - startPTS;
5654  if (parsedMediaDuration <= 0) {
5655    logger.warn('Fragment should have a positive duration', frag);
5656    endPTS = startPTS + frag.duration;
5657    endDTS = startDTS + frag.duration;
5658  }
5659  let maxStartPTS = startPTS;
5660  let minEndPTS = endPTS;
5661  const fragStartPts = frag.startPTS;
5662  const fragEndPts = frag.endPTS;
5663  if (isFiniteNumber(fragStartPts)) {
5664    // delta PTS between audio and video
5665    const deltaPTS = Math.abs(fragStartPts - startPTS);
5666    if (!isFiniteNumber(frag.deltaPTS)) {
5667      frag.deltaPTS = deltaPTS;
5668    } else {
5669      frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS);
5670    }
5671    maxStartPTS = Math.max(startPTS, fragStartPts);
5672    startPTS = Math.min(startPTS, fragStartPts);
5673    startDTS = Math.min(startDTS, frag.startDTS);
5674    minEndPTS = Math.min(endPTS, fragEndPts);
5675    endPTS = Math.max(endPTS, fragEndPts);
5676    endDTS = Math.max(endDTS, frag.endDTS);
5677  }
5678  const drift = startPTS - frag.start;
5679  if (frag.start !== 0) {
5680    frag.start = startPTS;
5681  }
5682  frag.duration = endPTS - frag.start;
5683  frag.startPTS = startPTS;
5684  frag.maxStartPTS = maxStartPTS;
5685  frag.startDTS = startDTS;
5686  frag.endPTS = endPTS;
5687  frag.minEndPTS = minEndPTS;
5688  frag.endDTS = endDTS;
5689  const sn = frag.sn; // 'initSegment'
5690  // exit if sn out of range
5691  if (!details || sn < details.startSN || sn > details.endSN) {
5692    return 0;
5693  }
5694  let i;
5695  const fragIdx = sn - details.startSN;
5696  const fragments = details.fragments;
5697  // update frag reference in fragments array
5698  // rationale is that fragments array might not contain this frag object.
5699  // this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS()
5700  // if we don't update frag, we won't be able to propagate PTS info on the playlist
5701  // resulting in invalid sliding computation
5702  fragments[fragIdx] = frag;
5703  // adjust fragment PTS/duration from seqnum-1 to frag 0
5704  for (i = fragIdx; i > 0; i--) {
5705    updateFromToPTS(fragments[i], fragments[i - 1]);
5706  }
5707
5708  // adjust fragment PTS/duration from seqnum to last frag
5709  for (i = fragIdx; i < fragments.length - 1; i++) {
5710    updateFromToPTS(fragments[i], fragments[i + 1]);
5711  }
5712  if (details.fragmentHint) {
5713    updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint);
5714  }
5715  details.PTSKnown = details.alignedSliding = true;
5716  return drift;
5717}
5718function mergeDetails(oldDetails, newDetails) {
5719  // Track the last initSegment processed. Initialize it to the last one on the timeline.
5720  let currentInitSegment = null;
5721  const oldFragments = oldDetails.fragments;
5722  for (let i = oldFragments.length - 1; i >= 0; i--) {
5723    const oldInit = oldFragments[i].initSegment;
5724    if (oldInit) {
5725      currentInitSegment = oldInit;
5726      break;
5727    }
5728  }
5729  if (oldDetails.fragmentHint) {
5730    // prevent PTS and duration from being adjusted on the next hint
5731    delete oldDetails.fragmentHint.endPTS;
5732  }
5733  // check if old/new playlists have fragments in common
5734  // loop through overlapping SN and update startPTS , cc, and duration if any foun
5734d
5735  let ccOffset = 0;
5736  let PTSFrag;
5737  mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => {
5738    if (oldFrag.relurl) {
5739      // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts.
5740      // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end
5741      // of the playlist.
5742      ccOffset = oldFrag.cc - newFrag.cc;
5743    }
5744    if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
5745      newFrag.start = newFrag.startPTS = oldFrag.startPTS;
5746      newFrag.startDTS = oldFrag.startDTS;
5747      newFrag.maxStartPTS = oldFrag.maxStartPTS;
5748      newFrag.endPTS = oldFrag.endPTS;
5749      newFrag.endDTS = oldFrag.endDTS;
5750      newFrag.minEndPTS = oldFrag.minEndPTS;
5751      newFrag.duration = oldFrag.endPTS - oldFrag.startPTS;
5752      if (newFrag.duration) {
5753        PTSFrag = newFrag;
5754      }
5755
5756      // PTS is known when any segment has startPTS and endPTS
5757      newDetails.PTSKnown = newDetails.alignedSliding = true;
5758    }
5759    newFrag.elementaryStreams = oldFrag.elementaryStreams;
5760    newFrag.loader = oldFrag.loader;
5761    newFrag.stats = oldFrag.stats;
5762    if (oldFrag.initSegment) {
5763      newFrag.initSegment = oldFrag.initSegment;
5764      currentInitSegment = oldFrag.initSegment;
5765    }
5766  });
5767  if (currentInitSegment) {
5768    const fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
5769    fragmentsToCheck.forEach(frag => {
5770      var _currentInitSegment;
5771      if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
5772        frag.initSegment = currentInitSegment;
5773      }
5774    });
5775  }
5776  if (newDetails.skippedSegments) {
5777    newDetails.deltaUpdateFailed = newDetails.fragments.some(frag => !frag);
5778    if (newDetails.deltaUpdateFailed) {
5779      logger.warn('[level-helper] Previous playlist missing segments ski
5779pped in delta playlist');
5780      for (let i = newDetails.skippedSegments; i--;) {
5781        newDetails.fragments.shift();
5782      }
5783      newDetails.startSN = newDetails.fragments[0].sn;
5784      newDetails.startCC = newDetails.fragments[0].cc;
5785    } else if (newDetails.canSkipDateRanges) {
5786      newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges);
5787    }
5788  }
5789  const newFragments = newDetails.fragments;
5790  if (ccOffset) {
5791    logger.warn('discontinuity sliding from playlist, take drift into account');
5792    for (let i = 0; i < newFragments.length; i++) {
5793      newFragments[i].cc += ccOffset;
5794    }
5795  }
5796  if (newDetails.skippedSegments) {
5797    newDetails.startCC = newDetails.fragments[0].cc;
5798  }
5799
5800  // Merge parts
5801  mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => {
5802    newPart.elementaryStreams = oldPart.elementaryStreams;
5803    newPart.stats = oldPart.stats;
5804  });
5805
5806  // if at least one fragment contains PTS info, recompute PTS information for all fragments
5807  if (PTSFrag) {
5808    updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS);
5809  } else {
5810    // ensure that delta is within oldFragments range
5811    // also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61])
5812    // in that case we also need to adjust start offset of all fragments
5813    adjustSliding(oldDetails, newDetails);
5814  }
5815  if (newFragments.length) {
5816    newDetails.totalduration = newDetails.edge - newFragments[0].start;
5817  }
5818  newDetails.driftStartTime = oldDetails.driftStartTime;
5819  newDetails.driftStart = oldDetails.driftStart;
5820  const advancedDateTime = newDetails.advancedDateTime;
5821  if (newDetails.advanced && advancedDateTime) {
5822    const edge = newDetails.edge;
5823    if (!newDetails.driftStart) {
5824      newDetails.driftStartTime = advancedDateTime;
5825      newDetails.driftStart = edge;
5826    }
5827    newDetails.driftEndTime = advancedDateTime;
5828    newDetails.driftEnd = edge;
5829  } else {
5830    newDetails.driftEndTime = oldDetails.driftEndTime;
5831    newDetails.driftEnd = oldDetails.driftEnd;
5832    newDetails.advancedDateTime = oldDetails.advancedDateTime;
5833  }
5834}
5835function mergeDateRanges(oldDateRanges, deltaDateRanges, recentlyRemovedDateranges) {
5836  const dateRanges = _extends({}, oldDateRanges);
5837  if (recentlyRemovedDateranges) {
5838    recentlyRemovedDateranges.forEach(id => {
5839      delete dateRanges[id];
5840    });
5841  }
5842  Object.keys(deltaDateRanges).forEach(id => {
5843    const dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]);
5844    if (dateRange.isValid) {
5845      dateRanges[id] = dateRange;
5846    } else {
5847      logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(deltaDateRanges[id].attr)}"`);
5848    }
5849  });
5850  return dateRanges;
5851}
5852function mapPartIntersection(oldParts, newParts, intersectionFn) {
5853  if (oldParts && newParts) {
5854    let delta = 0;
5855    for (let i = 0, len = oldParts.length; i <= len; i++) {
5856      const oldPart = oldParts[i];
5857      const newPart = newParts[i + delta];
5858      if (oldPart && newPart && oldPart.index === newPart.index && oldPart.fragment.sn === newPart.fragment.sn) {
5859        intersectionFn(oldPart, newPart);
5860      } else {
5861        delta--;
5862      }
5863    }
5864  }
5865}
5866function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
5867  const skippedSegments = newDetails.skippedSegments;
5868  const start = Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN;
5869  const end = (oldDetails.fragmentHint ? 1 : 0) + (skippedSegments ? newDetails.endSN : Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN;
5870  const delta = newDetails.startSN - oldDetails.startSN;
5871  const newFrags = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
5872  const oldFrags = oldDetails.fragmentHint ? oldDetails.fragments.concat(oldDetails.fragmentHint) : oldDetails.fragments;
5873  for (let i = start; i <= end; i++) {
5874    const oldFrag = oldFrags[delta + i];
5875    let newFrag = newFrags[i];
5876    if (skippedSegments && !newFrag && i < skippedSegments) {
5877      // Fill in skipped segments in delta playlist
5878      newFrag = newDetails.fragments[i] = oldFrag;
5879    }
5880    if (oldFrag && newFrag) {
5881      intersectionFn(oldFrag, newFrag);
5882    }
5883  }
5884}
5885function adjustSliding(oldDetails, newDetails) {
5886  const delta = newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN;
5887  const oldFragments = oldDetails.fragments;
5888  if (delta < 0 || delta >= oldFragments.length) {
5889    return;
5890  }
5891  addSliding(newDetails, oldFragments[delta].start);
5892}
5893function addSliding(details, start) {
5894  if (start) {
5895    const fragments = details.fragments;
5896    for (let i = details.skippedSegments; i < fragments.length; i++) {
5897      fragments[i].start += start;
5898    }
5899    if (details.fragmentHint) {
5900      details.fragmentHint.start += start;
5901    }
5902  }
5903}
5904function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) {
5905  let reloadInterval = 1000 * newDetails.targetduration;
5906  if (newDetails.updated) {
5907    // Use last segment duration when shorter than target duration and near live edge
5908    const fragments = newDetails.fragments;
5909    const liveEdgeMaxTargetDurations = 4;
5910    if (fragments.length && reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs) {
5911      const lastSegmentDuration = fragments[fragments.length - 1].duration * 1000;
5912      if (lastSegmentDuration < reloadInterval) {
5913        reloadInterval = lastSegmentDuration;
5914      }
5915    }
5916  } else {
5917    // estimate = 'miss half average';
5918    // follow HLS Spec, If the client reloads a Playlist file and finds that it has not
5919    // changed then it MUST wait for a period of one-half the target
5920    // duration before retrying.
5921    reloadInterval /= 2;
5922  }
5923  return Math.round(reloadInterval);
5924}
5925function getFragmentWithSN(level, sn, fragCurrent) {
5926  if (!(level != null && level.details)) {
5927    return null;
5928  }
5929  const levelDetails = level.details;
5930  let fragment = levelDetails.fragments[sn - levelDetails.startSN];
5931  if (fragment) {
5932    return fragment;
5933  }
5934  fragment = levelDetails.fragmentHint;
5935  if (fragment && fragment.sn === sn) {
5936    return fragment;
5937  }
5938  if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) {
5939    return fragCurrent;
5940  }
5941  return null;
5942}
5943function getPartWith(level, sn, partIndex) {
5944  var _level$details;
5945  if (!(level != null && level.details)) {
5946    return null;
5947  }
5948  return findPart((_level$details = level.details) == null ? void 0 : _level$details.partList, sn, partIndex);
5949}
5950function findPart(partList, sn, partIndex) {
5951  if (partList) {
5952    for (let i = partList.length; i--;) {
5953      const part = partList[i];
5954      if (part.index === partIndex && part.fragment.sn === sn) {
5955        return part;
5956      }
5957    }
5958  }
5959  return null;
5960}
5961function reassignFragmentLevelIndexes(levels) {
5962  levels.forEach((level, index) => {
5963    const {
5964      details
5965    } = level;
5966    if (details != null && details.fragments) {
5967      details.fragments.forEach(fragment => {
5968        fragment.level = index;
5969      });
5970    }
5971  });
5972}
5973
5974function isTimeoutError(error) {
5975  switch (error.details) {
5976    case ErrorDetails.FRAG_LOAD_TIMEOUT:
5977    case ErrorDetails.KEY_LOAD_TIMEOUT:
5978    case ErrorDetails.LEVEL_LOAD_TIMEOUT:
5979    case ErrorDetails.MANIFEST_LOAD_TIMEOUT:
5980      return true;
5981  }
5982  return false;
5983}
5984function getRetryConfig(loadPolicy, error) {
5985  const isTimeout = isTimeoutError(error);
5986  return loadPolicy.default[`${isTimeout ? 'timeout' : 'error'}Retry`];
5987}
5988function getRetryDelay(retryConfig, retryCount) {
5989  // exponential backoff capped to max retry delay
5990  const backoffFactor = retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount);
5991  return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs);
5992}
5993function getLoaderConfigWithoutReties(loderConfig) {
5994  return _objectSpread2(_objectSpread2({}, loderConfig), {
5995    errorRetry: null,
5996    timeoutRetry: null
5997  });
5998}
5999function shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse) {
6000  if (!retryConfig) {
6001    return false;
6002  }
6003  const httpStatus = loaderResponse == null ? void 0 : loaderResponse.code;
6004  const retry = retryCount < retryConfig.maxNumRetry && (retryForHttpStatus(httpStatus) || !!isTimeout);
6005  return retryConfig.shouldRetry ? retryConfig.shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse, retry) : retry;
6006}
6007function retryForHttpStatus(httpStatus) {
6008  // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error)
6009  return httpStatus === 0 && navigator.onLine === false || !!httpStatus && (httpStatus < 400 || httpStatus > 499);
6010}
6011
6012const BinarySearch = {
6013  /**
6014   * Searches for an item in an array which matches a certain condition.
6015   * This requires the condition to only match one item in the array,
6016   * and for the array to be ordered.
6017   *
6018   * @param list The array to search.
6019   * @param comparisonFn
6020   *      Called and provided a candidate item as the first argument.
6021   *      Should return:
6022   *          > -1 if the item should be located at a lower index than the provided item.
6023   *          > 1 if the item should be located at a higher index than the provided item.
6024   *          > 0 if the item is the item you're looking for.
6025   *
6026   * @returns the object if found, otherwise returns null
6027   */
6028  search: function (list, comparisonFn) {
6029    let minIndex = 0;
6030    let maxIndex = list.length - 1;
6031    let currentIndex = null;
6032    let currentElement = null;
6033    while (minIndex <= maxIndex) {
6034      currentIndex = (minIndex + maxIndex) / 2 | 0;
6035      currentElement = list[currentIndex];
6036      const comparisonResult = comparisonFn(currentElement);
6037      if (comparisonResult > 0) {
6038        minIndex = currentIndex + 1;
6039      } else if (comparisonResult < 0) {
6040        maxIndex = currentIndex - 1;
6041      } else {
6042        return currentElement;
6043      }
6044    }
6045    return null;
6046  }
6047};
6048
6049/**
6050 * Returns first fragment whose endPdt value exceeds the given PDT, or null.
6051 * @param fragments - The array of candidate fragments
6052 * @param PDTValue - The PDT value which must be exceeded
6053 * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
6054 */
6055function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) {
6056  if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !isFiniteNumber(PDTValue)) {
6057    return null;
6058  }
6059
6060  // if less than start
6061  const startPDT = fragments[0].programDateTime;
6062  if (PDTValue < (startPDT || 0)) {
6063    return null;
6064  }
6065  const endPDT = fragments[fragments.length - 1].endProgramDateTime;
6066  if (PDTValue >= (endPDT || 0)) {
6067    return null;
6068  }
6069  maxFragLookUpTolerance = maxFragLookUpTolerance || 0;
6070  for (let seg = 0; seg < fragments.length; ++seg) {
6071    const frag = fragments[seg];
6072    if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
6073      return frag;
6074    }
6075  }
6076  return null;
6077}
6078
6079/**
6080 * Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer.
6081 * This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus
6082 * breaking any traps which would cause the same fragment to be continuously selected within a small range.
6083 * @param fragPrevious - The last frag successfully appended
6084 * @param fragments - The array of candidate fragments
6085 * @param bufferEnd - The end of the contiguous buffered range the playhead is currently within
6086 * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
6087 * @returns a matching fragment or null
6088 */
6089function findFragmentByPTS(fragPrevious, fragments, bufferEnd = 0, maxFragLookUpTolerance = 0, nextFragLookupTolerance = 0.005) {
6090  let fragNext = null;
6091  if (fragPrevious) {
6092    fragNext = fragments[fragPrevious.sn - fragments[0].sn + 1] || null;
6093    // check for buffer-end rounding error
6094    const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
6095    if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
6096      bufferEnd += 0.0000015;
6097    }
6098  } else if (bufferEnd === 0 && fragments[0].start === 0) {
6099    fragNext = fragments[0];
6100  }
6101  // Prefer the next fragment if it's within tolerance
6102  if (fragNext && ((!fragPrevious || fragPrevious.level === fragNext.level) && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0 || fragmentWithinFastStartSwitch(fragNext, fragPrevious, Math.min(nextFragLookupTolerance, maxFragLookUpTolerance)))) {
6103    return fragNext;
6104  }
6105  // We might be seeking past the tolerance so find the best match
6106  const foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));
6107  if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) {
6108    return foundFragment;
6109  }
6110  // If no match was found return the next fragment after fragPrevious, or null
6111  return fragNext;
6112}
6113function fragmentWithinFastStartSwitch(fragNext, fragPrevious, nextFragLookupTolerance) {
6114  if (fragPrevious && fragPrevious.start === 0 && fragPrevious.level < fragNext.level && (fragPrevious.endPTS || 0) > 0) {
6115    const firstDuration = fragPrevious.tagList.reduce((duration, tag) => {
6116      if (tag[0] === 'INF') {
6117        duration += parseFloat(tag[1]);
6118      }
6119      return duration;
6120    }, nextFragLookupTolerance);
6121    return fragNext.start <= firstDuration;
6122  }
6123  return false;
6124}
6125
6126/**
6127 * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
6128 * @param candidate - The fragment to test
6129 * @param bufferEnd - The end of the current buffered range the playhead is currently within
6130 * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous
6131 * @returns 0 if it matches, 1 if too low, -1 if too high
6132 */
6133function fragmentWithinToleranceTest(bufferEnd = 0, maxFragLookUpTolerance = 0, candidate) {
6134  // eagerly accept an accurate match (no tolerance)
6135  if (candidate.start <= bufferEnd && candidate.start + candidate.duration > bufferEnd) {
6136    return 0;
6137  }
6138  // offset should be within fragment boundary - config.maxFragLookUpTolerance
6139  // this is to cope with situations like
6140  // bufferEnd = 9.991
6141  // frag[Ø] : [0,10]
6142  // frag[1] : [10,20]
6143  // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
6144  //              frag start               frag start+duration
6145  //                  |-----------------------------|
6146  //              <--->                         <--->
6147  //  ...--------><-----------------------------><---------....
6148  // previous frag         matching fragment         next frag
6149  //  return -1             return 0                 return 1
6150  // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
6151  // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments
6152  const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0));
6153  if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) {
6154    return 1;
6155  } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) {
6156    // if maxFragLookUpTolerance will have negative value then don't return -1 for first element
6157    return -1;
6158  }
6159  return 0;
6160}
6161
6162/**
6163 * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions.
6164 * This function tests the candidate's program date time values, as represented in Unix time
6165 * @param candidate - The fragment to test
6166 * @param pdtBufferEnd - The Unix time representing the end of the current buffered range
6167 * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous
6168 * @returns true if contiguous, false otherwise
6169 */
6170function pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate) {
6171  const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000;
6172
6173  // endProgramDateTime can be null, default to zero
6174  const endProgramDateTime = candidate.endProgramDateTime || 0;
6175  return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
6176}
6177function findFragWithCC(fragments, cc) {
6178  return BinarySearch.search(fragments, candidate => {
6179    if (candidate.cc < cc) {
6180      return 1;
6181    } else if (candidate.cc > cc) {
6182      return -1;
6183    } else {
6184      return 0;
6185    }
6186  });
6187}
6188
6189var NetworkErrorAction = {
6190  DoNothing: 0,
6191  SendAlternateToPenaltyBox: 2,
6192  RemoveAlternatePermanently: 3,
6193  RetryRequest: 5
6194};
6195var ErrorActionFlags = {
6196  None: 0,
6197  MoveAllAlternatesMatchingHost: 1,
6198  MoveAllAlternatesMatchingHDCP: 2}; // Reserved for future use
6199class ErrorController {
6200  constructor(hls) {
6201    this.hls = void 0;
6202    this.playlistError = 0;
6203    this.penalizedRenditions = {};
6204    this.log = void 0;
6205    this.warn = void 0;
6206    this.error = void 0;
6207    this.hls = hls;
6208    this.log = logger.log.bind(logger, `[info]:`);
6209    this.warn = logger.warn.bind(logger, `[warning]:`);
6210    this.error = logger.error.bind(logger, `[error]:`);
6211    this.registerListeners();
6212  }
6213  registerListeners() {
6214    const hls = this.hls;
6215    hls.on(Events.ERROR, this.onError, this);
6216    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
6217    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
6218  }
6219  unregisterListeners() {
6220    const hls = this.hls;
6221    if (!hls) {
6222      return;
6223    }
6224    hls.off(Events.ERROR, this.onError, this);
6225    hls.off(Events.ERROR, this.onErrorOut, this);
6226    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
6227    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
6228  }
6229  destroy() {
6230    this.unregisterListeners();
6231    // @ts-ignore
6232    this.hls = null;
6233    this.penalizedRenditions = {};
6234  }
6235  startLoad(startPosition) {}
6236  stopLoad() {
6237    this.playlistError = 0;
6238  }
6239  getVariantLevelIndex(frag) {
6240    return (frag == null ? void 0 : frag.type) === PlaylistLevelType.MAIN ? frag.level : this.hls.loadLevel;
6241  }
6242  onManifestLoading() {
6243    this.playlistError = 0;
6244    this.penalizedRenditions = {};
6245  }
6246  onLevelUpdated() {
6247    this.playlistError = 0;
6248  }
6249  onError(event, data) {
6250    var _data$frag, _data$level;
6251    if (data.fatal) {
6252      return;
6253    }
6254    const hls = this.hls;
6255    const context = data.context;
6256    switch (data.details) {
6257      case ErrorDetails.FRAG_LOAD_ERROR:
6258      case ErrorDetails.FRAG_LOAD_TIMEOUT:
6259      case ErrorDetails.KEY_LOAD_ERROR:
6260      case ErrorDetails.KEY_LOAD_TIMEOUT:
6261        data.errorAction = this.getFragRetryOrSwitchAction(data);
6262        return;
6263      case ErrorDetails.FRAG_PARSING_ERROR:
6264        // ignore empty segment errors marked as gap
6265        if ((_data$frag = data.frag) != null && _data$frag.gap) {
6266          data.errorAction = {
6267            action: NetworkErrorAction.DoNothing,
6268            flags: ErrorActionFlags.None
6269          };
6270          return;
6271        }
6272      // falls through
6273      case ErrorDetails.FRAG_GAP:
6274      case ErrorDetails.FRAG_DECRYPT_ERROR:
6275        {
6276          // Switch level if possible, otherwise allow retry count to reach max error retries
6277          data.errorAction = this.getFragRetryOrSwitchAction(data);
6278          data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
6279          return;
6280        }
6281      case ErrorDetails.LEVEL_EMPTY_ERROR:
6282      case ErrorDetails.LEVEL_PARSING_ERROR:
6283        {
6284          var _data$context, _data$context$levelDe;
6285          // Only retry when empty and live
6286          const levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel;
6287          if (data.details === ErrorDetails.LEVEL_EMPTY_ERROR && !!((_data$context = data.context) != null && (_data$context$levelDe = _data$context.levelDetails) != null && _data$context$levelDe.live)) {
6288            data.errorAction = this.getPlaylistRetryOrSwitchAction(data, levelIndex);
6289          } else {
6290            // Escalate to fatal if not retrying or switching
6291            data.levelRetry = false;
6292            data.errorAction = this.getLevelSwitchAction(data, levelIndex);
6293          }
6294        }
6295        return;
6296      case ErrorDetails.LEVEL_LOAD_ERROR:
6297      case ErrorDetails.LEVEL_LOAD_TIMEOUT:
6298        if (typeof (context == null ? void 0 : context.level) === 'number') {
6299          data.errorAction = this.getPlaylistRetryOrSwitchAction(data, context.level);
6300        }
6301        return;
6302      case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
6303      case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
6304      case ErrorDetails.SUBTITLE_LOAD_ERROR:
6305      case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:
6306        if (context) {
6307          const level = hls.levels[hls.loadLevel];
6308          if (level && (context.type === PlaylistContextType.AUDIO_TRACK && level.hasAudioGroup(context.groupId) || context.type === PlaylistContextType.SUBTITLE_TRACK && level.hasSubtitleGroup(context.groupId))) {
6309            // Perform Pathway switch or Redundant failover if possible for fastest recovery
6310            // otherwise allow playlist retry count to reach max error retries
6311            data.errorAction = this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel);
6312            data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
6313            data.errorAction.flags = ErrorActionFlags.MoveAllAlternatesMatchingHost;
6314            return;
6315          }
6316        }
6317        return;
6318      case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:
6319        {
6320          const level = hls.levels[hls.loadLevel];
6321          const restrictedHdcpLevel = level == null ? void 0 : level.attrs['HDCP-LEVEL'];
6322          if (restrictedHdcpLevel) {
6323            data.errorAction = {
6324              action: NetworkErrorAction.SendAlternateToPenaltyBox,
6325              flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP,
6326              hdcpLevel: restrictedHdcpLevel
6327            };
6328          } else {
6329            this.keySystemError(data);
6330          }
6331        }
6332        return;
6333      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
6334      case ErrorDetails.REMUX_ALLOC_ERROR:
6335      case ErrorDetails.BUFFER_APPEND_ERROR:
6336        data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel);
6337        return;
6338      case ErrorDetails.INTERNAL_EXCEPTION:
6339      case ErrorDetails.BUFFER_APPENDING_ERROR:
6340      case ErrorDetails.BUFFER_FULL_ERROR:
6341      case ErrorDetails.LEVEL_SWITCH_ERROR:
6342      case ErrorDetails.BUFFER_STALLED_ERROR:
6343      case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
6344      case ErrorDetails.BUFFER_NUDGE_ON_STALL:
6345        data.errorAction = {
6346          action: NetworkErrorAction.DoNothing,
6347          flags: ErrorActionFlags.None
6348        };
6349        return;
6350    }
6351    if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
6352      this.keySystemError(data);
6353    }
6354  }
6355  keySystemError(data) {
6356    const levelIndex = this.getVariantLevelIndex(data.frag);
6357    // Do not retry level. Escalate to fatal if switching levels fails.
6358    data.levelRetry = false;
6359    data.errorAction = this.getLevelSwitchAction(data, levelIndex);
6360  }
6361  getPlaylistRetryOrSwitchAction(data, levelIndex) {
6362    const hls = this.hls;
6363    const retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data);
6364    const retryCount = this.playlistError++;
6365    const retry = shouldRetry(retryConfig, retryCount, isTimeoutError(data), data.response);
6366    if (retry) {
6367      return {
6368        action: NetworkErrorAction.RetryRequest,
6369        flags: ErrorActionFlags.None,
6370        retryConfig,
6371        retryCount
6372      };
6373    }
6374    const errorAction = this.getLevelSwitchAction(data, levelIndex);
6375    if (retryConfig) {
6376      errorAction.retryConfig = retryConfig;
6377      errorAction.retryCount = retryCount;
6378    }
6379    return errorAction;
6380  }
6381  getFragRetryOrSwitchAction(data) {
6382    const hls = this.hls;
6383    // Share fragment error count accross media options (main, audio, subs)
6384    // This allows for level based rendition switching when media option assets fail
6385    const variantLevelIndex = this.getVariantLevelIndex(data.frag);
6386    const level = hls.levels[variantLevelIndex];
6387    const {
6388      fragLoadPolicy,
6389      keyLoadPolicy
6390    } = hls.config;
6391    const retryConfig = getRetryConfig(data.details.startsWith('key') ? keyLoadPolicy : fragLoadPolicy, data);
6392    const fragmentErrors = hls.levels.reduce((acc, level) => acc + level.fragmentError, 0);
6393    // Switch levels when out of retried or level index out of bounds
6394    if (level) {
6395      if (data.details !== ErrorDetails.FRAG_GAP) {
6396        level.fragmentError++;
6397      }
6398      const retry = shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), data.response);
6399      if (retry) {
6400        return {
6401          action: NetworkErrorAction.RetryRequest,
6402          flags: ErrorActionFlags.None,
6403          retryConfig,
6404          retryCount: fragmentErrors
6405        };
6406      }
6407    }
6408    // Reach max retry count, or Missing level reference
6409    // Switch to valid index
6410    const errorAction = this.getLevelSwitchAction(data, variantLevelIndex);
6411    // Add retry details to allow skipping of FRAG_PARSING_ERROR
6412    if (retryConfig) {
6413      errorAction.retryConfig = retryConfig;
6414      errorAction.retryCount = fragmentErrors;
6415    }
6416    return errorAction;
6417  }
6418  getLevelSwitchAction(data, levelIndex) {
6419    const hls = this.hls;
6420    if (levelIndex === null || levelIndex === undefined) {
6421      levelIndex = hls.loadLevel;
6422    }
6423    const level = this.hls.levels[levelIndex];
6424    if (level) {
6425      var _data$frag2, _data$context2;
6426      const errorDetails = data.details;
6427      level.loadError++;
6428      if (errorDetails === ErrorDetails.BUFFER_APPEND_ERROR) {
6429        level.fragmentError++;
6430      }
6431      // Search for next level to retry
6432      let nextLevel = -1;
6433      const {
6434        levels,
6435        loadLevel,
6436        minAutoLevel,
6437        maxAutoLevel
6438      } = hls;
6439      if (!hls.autoLevelEnabled) {
6440        hls.loadLevel = -1;
6441      }
6442      const fragErrorType = (_data$frag2 = data.frag) == null ? void 0 : _data$frag2.type;
6443      // Find alternate audio codec if available on audio codec error
6444      const isAudioCodecError = fragErrorType === PlaylistLevelType.AUDIO && errorDetails === ErrorDetails.FRAG_PARSING_ERROR || data.sourceBufferName === 'audio' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR);
6445      const findAudioCodecAlternate = isAudioCodecError && levels.some(({
6446        audioCodec
6447      }) => level.audioCodec !== audioCodec);
6448      // Find alternate video codec if available on video codec error
6449      const isVideoCodecError = data.sourceBufferName === 'video' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR);
6450      const findVideoCodecAlternate = isVideoCodecError && levels.some(({
6451        codecSet,
6452        audioCodec
6453      }) =>
vendor: 4,339 bytes, lines 6453-6542
6453 level.codecSet !== codecSet && level.audioCodec === audioCodec);
6454      const {
6455        type: playlistErrorType,
6456        groupId: playlistErrorGroupId
6457      } = (_data$context2 = data.context) != null ? _data$context2 : {};
6458      for (let i = levels.length; i--;) {
6459        const candidate = (i + loadLevel) % levels.length;
6460        if (candidate !== loadLevel && candidate >= minAutoLevel && candidate <= maxAutoLevel && levels[candidate].loadError === 0) {
6461          var _level$audioGroups, _level$subtitleGroups;
6462          const levelCandidate = levels[candidate];
6463          // Skip level switch if GAP tag is found in next level at same position
6464          if (errorDetails === ErrorDetails.FRAG_GAP && fragErrorType === PlaylistLevelType.MAIN && data.frag) {
6465            const levelDetails = levels[candidate].details;
6466            if (levelDetails) {
6467              const fragCandidate = findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start);
6468              if (fragCandidate != null && fragCandidate.gap) {
6469                continue;
6470              }
6471            }
6472          } else if (playlistErrorType === PlaylistContextType.AUDIO_TRACK && levelCandidate.hasAudioGroup(playlistErrorGroupId) || playlistErrorType === PlaylistContextType.SUBTITLE_TRACK && levelCandidate.hasSubtitleGroup(playlistErrorGroupId)) {
6473            // For audio/subs playlist errors find another group ID or fallthrough to redundant fail-over
6474            continue;
6475          } 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) {
6476            // For video/audio/subs frag errors find another group ID or fallthrough to redundant fail-over
6477            continue;
6478          }
6479          nextLevel = candidate;
6480          break;
6481        }
6482      }
6483      if (nextLevel > -1 && hls.loadLevel !== nextLevel) {
6484        data.levelRetry = true;
6485        this.playlistError = 0;
6486        return {
6487          action: NetworkErrorAction.SendAlternateToPenaltyBox,
6488          flags: ErrorActionFlags.None,
6489          nextAutoLevel: nextLevel
6490        };
6491      }
6492    }
6493    // No levels to switch / Manual level selection / Level not found
6494    // Resolve with Pathway switch, Redundant fail-over, or stay on lowest Level
6495    return {
6496      action: NetworkErrorAction.SendAlternateToPenaltyBox,
6497      flags: ErrorActionFlags.MoveAllAlternatesMatchingHost
6498    };
6499  }
6500  onErrorOut(event, data) {
6501    var _data$errorAction;
6502    switch ((_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.action) {
6503      case NetworkErrorAction.DoNothing:
6504        break;
6505      case NetworkErrorAction.SendAlternateToPenaltyBox:
6506        this.sendAlternateToPenaltyBox(data);
6507        if (!data.errorAction.resolved && data.details !== ErrorDetails.FRAG_GAP) {
6508          data.fatal = true;
6509        } else if (/MediaSource readyState: ended/.test(data.error.message)) {
6510          this.warn(`MediaSource ended after "${data.sourceBufferName}" sourceBuffer append error. Attempting to recover from media error.`);
6511          this.hls.recoverMediaError();
6512        }
6513        break;
6514    }
6515    if (data.fatal) {
6516      this.hls.stopLoad();
6517      return;
6518    }
6519  }
6520  sendAlternateToPenaltyBox(data) {
6521    const hls = this.hls;
6522    const errorAction = data.errorAction;
6523    if (!errorAction) {
6524      return;
6525    }
6526    const {
6527      flags,
6528      hdcpLevel,
6529      nextAutoLevel
6530    } = errorAction;
6531    switch (flags) {
6532      case ErrorActionFlags.None:
6533        this.switchLevel(data, nextAutoLevel);
6534        break;
6535      case ErrorActionFlags.MoveAllAlternatesMatchingHDCP:
6536        if (hdcpLevel) {
6537          hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(hdcpLevel) - 1];
6538          errorAction.resolved = true;
6539        }
6540        this.warn(`Restricting playback to HDCP-LEVEL of "${hls.maxHdcpLevel}" or lower`);
6541        break;
6542    }
6543    // If not resolved by previous actions try to switch to next level
6544    if (!errorAction.resolved) {
6545      this.switchLevel(data, nextAutoLevel);
6546    }
6547  }
6548  switchLevel(data, levelIndex) {
6549    if (levelIndex !== undefined && data.errorAction) {
6550      this.warn(`switching to level ${levelIndex} after ${data.details}`);
6551      this.hls.nextAutoLevel = levelIndex;
6552      data.errorAction.resolved = true;
6553      // Stream controller is responsible for this but won't switch on false start
6554      this.hls.nextLoadLevel = this.hls.nextAutoLevel;
6555    }
6556  }
6557}
6558
6559class BasePlaylistController {
6560  constructor(hls, logPrefix) {
6561    this.hls = void 0;
6562    this.timer = -1;
6563    this.requestScheduled = -1;
6564    this.canLoad = false;
6565    this.log = void 0;
6566    this.warn = void 0;
6567    this.log = logger.log.bind(logger, `${logPrefix}:`);
6568    this.warn = logger.warn.bind(logger, `${logPrefix}:`);
6569    this.hls = hls;
6570  }
6571  destroy() {
6572    this.clearTimer();
6573    // @ts-ignore
6574    this.hls = this.log = this.warn = null;
6575  }
6576  clearTimer() {
6577    if (this.timer !== -1) {
6578      self.clearTimeout(this.timer);
6579      this.timer = -1;
6580    }
6581  }
6582  startLoad() {
6583    this.canLoad = true;
6584    this.requestScheduled = -1;
6585    this.loadPlaylist();
6586  }
6587  stopLoad() {
6588    this.canLoad = false;
6589    this.clearTimer();
6590  }
6591  switchParams(playlistUri, previous, current) {
6592    const renditionReports = previous == null ? void 0 : previous.renditionReports;
6593    if (renditionReports) {
6594      let foundIndex = -1;
6595      for (let i = 0; i < renditionReports.length; i++) {
6596        const attr = renditionReports[i];
6597        let uri;
6598        try {
6599          uri = new self.URL(attr.URI, previous.url).href;
6600        } catch (error) {
6601          logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
6602          uri = attr.URI || '';
6603        }
6604        // Use exact match. Otherwise, the last partial match, if any, will be used
6605        // (Playlist URI includes a query string that the Rendition Report does not)
6606        if (uri === playlistUri) {
6607          foundIndex = i;
6608          break;
6609        } else if (uri === playlistUri.substring(0, uri.length)) {
6610          foundIndex = i;
6611        }
6612      }
6613      if (foundIndex !== -1) {
6614        const attr = renditionReports[foundIndex];
6615        const msn = parseInt(attr['LAST-MSN']) || (previous == null ? void 0 : previous.lastPartSn);
6616        let part = parseInt(attr['LAST-PART']) || (previous == null ? void 0 : previous.lastPartIndex);
6617        if (this.hls.config.lowLatencyMode) {
6618          const currentGoal = Math.min(previous.age - previous.partTarget, previous.targetduration);
6619          if (part >= 0 && currentGoal > previous.partTarget) {
6620            part += 1;
6621          }
6622        }
6623        const skip = current && getSkipValue(current);
6624        return new HlsUrlParameters(msn, part >= 0 ? part : undefined, skip);
6625      }
6626    }
6627  }
6628  loadPlaylist(hlsUrlParameters) {
6629    if (this.requestScheduled === -1) {
6630      this.requestScheduled = self.performance.now();
6631    }
6632    // Loading is handled by the subclasses
6633  }
6634  shouldLoadPlaylist(playlist) {
6635    return this.canLoad && !!playlist && !!playlist.url && (!playlist.details || playlist.details.live);
6636  }
6637  shouldReloadPlaylist(playlist) {
6638    return this.timer === -1 && this.requestScheduled === -1 && this.shouldLoadPlaylist(playlist);
6639  }
6640  playlistLoaded(index, data, previousDetails) {
6641    const {
6642      details,
6643      stats
6644    } = data;
6645
6646    // Set last updated date-time
6647    const now = self.performance.now();
6648    const elapsed = stats.loading.first ? Math.max(0, now - stats.loading.first) : 0;
6649    details.advancedDateTime = Date.now() - elapsed;
6650
6651    // if current playlist is a live playlist, arm a timer to reload it
6652    if (details.live || previousDetails != null && previousDetails.live) {
6653      details.reloaded(previousDetails);
6654      if (previousDetails) {
6655        this.log(`live playlist ${index} ${details.advanced ? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex : details.updated ? 'UPDATED' : 'MISSED'}`);
6656      }
6657      // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments
6658      if (previousDetails && details.fragments.length > 0) {
6659        mergeDetails(previousDetails, details);
6660      }
6661      if (!this.canLoad || !details.live) {
6662        return;
6663      }
6664      let deliveryDirectives;
6665      let msn = undefined;
6666      let part = undefined;
6667      if (details.canBlockReload && details.endSN && details.advanced) {
6668        // Load level with LL-HLS delivery directives
6669        const lowLatencyMode = this.hls.config.lowLatencyMode;
6670        const lastPartSn = details.lastPartSn;
6671        const endSn = details.endSN;
6672        const lastPartIndex = details.lastPartIndex;
6673        const hasParts = lastPartIndex !== -1;
6674        const lastPart = lastPartSn === endSn;
6675        // When low latency mode is disabled, we'll skip part requests once the last part index is found
6676        const nextSnStartIndex = lowLatencyMode ? 0 : lastPartIndex;
6677        if (hasParts) {
6678          msn = lastPart ? endSn + 1 : lastPartSn;
6679          part = lastPart ? nextSnStartIndex : lastPartIndex + 1;
6680        } else {
6681          msn = endSn + 1;
6682        }
6683        // Low-Latency CDN Tune-in: "age" header and time since load indicates we're behind by more than one part
6684        // Update directives to obtain the Playlist that has the estimated additional duration of media
6685        const lastAdvanced = details.age;
6686        const cdnAge = lastAdvanced + details.ageHeader;
6687        let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
6688        if (currentGoal > 0) {
6689          if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
6690            // If we attempted to get the next or latest playlist update, but currentGoal increased,
6691            // then we either can't catchup, or the "age" header cannot be trusted.
6692            this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
6693            currentGoal = 0;
6694          } else {
6695            const segments = Math.floor(currentGoal / details.targetduration);
6696            msn += segments;
6697            if (part !== undefined) {
6698              const parts = Math.round(currentGoal % details.targetduration / details.partTarget);
6699              part += parts;
6700            }
6701            this.log(`CDN Tune-in age: ${details.ageHeader}s last advanced ${lastAdvanced.toFixed(2)}s goal: ${currentGoal} skip sn ${segments} to part ${part}`);
6702          }
6703          details.tuneInGoal = currentGoal;
6704        }
6705        deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
6706        if (lowLatencyMode || !lastPart) {
6707          this.loadPlaylist(deliveryDirectives);
6708          return;
6709        }
6710      } else if (details.canBlockReload || details.canSkipUntil) {
6711        deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
6712      }
6713      const bufferInfo = this.hls.mainForwardBufferInfo;
6714      const position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0;
6715      const distanceToLiveEdgeMs = (details.edge - position) * 1000;
6716      const reloadInterval = computeReloadInterval(details, distanceToLiveEdgeMs);
6717      if (details.updated && now > this.requestScheduled + reloadInterval) {
6718        this.requestScheduled = stats.loading.start;
6719      }
6720      if (msn !== undefined && details.canBlockReload) {
6721        this.requestScheduled = stats.loading.first + reloadInterval - (details.partTarget * 1000 || 1000);
6722      } else if (this.requestScheduled === -1 || this.requestScheduled + reloadInterval < now) {
6723        this.requestScheduled = now;
6724      } else if (this.requestScheduled - now <= 0) {
6725        this.requestScheduled += reloadInterval;
6726      }
6727      let estimatedTimeUntilUpdate = this.requestScheduled - now;
6728      estimatedTimeUntilUpdate = Math.max(0, estimatedTimeUntilUpdate);
6729      this.log(`reload live playlist ${index} in ${Math.round(estimatedTimeUntilUpdate)} ms`);
6730      // this.log(
6731      //   `live reload ${details.updated ? 'REFRESHED' : 'MISSED'}
6732      // reload in ${estimatedTimeUntilUpdate / 1000}
6733      // round trip ${(stats.loading.end - stats.loading.start) / 1000}
6734      // diff ${
6735      //   (reloadInterval -
6736      //     (estimatedTimeUntilUpdate +
6737      //       stats.loading.end -
6738      //       stats.loading.start)) /
6739      //   1000
6740      // }
6741      // reload interval ${reloadInterval / 1000}
6742      // target duration ${details.targetduration}
6743      // distance to edge ${distanceToLiveEdgeMs / 1000}`
6744      // );
6745
6746      this.timer = self.setTimeout(() => this.loadPlaylist(deliveryDirectives), estimatedTimeUntilUpdate);
6747    } else {
6748      this.clearTimer();
6749    }
6750  }
6751  getDeliveryDirectives(details, previousDeliveryDirectives, msn, part) {
6752    let skip = getSkipValue(details);
6753    if (previousDeliveryDirectives != null && previousDeliveryDirectives.skip && details.deltaUpdateFailed) {
6754      msn = previousDeliveryDirectives.msn;
6755      part = previousDeliveryDirectives.part;
6756      skip = HlsSkip.No;
6757    }
6758    return new HlsUrlParameters(msn, part, skip);
6759  }
6760  checkRetry(errorEvent) {
6761    const errorDetails = errorEvent.details;
6762    const isTimeout = isTimeoutError(errorEvent);
6763    const errorAction = errorEvent.errorAction;
6764    const {
6765      action,
6766      retryCount = 0,
6767      retryConfig
6768    } = errorAction || {};
6769    const retry = !!errorAction && !!retryConfig && (action === NetworkErrorAction.RetryRequest || !errorAction.resolved && action === NetworkErrorAction.SendAlternateToPenaltyBox);
6770    if (retry) {
6771      var _errorEvent$context;
6772      this.requestScheduled = -1;
6773      if (retryCount >= retryConfig.maxNumRetry) {
6774        return false;
6775      }
6776      if (isTimeout && (_errorEvent$context = errorEvent.context) != null && _errorEvent$context.deliveryDirectives) {
6777        // The LL-HLS request already timed out so retry immediately
6778        this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" without delivery-directives`);
6779        this.loadPlaylist();
6780      } else {
6781        const delay = getRetryDelay(retryConfig, retryCount);
6782        // Schedule level/track reload
6783        this.timer = self.setTimeout(() => this.loadPlaylist(), delay);
6784        this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" in ${delay}ms`);
6785      }
6786      // `levelRetry = true` used to inform other controllers that a retry is happening
6787      errorEvent.levelRetry = true;
6788      errorAction.resolved = true;
6789    }
6790    return retry;
6791  }
6792}
6793
6794/*
6795 * compute an Exponential Weighted moving average
6796 * - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
6797 *  - heavily inspired from shaka-player
6798 */
6799
6800class EWMA {
6801  //  About half of the estimated value will be from the last |halfLife| samples by weight.
6802  constructor(halfLife, estimate = 0, weight = 0) {
6803    this.halfLife = void 0;
6804    this.alpha_ = void 0;
6805    this.estimate_ = void 0;
6806    this.totalWeight_ = void 0;
6807    this.halfLife = halfLife;
6808    // Larger values of alpha expire historical data more slowly.
6809    this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0;
6810    this.estimate_ = estimate;
6811    this.totalWeight_ = weight;
6812  }
6813  sample(weight, value) {
6814    const adjAlpha = Math.pow(this.alpha_, weight);
6815    this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_;
6816    this.totalWeight_ += weight;
6817  }
vendor: 4,303 bytes, lines 6818-6942
6818  getTotalWeight() {
6819    return this.totalWeight_;
6820  }
6821  getEstimate() {
6822    if (this.alpha_) {
6823      const zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_);
6824      if (zeroFactor) {
6825        return this.estimate_ / zeroFactor;
6826      }
6827    }
6828    return this.estimate_;
6829  }
6830}
6831
6832/*
6833 * EWMA Bandwidth Estimator
6834 *  - heavily inspired from shaka-player
6835 * Tracks bandwidth samples and estimates available bandwidth.
6836 * Based on the minimum of two exponentially-weighted moving averages with
6837 * different half-lives.
6838 */
6839
6840class EwmaBandWidthEstimator {
6841  constructor(slow, fast, defaultEstimate, defaultTTFB = 100) {
6842    this.defaultEstimate_ = void 0;
6843    this.minWeight_ = void 0;
6844    this.minDelayMs_ = void 0;
6845    this.slow_ = void 0;
6846    this.fast_ = void 0;
6847    this.defaultTTFB_ = void 0;
6848    this.ttfb_ = void 0;
6849    this.defaultEstimate_ = defaultEstimate;
6850    this.minWeight_ = 0.001;
6851    this.minDelayMs_ = 50;
6852    this.slow_ = new EWMA(slow);
6853    this.fast_ = new EWMA(fast);
6854    this.defaultTTFB_ = defaultTTFB;
6855    this.ttfb_ = new EWMA(slow);
6856  }
6857  update(slow, fast) {
6858    const {
6859      slow_,
6860      fast_,
6861      ttfb_
6862    } = this;
6863    if (slow_.halfLife !== slow) {
6864      this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight());
6865    }
6866    if (fast_.halfLife !== fast) {
6867      this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight());
6868    }
6869    if (ttfb_.halfLife !== slow) {
6870      this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight());
6871    }
6872  }
6873  sample(durationMs, numBytes) {
6874    durationMs = Math.max(durationMs, this.minDelayMs_);
6875    const numBits = 8 * numBytes;
6876    // weight is duration in seconds
6877    const durationS = durationMs / 1000;
6878    // value is bandwidth in bits/s
6879    const bandwidthInBps = numBits / durationS;
6880    this.fast_.sample(durationS, bandwidthInBps);
6881    this.slow_.sample(durationS, bandwidthInBps);
6882  }
6883  sampleTTFB(ttfb) {
6884    // weight is frequency curve applied to TTFB in seconds
6885    // (longer times have less weight with expected input under 1 second)
6886    const seconds = ttfb / 1000;
6887    const weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2);
6888    this.ttfb_.sample(weight, Math.max(ttfb, 5));
6889  }
6890  canEstimate() {
6891    return this.fast_.getTotalWeight() >= this.minWeight_;
6892  }
6893  getEstimate() {
6894    if (this.canEstimate()) {
6895      // console.log('slow estimate:'+ Math.round(this.slow_.getEstimate()));
6896      // console.log('fast estimate:'+ Math.round(this.fast_.getEstimate()));
6897      // Take the minimum of these two estimates.  This should have the effect of
6898      // adapting down quickly, but up more slowly.
6899      return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate());
6900    } else {
6901      return this.defaultEstimate_;
6902    }
6903  }
6904  getEstimateTTFB() {
6905    if (this.ttfb_.getTotalWeight() >= this.minWeight_) {
6906      return this.ttfb_.getEstimate();
6907    } else {
6908      return this.defaultTTFB_;
6909    }
6910  }
6911  destroy() {}
6912}
6913
6914const SUPPORTED_INFO_DEFAULT = {
6915  supported: true,
6916  configurations: [],
6917  decodingInfoResults: [{
6918    supported: true,
6919    powerEfficient: true,
6920    smooth: true
6921  }]
6922};
6923const SUPPORTED_INFO_CACHE = {};
6924function requiresMediaCapabilitiesDecodingInfo(level, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference) {
6925  // Only test support when configuration is exceeds minimum options
6926  const audioGroups = level.audioCodec ? level.audioGroups : null;
6927  const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec;
6928  const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels;
6929  const maxChannels = channelsPreference ? parseInt(channelsPreference) : audioCodecPreference ? Infinity : 2;
6930  let audioChannels = null;
6931  if (audioGroups != null && audioGroups.length) {
6932    try {
6933      if (audioGroups.length === 1 && audioGroups[0]) {
6934        audioChannels = audioTracksByGroup.groups[audioGroups[0]].channels;
6935      } else {
6936        audioChannels = audioGroups.reduce((acc, groupId) => {
6937          if (groupId) {
6938            const audioTrackGroup = audioTracksByGroup.groups[groupId];
6939            if (!audioTrackGroup) {
6940              throw new Error(`Audio track group ${groupId} not found`);
6941            }
6942            // Sum all channel key values
6943            Object.keys(audioTrackGroup.channels).forEach(key => {
6944              acc[key] = (acc[key] || 0) + audioTrackGroup.channels[key];
6945            });
6946          }
6947          return acc;
6948        }, {
6949          2: 0
6950        });
6951      }
6952    } catch (error) {
6953      return true;
6954    }
6955  }
6956  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);
6957}
6958function getMediaDecodingInfoPromise(level, audioTracksByGroup, mediaCapabilities) {
6959  const videoCodecs = level.videoCodec;
6960  const audioCodecs = level.audioCodec;
6961  if (!videoCodecs || !audioCodecs || !mediaCapabilities) {
6962    return Promise.resolve(SUPPORTED_INFO_DEFAULT);
6963  }
6964  const baseVideoConfiguration = {
6965    width: level.width,
6966    height: level.height,
6967    bitrate: Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate)),
6968    // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0.
6969    framerate: level.frameRate || 30
6970  };
6971  const videoRange = level.videoRange;
6972  if (videoRange !== 'SDR') {
6973    baseVideoConfiguration.transferFunction = videoRange.toLowerCase();
6974  }
6975  const configurations = videoCodecs.split(',').map(videoCodec => ({
6976    type: 'media-source',
6977    video: _objectSpread2(_objectSpread2({}, baseVideoConfiguration), {}, {
6978      contentType: mimeTypeForCodec(videoCodec, 'video')
6979    })
6980  }));
6981  if (audioCodecs && level.audioGroups) {
6982    level.audioGroups.forEach(audioGroupId => {
6983      var _audioTracksByGroup$g;
6984      if (!audioGroupId) {
6985        return;
6986      }
6987      (_audioTracksByGroup$g = audioTracksByGroup.groups[audioGroupId]) == null ? void 0 : _audioTracksByGroup$g.tracks.forEach(audioTrack => {
6988        if (audioTrack.groupId === audioGroupId) {
6989          const channels = audioTrack.channels || '';
6990          const channelsNumber = parseFloat(channels);
6991          if (isFiniteNumber(channelsNumber) && channelsNumber > 2) {
6992            configurations.push.apply(configurations, audioCodecs.split(',').map(audioCodec => ({
6993              type: 'media-source',
6994              audio: {
6995                contentType: mimeTypeForCodec(audioCodec, 'audio'),
6996                channels: '' + channelsNumber
6997                // spatialRendering:
6998                //   audioCodec === 'ec-3' && channels.indexOf('JOC'),
6999              }
7000            })));
7001          }
7002        }
7003      });
7004    });
7005  }
7006  return Promise.all(configurations.map(configuration => {
7007    // Cache MediaCapabilities promises
7008    const decodingInfoKey = getMediaDecodingInfoKey(configuration);
7009    return SUPPORTED_INFO_CACHE[decodingInfoKey] || (SUPPORTED_INFO_CACHE[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration));
7010  })).then(decodingInfoResults => ({
7011    supported: !decodingInfoResults.some(info => !info.supported),
7012    configurations,
7013    decodingInfoResults
7014  })).catch(error => ({
7015    supported: false,
7016    configurations,
7017    decodingInfoResults: [],
7018    error
7019  }));
7020}
7021function getMediaDecodingInfoKey(config) {
7022  const {
7023    audio,
7024    video
7025  } = config;
7026  const mediaConfig = video || audio;
7027  if (mediaConfig) {
7028    const codec = mediaConfig.contentType.split('"')[1];
7029    if (video) {
7030      return `r${video.height}x${video.width}f${Math.ceil(video.framerate)}${video.transferFunction || 'sd'}_${codec}_${Math.ceil(video.bitrate / 1e5)}`;
7031    }
7032    if (audio) {
7033      return `c${audio.channels}${audio.spatialRendering ? 's' : 'n'}_${codec}`;
7034    }
7035  }
7036  return '';
7037}
7038
7039/**
7040 * @returns Whether we can detect and validate HDR capability within the window context
7041 */
7042function isHdrSupported() {
7043  if (typeof matchMedia === 'function') {
7044    const mediaQueryList = matchMedia('(dynamic-range: high)');
7045    const badQuery = matchMedia('bad query');
7046    if (mediaQueryList.media !== badQuery.media) {
7047      return mediaQueryList.matches === true;
7048    }
7049  }
7050  return false;
7051}
7052
7053/**
7054 * Sanitizes inputs to return the active video selection options for HDR/SDR.
7055 * When both inputs are null:
7056 *
7057 *    `{ preferHDR: false, allowedVideoRanges: [] }`
7058 *
7059 * When `currentVideoRange` non-null, maintain the active range:
7060 *
7061 *    `{ preferHDR: currentVideoRange !== 'SDR', allowedVideoRanges: [currentVideoRange] }`
7062 *
7063 * When VideoSelectionOption non-null:
7064 *
7065 *  - Allow all video ranges if `allowedVideoRanges` unspecified.
7066 *  - If `preferHDR` is non-null use the value to filter `allowedVideoRanges`.
7067 *  - Else check window for HDR support and set `preferHDR` to the result.
7068 *
7069 * @param currentVideoRange
7070 * @param videoPreference
7071 */
7072function getVideoSelectionOptions(currentVideoRange, videoPreference) {
7073  let preferHDR = false;
7074  let allowedVideoRanges = [];
7075  if (currentVideoRange) {
7076    preferHDR = currentVideoRange !== 'SDR';
7077    allowedVideoRanges = [currentVideoRange];
7078  }
7079  if (videoPreference) {
7080    allowedVideoRanges = videoPreference.allowedVideoRanges || VideoRangeValues.slice(0);
7081    preferHDR = videoPreference.preferHDR !== undefined ? videoPreference.preferHDR : isHdrSupported();
7082    if (preferHDR) {
7083      allowedVideoRanges = allowedVideoRanges.filter(range => range !== 'SDR');
7084    } else {
7085      allowedVideoRanges = ['SDR'];
7086    }
7087  }
7088  return {
7089    preferHDR,
7090    allowedVideoRanges
7091  };
7092}
7093
7094function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference) {
7095  const codecSets = Object.keys(codecTiers);
7096  const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels;
7097  const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec;
7098  const preferStereo = channelsPreference && parseInt(channelsPreference) === 2;
7099  // Use first level set to determine stereo, and minimum resolution and framerate
7100  let hasStereo = true;
7101  let hasCurrentVideoRange = false;
7102  let minHeight = Infinity;
7103  let minFramerate = Infinity;
7104  let minBitrate = Infinity;
7105  let selectedScore = 0;
7106  let videoRanges = [];
7107  const {
7108    preferHDR,
7109    allowedVideoRanges
7110  } = getVideoSelectionOptions(currentVideoRange, videoPreference);
7111  for (let i = codecSets.length; i--;) {
7112    const tier = codecTiers[codecSets[i]];
7113    hasStereo = tier.channels[2] > 0;
7114    minHeight = Math.min(minHeight, tier.minHeight);
7115    minFramerate = Math.min(minFramerate, tier.minFramerate);
7116    minBitrate = Math.min(minBitrate, tier.minBitrate);
7117    const matchingVideoRanges = allowedVideoRanges.filter(range => tier.videoRanges[range] > 0);
7118    if (matchingVideoRanges.length > 0) {
7119      hasCurrentVideoRange = true;
7120      videoRanges = matchingVideoRanges;
7121    }
7122  }
7123  minHeight = isFiniteNumber(minHeight) ? minHeight : 0;
7124  minFramerate = isFiniteNumber(minFramerate) ? minFramerate : 0;
7125  const maxHeight = Math.max(1080, minHeight);
7126  const maxFramerate = Math.max(30, minFramerate);
7127  minBitrate = isFiniteNumber(minBitrate) ? minBitrate : currentBw;
7128  currentBw = Math.max(minBitrate, currentBw);
7129  // If there are no variants with matching preference, set currentVideoRange to undefined
7130  if (!hasCurrentVideoRange) {
7131    currentVideoRange = undefined;
7132    videoRanges = [];
7133  }
7134  const codecSet = codecSets.reduce((selected, candidate) => {
7135    // 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
7136    const candidateTier = codecTiers[candidate];
7137    if (candidate === selected) {
7138      return selected;
7139    }
7140    if (candidateTier.minBitrate > currentBw) {
7141      logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);
7142      return selected;
7143    }
7144    if (!candidateTier.hasDefaultAudio) {
7145      logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);
7146      return selected;
7147    }
7148    if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
7149      logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`);
7150      return selected;
7151    }
7152    if (channelsPreference && !preferStereo) {
7153      if (!candidateTier.channels[channelsPreference]) {
7154        logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);
7155        return selected;
7156      }
7157    } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) {
7158      logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);
7159      return selected;
7160    }
7161    if (candidateTier.minHeight > maxHeight) {
7162      logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);
7163      return selected;
7164    }
7165    if (candidateTier.minFramerate > maxFramerate) {
7166      logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);
7167      return selected;
7168    }
7169    if (!videoRanges.some(range => candidateTier.videoRanges[range] > 0)) {
7170      logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${JSON.stringify(videoRanges)} found`);
7171      return selected;
7172    }
7173    if (candidateTier.maxScore < selectedScore) {
7174      logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);
7175      return selected;
7176    }
7177    // Remove candiates with less preferred codecs or more errors
7178    if (selected && (codecsSetSelectionPreferenceValue(candidate) >
vendor: 4,457 bytes, lines 7178-7301
7178= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) {
7179      return selected;
7180    }
7181    selectedScore = candidateTier.maxScore;
7182    return candidate;
7183  }, undefined);
7184  return {
7185    codecSet,
7186    videoRanges,
7187    preferHDR,
7188    minFramerate,
7189    minBitrate
7190  };
7191}
7192function logStartCodecCandidateIgnored(codeSet, reason) {
7193  logger.log(`[abr] start candidates with "${codeSet}" ignored because ${reason}`);
7194}
7195function getAudioTracksByGroup(allAudioTracks) {
7196  return allAudioTracks.reduce((audioTracksByGroup, track) => {
7197    let trackGroup = audioTracksByGroup.groups[track.groupId];
7198    if (!trackGroup) {
7199      trackGroup = audioTracksByGroup.groups[track.groupId] = {
7200        tracks: [],
7201        channels: {
7202          2: 0
7203        },
7204        hasDefault: false,
7205        hasAutoSelect: false
7206      };
7207    }
7208    trackGroup.tracks.push(track);
7209    const channelsKey = track.channels || '2';
7210    trackGroup.channels[channelsKey] = (trackGroup.channels[channelsKey] || 0) + 1;
7211    trackGroup.hasDefault = trackGroup.hasDefault || track.default;
7212    trackGroup.hasAutoSelect = trackGroup.hasAutoSelect || track.autoselect;
7213    if (trackGroup.hasDefault) {
7214      audioTracksByGroup.hasDefaultAudio = true;
7215    }
7216    if (trackGroup.hasAutoSelect) {
7217      audioTracksByGroup.hasAutoSelectAudio = true;
7218    }
7219    return audioTracksByGroup;
7220  }, {
7221    hasDefaultAudio: false,
7222    hasAutoSelectAudio: false,
7223    groups: {}
7224  });
7225}
7226function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
7227  return levels.slice(minAutoLevel, maxAutoLevel + 1).reduce((tiers, level) => {
7228    if (!level.codecSet) {
7229      return tiers;
7230    }
7231    const audioGroups = level.audioGroups;
7232    let tier = tiers[level.codecSet];
7233    if (!tier) {
7234      tiers[level.codecSet] = tier = {
7235        minBitrate: Infinity,
7236        minHeight: Infinity,
7237        minFramerate: Infinity,
7238        maxScore: 0,
7239        videoRanges: {
7240          SDR: 0
7241        },
7242        channels: {
7243          '2': 0
7244        },
7245        hasDefaultAudio: !audioGroups,
7246        fragmentError: 0
7247      };
7248    }
7249    tier.minBitrate = Math.min(tier.minBitrate, level.bitrate);
7250    const lesserWidthOrHeight = Math.min(level.height, level.width);
7251    tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight);
7252    tier.minFramerate = Math.min(tier.minFramerate, level.frameRate);
7253    tier.maxScore = Math.max(tier.maxScore, level.score);
7254    tier.fragmentError += level.fragmentError;
7255    tier.videoRanges[level.videoRange] = (tier.videoRanges[level.videoRange] || 0) + 1;
7256    if (audioGroups) {
7257      audioGroups.forEach(audioGroupId => {
7258        if (!audioGroupId) {
7259          return;
7260        }
7261        const audioGroup = audioTracksByGroup.groups[audioGroupId];
7262        if (!audioGroup) {
7263          return;
7264        }
7265        // Default audio is any group with DEFAULT=YES, or if missing then any group with AUTOSELECT=YES, or all variants
7266        tier.hasDefaultAudio = tier.hasDefaultAudio || audioTracksByGroup.hasDefaultAudio ? audioGroup.hasDefault : audioGroup.hasAutoSelect || !audioTracksByGroup.hasDefaultAudio && !audioTracksByGroup.hasAutoSelectAudio;
7267        Object.keys(audioGroup.channels).forEach(channels => {
7268          tier.channels[channels] = (tier.channels[channels] || 0) + audioGroup.channels[channels];
7269        });
7270      });
7271    }
7272    return tiers;
7273  }, {});
7274}
7275function findMatchingOption(option, tracks, matchPredicate) {
7276  if ('attrs' in option) {
7277    const index = tracks.indexOf(option);
7278    if (index !== -1) {
7279      return index;
7280    }
7281  }
7282  for (let i = 0; i < tracks.length; i++) {
7283    const track = tracks[i];
7284    if (matchesOption(option, track, matchPredicate)) {
7285      return i;
7286    }
7287  }
7288  return -1;
7289}
7290function matchesOption(option, track, matchPredicate) {
7291  const {
7292    groupId,
7293    name,
7294    lang,
7295    assocLang,
7296    characteristics,
7297    default: isDefault
7298  } = option;
7299  const forced = option.forced;
7300  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));
7301}
7302function characteristicsMatch(characteristicsA, characteristicsB = '') {
7303  const arrA = characteristicsA.split(',');
7304  const arrB = characteristicsB.split(',');
7305  // Expects each item to be unique:
7306  return arrA.length === arrB.length && !arrA.some(el => arrB.indexOf(el) === -1);
7307}
7308function audioMatchPredicate(option, track) {
7309  const {
7310    audioCodec,
7311    channels
7312  } = option;
7313  return (audioCodec === undefined || (track.audioCodec || '').substring(0, 4) === audioCodec.substring(0, 4)) && (channels === undefined || channels === (track.channels || '2'));
7314}
7315function findClosestLevelWithAudioGroup(option, levels, allAudioTracks, searchIndex, matchPredicate) {
7316  const currentLevel = levels[searchIndex];
7317  // Are there variants with same URI as current level?
7318  // If so, find a match that does not require any level URI change
7319  const variants = levels.reduce((variantMap, level, index) => {
7320    const uri = level.uri;
7321    const renditions = variantMap[uri] || (variantMap[uri] = []);
7322    renditions.push(index);
7323    return variantMap;
7324  }, {});
7325  const renditions = variants[currentLevel.uri];
7326  if (renditions.length > 1) {
7327    searchIndex = Math.max.apply(Math, renditions);
7328  }
7329  // Find best match
7330  const currentVideoRange = currentLevel.videoRange;
7331  const currentFrameRate = currentLevel.frameRate;
7332  const currentVideoCodec = currentLevel.codecSet.substring(0, 4);
7333  const matchingVideo = searchDownAndUpList(levels, searchIndex, level => {
7334    if (level.videoRange !== currentVideoRange || level.frameRate !== currentFrameRate || level.codecSet.substring(0, 4) !== currentVideoCodec) {
7335      return false;
7336    }
7337    const audioGroups = level.audioGroups;
7338    const tracks = allAudioTracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
7339    return findMatchingOption(option, tracks, matchPredicate) > -1;
7340  });
7341  if (matchingVideo > -1) {
7342    return matchingVideo;
7343  }
7344  return searchDownAndUpList(levels, searchIndex, level => {
7345    const audioGroups = level.audioGroups;
7346    const tracks = allAudioTracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
7347    return findMatchingOption(option, tracks, matchPredicate) > -1;
7348  });
7349}
7350function searchDownAndUpList(arr, searchIndex, predicate) {
7351  for (let i = searchIndex; i; i--) {
7352    if (predicate(arr[i])) {
7353      return i;
7354    }
7355  }
7356  for (let i = searchIndex + 1; i < arr.length; i++) {
7357    if (predicate(arr[i])) {
7358      return i;
7359    }
7360  }
7361  return -1;
7362}
7363
7364class AbrController {
7365  constructor(_hls) {
7366    this.hls = void 0;
7367    this.lastLevelLoadSec = 0;
7368    this.lastLoadedFragLevel = -1;
7369    this.firstSelection = -1;
7370    this._nextAutoLevel = -1;
7371    this.nextAutoLevelKey = '';
7372    this.audioTracksByGroup = null;
7373    this.codecTiers = null;
7374    this.timer = -1;
7375    this.fragCurrent = null;
7376    this.partCurrent = null;
7377    this.bitrateTestDelay = 0;
7378    this.bwEstimator = void 0;
7379    /*
7380        This method monitors the download rate of the current fragment, and will downswitch if that fragment will not load
7381        quickly enough to prevent underbuffering
7382      */
7383    this._abandonRulesCheck = () => {
7384      const {
7385        fragCurrent: frag,
7386        partCurrent: part,
7387        hls
7388      } = this;
7389      const {
7390        autoLevelEnabled,
7391        media
7392      } = hls;
7393      if (!frag || !media) {
7394        return;
7395      }
7396      const now = performance.now();
7397      const stats = part ? part.stats : frag.stats;
7398      const duration = part ? part.duration : frag.duration;
7399      const timeLoading = now - stats.loading.start;
7400      const minAutoLevel = hls.minAutoLevel;
7401      // If frag loading is aborted, complete, or from lowest level, stop timer and return
7402      if (stats.aborted || stats.loaded && stats.loaded === stats.total || frag.level <= minAutoLevel) {
7403        this.clearTimer();
7404        // reset forced auto level value so that next level will be selected
7405        this._nextAutoLevel = -1;
7406        return;
7407      }
7408
7409      // This check only runs if we're in ABR mode and actually playing
7410      if (!autoLevelEnabled || media.paused || !media.playbackRate || !media.readyState) {
7411        return;
7412      }
7413      const bufferInfo = hls.mainForwardBufferInfo;
7414      if (bufferInfo === null) {
7415        return;
7416      }
7417      const ttfbEstimate = this.bwEstimator.getEstimateTTFB();
7418      const playbackRate = Math.abs(media.playbackRate);
7419      // To maintain stable adaptive playback, only begin monitoring frag loading after half or more of its playback duration has passed
7420      if (timeLoading <= Math.max(ttfbEstimate, 1000 * (duration / (playbackRate * 2)))) {
7421        return;
7422      }
7423
7424      // bufferStarvationDelay is an estimate of the amount time (in seconds) it will take to exhaust the buffer
7425      const bufferStarvationDelay = bufferInfo.len / playbackRate;
7426      const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
7427      const loadedFirstByte = stats.loaded && ttfb > -1;
7428      const bwEstimate = this.getBwEstimate();
7429      const levels = hls.levels;
7430      const level = levels[frag.level];
7431      const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8));
7432      let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
7433      if (timeStreaming < 1 && loadedFirstByte) {
7434        timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
7435      }
7436      const loadRate = loadedFirstByte ? stats.loaded * 1000 / timeStreaming : 0;
7437      // fragLoadDelay is an estimate of the time (in seconds) it will take to buffer the remainder of the fragment
7438      const fragLoadedDelay = loadRate ? (expectedLen - stats.loaded) / loadRate : expectedLen * 8 / bwEstimate + ttfbEstimate / 1000;
7439      // Only downswitch if the time to finish loading the current fragment is greater than the amount of buffer left
7440      if (fragLoadedDelay <= bufferStarvationDelay) {
7441        return;
7442      }
7443      const bwe = loadRate ? loadRate * 8 : bwEstimate;
7444      let fragLevelNextLoadedDelay = Number.POSITIVE_INFINITY;
7445      let nextLoadLevel;
7446      // Iterate through lower level and try to find the largest one that avoids rebuffering
7447      for (nextLoadLevel = frag.level - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) {
7448        // compute time to load next fragment at lower level
7449        // 8 = bits per byte (bps/Bps)
7450        const levelNextBitrate = levels[nextLoadLevel].maxBitrate;
7451        fragLevelNextLoadedDelay = this.getTimeToLoadFrag(ttfbEstimate / 1000, bwe, duration * levelNextBitrate, !levels[nextLoadLevel].details);
7452        if (fragLevelNextLoadedDelay < bufferStarvationDelay) {
7453          break;
7454        }
7455      }
7456      // Only emergency switch down if it takes less time to load a new fragment at lowest level instead of continuing
7457      // to load the current one
7458      if (fragLevelNextLoadedDelay >= fragLoadedDelay) {
7459        return;
7460      }
7461
7462      // if estimated load time of new segment is completely unreasonable, ignore and do not emergency switch down
7463      if (fragLevelNextLoadedDelay > duration * 10) {
7464        return;
7465      }
7466      hls.nextLoadLevel = hls.nextAutoLevel = nextLoadLevel;
7467      if (loadedFirstByte) {
7468        // If there has been loading progress, sample bandwidth using loading time offset by minimum TTFB time
7469        this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
7470      } else {
7471        // If there has been no loading progress, sample TTFB
7472        this.bwEstimator.sampleTTFB(timeLoading);
7473      }
7474      const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
7475      if (this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor >
vendor: 7,307 bytes, lines 7475-7678
7475 nextLoadLevelBitrate) {
7476        this.resetEstimator(nextLoadLevelBitrate);
7477      }
7478      this.clearTimer();
7479      logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
7480      Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
7481      Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
7482      Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
7483      TTFB estimate: ${ttfb | 0} ms
7484      Current BW estimate: ${isFiniteNumber(bwEstimate) ? bwEstimate | 0 : 'Unknown'} bps
7485      New BW estimate: ${this.getBwEstimate() | 0} bps
7486      Switching to level ${nextLoadLevel} @ ${nextLoadLevelBitrate | 0} bps`);
7487      hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, {
7488        frag,
7489        part,
7490        stats
7491      });
7492    };
7493    this.hls = _hls;
7494    this.bwEstimator = this.initEstimator();
7495    this.registerListeners();
7496  }
7497  resetEstimator(abrEwmaDefaultEstimate) {
7498    if (abrEwmaDefaultEstimate) {
7499      logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
7500      this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
7501    }
7502    this.firstSelection = -1;
7503    this.bwEstimator = this.initEstimator();
7504  }
7505  initEstimator() {
7506    const config = this.hls.config;
7507    return new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate);
7508  }
7509  registerListeners() {
7510    const {
7511      hls
7512    } = this;
7513    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
7514    hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
7515    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
7516    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
7517    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
7518    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
7519    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
7520    hls.on(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
7521    hls.on(Events.ERROR, this.onError, this);
7522  }
7523  unregisterListeners() {
7524    const {
7525      hls
7526    } = this;
7527    if (!hls) {
7528      return;
7529    }
7530    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
7531    hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
7532    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
7533    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
7534    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
7535    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
7536    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
7537    hls.off(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
7538    hls.off(Events.ERROR, this.onError, this);
7539  }
7540  destroy() {
7541    this.unregisterListeners();
7542    this.clearTimer();
7543    // @ts-ignore
7544    this.hls = this._abandonRulesCheck = null;
7545    this.fragCurrent = this.partCurrent = null;
7546  }
7547  onManifestLoading(event, data) {
7548    this.lastLoadedFragLevel = -1;
7549    this.firstSelection = -1;
7550    this.lastLevelLoadSec = 0;
7551    this.fragCurrent = this.partCurrent = null;
7552    this.onLevelsUpdated();
7553    this.clearTimer();
7554  }
7555  onLevelsUpdated() {
7556    if (this.lastLoadedFragLevel > -1 && this.fragCurrent) {
7557      this.lastLoadedFragLevel = this.fragCurrent.level;
7558    }
7559    this._nextAutoLevel = -1;
7560    this.onMaxAutoLevelUpdated();
7561    this.codecTiers = null;
7562    this.audioTracksByGroup = null;
7563  }
7564  onMaxAutoLevelUpdated() {
7565    this.firstSelection = -1;
7566    this.nextAutoLevelKey = '';
7567  }
7568  onFragLoading(event, data) {
7569    const frag = data.frag;
7570    if (this.ignoreFragment(frag)) {
7571      return;
7572    }
7573    if (!frag.bitrateTest) {
7574      var _data$part;
7575      this.fragCurrent = frag;
7576      this.partCurrent = (_data$part = data.part) != null ? _data$part : null;
7577    }
7578    this.clearTimer();
7579    this.timer = self.setInterval(this._abandonRulesCheck, 100);
7580  }
7581  onLevelSwitching(event, data) {
7582    this.clearTimer();
7583  }
7584  onError(event, data) {
7585    if (data.fatal) {
7586      return;
7587    }
7588    switch (data.details) {
7589      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
7590      case ErrorDetails.BUFFER_APPEND_ERROR:
7591        // Reset last loaded level so that a new selection can be made after calling recoverMediaError
7592        this.lastLoadedFragLevel = -1;
7593        this.firstSelection = -1;
7594        break;
7595      case ErrorDetails.FRAG_LOAD_TIMEOUT:
7596        {
7597          const frag = data.frag;
7598          const {
7599            fragCurrent,
7600            partCurrent: part
7601          } = this;
7602          if (frag && fragCurrent && frag.sn === fragCurrent.sn && frag.level === fragCurrent.level) {
7603            const now = performance.now();
7604            const stats = part ? part.stats : frag.stats;
7605            const timeLoading = now - stats.loading.start;
7606            const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
7607            const loadedFirstByte = stats.loaded && ttfb > -1;
7608            if (loadedFirstByte) {
7609              const ttfbEstimate = this.bwEstimator.getEstimateTTFB();
7610              this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
7611            } else {
7612              this.bwEstimator.sampleTTFB(timeLoading);
7613            }
7614          }
7615          break;
7616        }
7617    }
7618  }
7619  getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch) {
7620    const fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth;
7621    const playlistLoadSec = isSwitch ? this.lastLevelLoadSec : 0;
7622    return fragLoadSec + playlistLoadSec;
7623  }
7624  onLevelLoaded(event, data) {
7625    const config = this.hls.config;
7626    const {
7627      loading
7628    } = data.stats;
7629    const timeLoadingMs = loading.end - loading.start;
7630    if (isFiniteNumber(timeLoadingMs)) {
7631      this.lastLevelLoadSec = timeLoadingMs / 1000;
7632    }
7633    if (data.details.live) {
7634      this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive);
7635    } else {
7636      this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD);
7637    }
7638  }
7639  onFragLoaded(event, {
7640    frag,
7641    part
7642  }) {
7643    const stats = part ? part.stats : frag.stats;
7644    if (frag.type === PlaylistLevelType.MAIN) {
7645      this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start);
7646    }
7647    if (this.ignoreFragment(frag)) {
7648      return;
7649    }
7650    // stop monitoring bw once frag loaded
7651    this.clearTimer();
7652    // reset forced auto level value so that next level will be selected
7653    if (frag.level === this._nextAutoLevel) {
7654      this._nextAutoLevel = -1;
7655    }
7656    this.firstSelection = -1;
7657
7658    // compute level average bitrate
7659    if (this.hls.config.abrMaxWithRealBitrate) {
7660      const duration = part ? part.duration : frag.duration;
7661      const level = this.hls.levels[frag.level];
7662      const loadedBytes = (level.loaded ? level.loaded.bytes : 0) + stats.loaded;
7663      const loadedDuration = (level.loaded ? level.loaded.duration : 0) + duration;
7664      level.loaded = {
7665        bytes: loadedBytes,
7666        duration: loadedDuration
7667      };
7668      level.realBitrate = Math.round(8 * loadedBytes / loadedDuration);
7669    }
7670    if (frag.bitrateTest) {
7671      const fragBufferedData = {
7672        stats,
7673        frag,
7674        part,
7675        id: frag.type
7676      };
7677      this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData);
7678      frag.bitrateTest = false;
vendor: 3,466 bytes, lines 7679-7769
7679    } else {
7680      // store level id after successful fragment load for playback
7681      this.lastLoadedFragLevel = frag.level;
7682    }
7683  }
7684  onFragBuffered(event, data) {
7685    const {
7686      frag,
7687      part
7688    } = data;
7689    const stats = part != null && part.stats.loaded ? part.stats : frag.stats;
7690    if (stats.aborted) {
7691      return;
7692    }
7693    if (this.ignoreFragment(frag)) {
7694      return;
7695    }
7696    // Use the difference between parsing and request instead of buffering and request to compute fragLoadingProcessing;
7697    // rationale is that buffer appending only happens once media is attached. This can happen when config.startFragPrefetch
7698    // is used. If we used buffering in that case, our BW estimate sample will be very large.
7699    const processingMs = stats.parsing.end - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB());
7700    this.bwEstimator.sample(processingMs, stats.loaded);
7701    stats.bwEstimate = this.getBwEstimate();
7702    if (frag.bitrateTest) {
7703      this.bitrateTestDelay = processingMs / 1000;
7704    } else {
7705      this.bitrateTestDelay = 0;
7706    }
7707  }
7708  ignoreFragment(frag) {
7709    // Only count non-alt-audio frags which were actually buffered in our BW calculations
7710    return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment';
7711  }
7712  clearTimer() {
7713    if (this.timer > -1) {
7714      self.clearInterval(this.timer);
7715      this.timer = -1;
7716    }
7717  }
7718  get firstAutoLevel() {
7719    const {
7720      maxAutoLevel,
7721      minAutoLevel
7722    } = this.hls;
7723    const bwEstimate = this.getBwEstimate();
7724    const maxStartDelay = this.hls.config.maxStarvationDelay;
7725    const abrAutoLevel = this.findBestLevel(bwEstimate, minAutoLevel, maxAutoLevel, 0, maxStartDelay, 1, 1);
7726    if (abrAutoLevel > -1) {
7727      return abrAutoLevel;
7728    }
7729    const firstLevel = this.hls.firstLevel;
7730    const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
7731    logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
7732    return clamped;
7733  }
7734  get forcedAutoLevel() {
7735    if (this.nextAutoLevelKey) {
7736      return -1;
7737    }
7738    return this._nextAutoLevel;
7739  }
7740
7741  // return next auto level
7742  get nextAutoLevel() {
7743    const forcedAutoLevel = this.forcedAutoLevel;
7744    const bwEstimator = this.bwEstimator;
7745    const useEstimate = bwEstimator.canEstimate();
7746    const loadedFirstFrag = this.lastLoadedFragLevel > -1;
7747    // in case next auto level has been forced, and bw not available or not reliable, return forced value
7748    if (forcedAutoLevel !== -1 && (!useEstimate || !loadedFirstFrag || this.nextAutoLevelKey === this.getAutoLevelKey())) {
7749      return forcedAutoLevel;
7750    }
7751
7752    // compute next level using ABR logic
7753    const nextABRAutoLevel = useEstimate && loadedFirstFrag ? this.getNextABRAutoLevel() : this.firstAutoLevel;
7754
7755    // use forced auto level while it hasn't errored more than ABR selection
7756    if (forcedAutoLevel !== -1) {
7757      const levels = this.hls.levels;
7758      if (levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel) && levels[forcedAutoLevel].loadError <= levels[nextABRAutoLevel].loadError) {
7759        return forcedAutoLevel;
7760      }
7761    }
7762
7763    // save result until state has changed
7764    this._nextAutoLevel = nextABRAutoLevel;
7765    this.nextAutoLevelKey = this.getAutoLevelKey();
7766    return nextABRAutoLevel;
7767  }
7768  getAutoLevelKey() {
7769    return `${this.getBwEstimate()}
7769_${this.getStarvationDelay().toFixed(2)}`;
7770  }
7771  getNextABRAutoLevel() {
7772    const {
7773      fragCurrent,
7774      partCurrent,
7775      hls
7776    } = this;
7777    const {
7778      maxAutoLevel,
7779      config,
7780      minAutoLevel
7781    } = hls;
7782    const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
7783    const avgbw = this.getBwEstimate();
7784    // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
7785    const bufferStarvationDelay = this.getStarvationDelay();
7786    let bwFactor = config.abrBandWidthFactor;
7787    let bwUpFactor = config.abrBandWidthUpFactor;
7788
7789    // First, look to see if we can find a level matching with our avg bandwidth AND that could also guarantee no rebuffering at all
7790    if (bufferStarvationDelay) {
7791      const _bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, 0, bwFactor, bwUpFactor);
7792      if (_bestLevel >= 0) {
7793        return _bestLevel;
7794      }
7795    }
7796    // not possible to get rid of rebuffering... try to find level that will guarantee less than maxStarvationDelay of rebuffering
7797    let maxStarvationDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay) : config.maxStarvationDelay;
7798    if (!bufferStarvationDelay) {
7799      // in case buffer is empty, let's check if previous fragment was loaded to perform a bitrate test
7800      const bitrateTestDelay = this.bitrateTestDelay;
7801      if (bitrateTestDelay) {
7802        // if it is the case, then we need to adjust our max starvation delay using maxLoadingDelay config value
7803        // max video loading delay used in  automatic start level selection :
7804        // in that mode ABR controller will ensure that video loading time (ie the time to fetch the first fragment at lowest quality level +
7805        // the time to fetch the fragment at the appropriate quality level is less than ```maxLoadingDelay``` )
7806        // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
7807        const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
7808        maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
7809        logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
7810        // don't use conservative factor on bitrate test
7811        bwFactor = bwUpFactor = 1;
7812      }
7813    }
7814    const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
7815    logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
7816    if (bestLevel > -1) {
7817      return bestLevel;
7818    }
7819    // If no matching level found, see if min auto level would be a better option
7820    const minLevel = hls.levels[minAutoLevel];
7821    const autoLevel = hls.levels[hls.loadLevel];
7822    if ((minLevel == null ? void 0 : minLevel.bitrate) < (autoLevel == null ? void 0 : autoLevel.bitrate)) {
7823      return minAutoLevel;
7824    }
7825    // or if bitrate is not lower, continue to use loadLevel
7826    return hls.loadLevel;
7827  }
7828  getStarvationDelay() {
7829    const hls = this.hls;
7830    const media = hls.media;
7831    if (!media) {
7832      return Infinity;
7833    }
7834    // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
7835    // if we're playing back at the normal rate.
7836    const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
7837    const bufferInfo = hls.mainForwardBufferInfo;
7838    return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
7839  }
7840  getBwEstimate() {
7841    return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate;
7842  }
7843  findBestLevel(currentBw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor) {
7844    var _level$details;
7845    const maxFetchDuration = bufferStarvationDelay + maxStarvationDelay;
7846    const lastLoadedFragLevel = this.lastLoadedFragLevel;
7847    const selectionBaseLevel = lastLoadedFragLevel === -1 ? this.hls.firstLevel : lastLoadedFragLevel;
7848    const {
7849      fragCurrent,
7850      partCurrent
7851    } = this;
7852    const {
7853      levels,
7854      allAudioTracks,
7855      loadLevel,
7856      config
7857    } = this.hls;
7858    if (levels.length === 1) {
7859      return 0;
7860    }
7861    const level = levels[selectionBaseLevel];
7862    const live = !!(level != null && (_level$details = level.details) != null && _level$details.live);
7863    const firstSelection = loadLevel === -1 || lastLoadedFragLevel === -1;
7864    let currentCodecSet;
7865    let currentVideoRange = 'SDR';
7866    let currentFrameRate = (level == null ? void 0 : level.frameRate) || 0;
7867    const {
7868      audioPreference,
7869      videoPreference
7870    } = config;
7871    const audioTracksByGroup = this.audioTracksByGroup || (this.audioTracksByGroup = getAudioTracksByGroup(allAudioTracks));
7872    if (firstSelection) {
7873      if (this.firstSelection !== -1) {
7874        return this.firstSelection;
7875      }
7876      const codecTiers = this.codecTiers || (this.codecTiers = getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel));
7877      const startTier = getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference);
7878      const {
7879        codecSet,
7880        videoRanges,
7881        minFramerate,
7882        minBitrate,
7883        preferHDR
7884      } = startTier;
7885      currentCodecSet = codecSet;
7886      currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
7887      currentFrameRate = minFramerate;
7888      currentBw = Math.max(currentBw, minBitrate);
7889      logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
7890    } else {
7891      currentCodecSet = level == null ? void 0 : level.codecSet;
7892      currentVideoRange = level == null ? void 0 : level.videoRange;
7893    }
7894    const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
7895    const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000;
7896    const levelsSkipped = [];
7897    for (let i = maxAutoLevel; i >= minAutoLevel; i--) {
7898      var _levelInfo$supportedR;
7899      const levelInfo = levels[i];
7900      const upSwitch = i > selectionBaseLevel;
7901      if (!levelInfo) {
7902        continue;
7903      }
7904      if (config.useMediaCapabilities && !levelInfo.supportedResult && !levelInfo.supportedPromise) {
7905        const mediaCapabilities = navigator.mediaCapabilities;
7906        if (typeof (mediaCapabilities == null ? void 0 : mediaCapabilities.decodingInfo) === 'function' && requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)) {
7907          levelInfo.supportedPromise = getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities);
7908          levelInfo.supportedPromise.then(decodingInfo => {
7909            if (!this.hls) {
7910              return;
7911            }
7912            levelInfo.supportedResult = decodingInfo;
7913            const levels = this.hls.levels;
7914            const index = levels.indexOf(levelInfo);
7915            if (decodingInfo.error) {
7916              logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
7917            } else if (!decodingInfo.supported) {
7918              logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
7919              if (index > -1 && levels.length > 1) {
7920                logger.log(`[abr] Removing unsupported level ${index}`);
7921                this.hls.removeLevel(index);
7922              }
7923            }
7924          });
7925        } else {
7926          levelInfo.supportedResult = SUPPORTED_INFO_DEFAULT;
7927        }
7928      }
7929
7930      // skip candidates which change codec-family or video-range,
7931      // and which decrease or increase frame-rate for up and down-switch respectfully
7932      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)) {
7933        levelsSkipped.push(i);
7934        continue;
7935      }
7936      const levelDetails = levelInfo.details;
7937      const avgDuration = (partCurrent ? levelDetails == null ? void 0 : levelDetails.partTarget : levelDetails == null ? void 0 : levelDetails.averagetargetduration) || currentFragDuration;
7938      let adjustedbw;
7939      // follow algorithm captured from stagefright :
7940      // https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp
7941      // Pick the highest bandwidth stream below or equal to estimated bandwidth.
7942      // consider only 80% of the available bandwidth, but if we are switching up,
7943      // be even more conservative (70%) to avoid overestimating and immediately
7944      // switching back.
7945      if (!upSwitch) {
7946        adjustedbw = bwFactor * currentBw;
7947      } else {
7948        adjustedbw = bwUpFactor * currentBw;
7949      }
7950
7951      // Use average bitrate when starvation delay (buffer length) is gt or eq tw
7951o segment durations and rebuffering is not expected (maxStarvationDelay > 0)
7952      const bitrate = currentFragDuration && bufferStarvationDelay >= currentFragDuration * 2 && maxStarvationDelay === 0 ? levels[i].averageBitrate : levels[i].maxBitrate;
7953      const fetchDuration = this.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, levelDetails === undefined);
7954      const canSwitchWithinTolerance =
7955      // if adjusted bw is greater than level bitrate AND
7956      adjustedbw >= bitrate && (
7957      // no level change, or new level has no error history
7958      i === lastLoadedFragLevel || levelInfo.loadError === 0 && levelInfo.fragmentError === 0) && (
7959      // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches
7960      // 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 ...
7961      // special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that findBestLevel will return -1
7962      fetchDuration <= ttfbEstimateSec || !isFiniteNumber(fetchDuration) || live && !this.bitrateTestDelay || fetchDuration < maxFetchDuration);
7963      if (canSwitchWithinTolerance) {
7964        const forcedAutoLevel = this.forcedAutoLevel;
7965        if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
7966          if (levelsSkipped.length) {
7967            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}`);
7968          }
7969          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}`);
7970        }
7971        if (firstSelection) {
7972          this.firstSelection = i;
7973        }
7974        // as we are looping from highest to lowest, this will return the best achievable quality level
7975        return i;
7976      }
7977    }
7978    // not enough time budget even with quality level 0 ... rebuffering might happen
7979    return -1;
7980  }
7981  set nextAutoLevel(nextLevel) {
7982    const {
7983      maxAutoLevel,
7984      minAutoLevel
7985    } = this.hls;
7986    const value = Math.min(Math.max(nextLevel, minAutoLevel), maxAutoLevel);
7987    if (this._nextAutoLevel !== value) {
7988      this.nextAutoLevelKey = '';
7989      this._nextAutoLevel = value;
7990    }
7991  }
7992}
7993
7994/**
7995 * @ignore
7996 * Sub-class specialization of EventHandler base class.
7997 *
7998 * TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop,
7999 * scheduled asynchroneously, avoiding recursive calls in the same tick.
8000 *
8001 * The task itself is implemented in `doTick`. It can be requested and called for single execution
8002 * using the `tick` method.
8003 *
8004 * It will be assured that the task execution method (`tick`) only gets called once per main loop "tick",
8005 * no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly.
8006 *
8007 * If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`,
8008 * and cancelled with `clearNextTick`.
8009 *
8010 * The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`).
8011 *
8012 * Sub-classes need to implement the `doTick` method which will effectively have the task execution routine.
8013 *
8014 * Further explanations:
8015 *
8016 * The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously
8017 * only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks.
8018 *
8019 * When the task execution (`tick` method) is called in re-entrant way this is detected and
8020 * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
8021 * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
8022 */
8023class TaskLoop {
8024  constructor() {
8025    this._boundTick = void 0;
8026    this._tickTimer = null;
8027    this._tickInterval = null;
8028    this._tickCallCount = 0;
8029    this._boundTick = this.tick.bind(this);
8030  }
8031  destroy() {
8032    this.onHandlerDestroying();
8033    this.onHandlerDestroyed();
8034  }
8035  onHandlerDestroying() {
8036    // clear all timers before unregistering from event bus
8037    this.clearNextTick();
8038    this.clearInterval();
8039  }
8040  onHandlerDestroyed() {}
8041  hasInterval() {
8042    return !!this._tickInterval;
8043  }
8044  hasNextTick() {
8045    return !!this._tickTimer;
8046  }
8047
8048  /**
8049   * @param millis - Interval time (ms)
8050   * @eturns True when interval has been scheduled, false when already scheduled (no effect)
8051   */
8052  setInterval(millis) {
8053    if (!this._tickInterval) {
8054      this._tickCallCount = 0;
8055      this._tickInterval = self.setInterval(this._boundTick, millis);
8056      return true;
8057    }
8058    return false;
8059  }
8060
8061  /**
8062   * @returns True when interval was cleared, false when none was set (no effect)
8063   */
8064  clearInterval() {
8065    if (this._tickInterval) {
8066      self.clearInterval(this._tickInterval);
8067      this._tickInterval = null;
8068      return true;
8069    }
8070    return false;
8071  }
8072
8073  /**
8074   * @returns True when timeout was cleared, false when none was set (no effect)
8075   */
8076  clearNextTick() {
8077    if (this._tickTimer) {
8078      self.clearTimeout(this._tickTimer);
8079      this._tickTimer = null;
8080      return true;
8081    }
8082    return false;
8083  }
8084
8085  /**
8086   * Will call the subclass doTick implementation in this main loop tick
8087   * or in the next one (via setTimeout(,0)) in case it has already been called
8088   * in this tick (in case this is a re-entrant call).
8089   */
8090  tick() {
8091    this._tickCallCount++;
8092    if (this._tickCallCount === 1) {
8093      this.doTick();
8094      // re-entrant call to tick from previous doTick call stack
8095      // -> schedule a call on the next main loop iteration to process this task processing request
8096      if (this._tickCallCount > 1) {
8097        // make sure only one timer exists at any time at max
8098        this.tickImmediate();
8099      }
8100      this._tickCallCount = 0;
8101    }
8102  }
8103  tickImmediate() {
8104    this.clearNextTick();
8105    this._tickTimer = self.setTimeout(this._boundTick, 0);
8106  }
8107
8108  /**
8109   * For subclass to implement task logic
8110   * @abstract
8111   */
8112  doTick() {}
8113}
8114
8115var FragmentState = {
8116  NOT_LOADED: "NOT_LOADED",
8117  APPENDING: "APPENDING",
8118  PARTIAL: "PARTIAL",
8119  OK: "OK"
8120};
8121class FragmentTracker {
8122  constructor(hls) {
8123    this.activePartLists = Object.create(null);
8124    this.endListFragments = Object.create(null);
8125    this.fragments = Object.create(null);
8126    this.timeRanges = Object.create(null);
8127    this.bufferPadding = 0.2;
8128    this.hls = void 0;
8129    this.hasGaps = false;
8130    this.hls = hls;
8131    this._registerListeners();
8132  }
8133  _registerListeners() {
8134    const {
8135      hls
8136    } = this;
8137    hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
8138    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
8139    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
8140  }
8141  _unregisterListeners() {
8142    const {
8143      hls
8144    } = this;
8145    hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
8146    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
8147    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
8148  }
8149  destroy() {
8150    this._unregisterListeners();
8151    // @ts-ignore
8152    this.fragments =
8153    // @ts-ignore
8154    this.activePartLists =
8155    // @ts-ignore
8156    this.endListFragments = this.timeRanges = null;
8157  }
8158
8159  /**
8160   * Return a Fragment or Part with an appended range that matches the position and levelType
8161   * Otherwise, return null
8162   */
8163  getAppendedFrag(position, levelType) {
8164    const activeParts = this.activePartLists[levelType];
8165    if (activeParts) {
8166      for (let i = activeParts.length; i--;) {
8167        const activePart = activeParts[i];
8168        if (!activePart) {
8169          break;
8170        }
8171        const appendedPTS = activePart.end;
8172        if (activePart.start <= position && appendedPTS !== null && position <= appendedPTS) {
8173          return activePart;
8174        }
8175      }
8176    }
8177    return this.getBufferedFrag(position, levelType);
8178  }
8179
8180  /**
8181   * Return a buffered Fragment that matches the position and levelType.
8182   * A buffered Fragment is one whose loading, parsing and appending is done (completed or "partial" meaning aborted).
8183   * If not found any Fragment, return null
8184   */
8185  getBufferedFrag(position, levelType) {
8186    const {
8187      fragments
8188    } = this;
vendor: 6,101 bytes, lines 8189-8366
8189    const keys = Object.keys(fragments);
8190    for (let i = keys.length; i--;) {
8191      const fragmentEntity = fragments[keys[i]];
8192      if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
8193        const frag = fragmentEntity.body;
8194        if (frag.start <= position && position <= frag.end) {
8195          return frag;
8196        }
8197      }
8198    }
8199    return null;
8200  }
8201
8202  /**
8203   * Partial fragments effected by coded frame eviction will be removed
8204   * The browser will unload parts of the buffer to free up memory for new buffer data
8205   * Fragments will need to be reloaded when the buffer is freed up, removing partial fragments will allow them to reload(since there might be parts that are still playable)
8206   */
8207  detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart) {
8208    if (this.timeRanges) {
8209      this.timeRanges[elementaryStream] = timeRange;
8210    }
8211    // Check if any flagged fragments have been unloaded
8212    // excluding anything newer than appendedPartSn
8213    const appendedPartSn = (appendedPart == null ? void 0 : appendedPart.fragment.sn) || -1;
8214    Object.keys(this.fragments).forEach(key => {
8215      const fragmentEntity = this.fragments[key];
8216      if (!fragmentEntity) {
8217        return;
8218      }
8219      if (appendedPartSn >= fragmentEntity.body.sn) {
8220        return;
8221      }
8222      if (!fragmentEntity.buffered && !fragmentEntity.loaded) {
8223        if (fragmentEntity.body.type === playlistType) {
8224          this.removeFragment(fragmentEntity.body);
8225        }
8226        return;
8227      }
8228      const esData = fragmentEntity.range[elementaryStream];
8229      if (!esData) {
8230        return;
8231      }
8232      esData.time.some(time => {
8233        const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange);
8234        if (isNotBuffered) {
8235          // Unregister partial fragment as it needs to load again to be reused
8236          this.removeFragment(fragmentEntity.body);
8237        }
8238        return isNotBuffered;
8239      });
8240    });
8241  }
8242
8243  /**
8244   * Checks if the fragment passed in is loaded in the buffer properly
8245   * Partially loaded fragments will be registered as a partial fragment
8246   */
8247  detectPartialFragments(data) {
8248    const timeRanges = this.timeRanges;
8249    const {
8250      frag,
8251      part
8252    } = data;
8253    if (!timeRanges || frag.sn === 'initSegment') {
8254      return;
8255    }
8256    const fragKey = getFragmentKey(frag);
8257    const fragmentEntity = this.fragments[fragKey];
8258    if (!fragmentEntity || fragmentEntity.buffered && frag.gap) {
8259      return;
8260    }
8261    const isFragHint = !frag.relurl;
8262    Object.keys(timeRanges).forEach(elementaryStream => {
8263      const streamInfo = frag.elementaryStreams[elementaryStream];
8264      if (!streamInfo) {
8265        return;
8266      }
8267      const timeRange = timeRanges[elementaryStream];
8268      const partial = isFragHint || streamInfo.partial === true;
8269      fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, part, partial, timeRange);
8270    });
8271    fragmentEntity.loaded = null;
8272    if (Object.keys(fragmentEntity.range).length) {
8273      fragmentEntity.buffered = true;
8274      const endList = fragmentEntity.body.endList = frag.endList || fragmentEntity.body.endList;
8275      if (endList) {
8276        this.endListFragments[fragmentEntity.body.type] = fragmentEntity;
8277      }
8278      if (!isPartial(fragmentEntity)) {
8279        // Remove older fragment parts from lookup after frag is tracked as buffered
8280        this.removeParts(frag.sn - 1, frag.type);
8281      }
8282    } else {
8283      // remove fragment if nothing was appended
8284      this.removeFragment(fragmentEntity.body);
8285    }
8286  }
8287  removeParts(snToKeep, levelType) {
8288    const activeParts = this.activePartLists[levelType];
8289    if (!activeParts) {
8290      return;
8291    }
8292    this.activePartLists[levelType] = activeParts.filter(part => part.fragment.sn >= snToKeep);
8293  }
8294  fragBuffered(frag, force) {
8295    const fragKey = getFragmentKey(frag);
8296    let fragmentEntity = this.fragments[fragKey];
8297    if (!fragmentEntity && force) {
8298      fragmentEntity = this.fragments[fragKey] = {
8299        body: frag,
8300        appendedPTS: null,
8301        loaded: null,
8302        buffered: false,
8303        range: Object.create(null)
8304      };
8305      if (frag.gap) {
8306        this.hasGaps = true;
8307      }
8308    }
8309    if (fragmentEntity) {
8310      fragmentEntity.loaded = null;
8311      fragmentEntity.buffered = true;
8312    }
8313  }
8314  getBufferedTimes(fragment, part, partial, timeRange) {
8315    const buffered = {
8316      time: [],
8317      partial
8318    };
8319    const startPTS = fragment.start;
8320    const endPTS = fragment.end;
8321    const minEndPTS = fragment.minEndPTS || endPTS;
8322    const maxStartPTS = fragment.maxStartPTS || startPTS;
8323    for (let i = 0; i < timeRange.length; i++) {
8324      const startTime = timeRange.start(i) - this.bufferPadding;
8325      const endTime = timeRange.end(i) + this.bufferPadding;
8326      if (maxStartPTS >= startTime && minEndPTS <= endTime) {
8327        // Fragment is entirely contained in buffer
8328        // No need to check the other timeRange times since it's completely playable
8329        buffered.time.push({
8330          startPTS: Math.max(startPTS, timeRange.start(i)),
8331          endPTS: Math.min(endPTS, timeRange.end(i))
8332        });
8333        break;
8334      } else if (startPTS < endTime && endPTS > startTime) {
8335        const start = Math.max(startPTS, timeRange.start(i));
8336        const end = Math.min(endPTS, timeRange.end(i));
8337        if (end > start) {
8338          buffered.partial = true;
8339          // Check for intersection with buffer
8340          // Get playable sections of the fragment
8341          buffered.time.push({
8342            startPTS: start,
8343            endPTS: end
8344          });
8345        }
8346      } else if (endPTS <= startTime) {
8347        // No need to check the rest of the timeRange as it is in order
8348        break;
8349      }
8350    }
8351    return buffered;
8352  }
8353
8354  /**
8355   * Gets the partial fragment for a certain time
8356   */
8357  getPartialFragment(time) {
8358    let bestFragment = null;
8359    let timePadding;
8360    let startTime;
8361    let endTime;
8362    let bestOverlap = 0;
8363    const {
8364      bufferPadding,
8365      fragments
8366    } = this;
vendor: 4,068 bytes, lines 8367-8496
8367    Object.keys(fragments).forEach(key => {
8368      const fragmentEntity = fragments[key];
8369      if (!fragmentEntity) {
8370        return;
8371      }
8372      if (isPartial(fragmentEntity)) {
8373        startTime = fragmentEntity.body.start - bufferPadding;
8374        endTime = fragmentEntity.body.end + bufferPadding;
8375        if (time >= startTime && time <= endTime) {
8376          // Use the fragment that has the most padding from start and end time
8377          timePadding = Math.min(time - startTime, endTime - time);
8378          if (bestOverlap <= timePadding) {
8379            bestFragment = fragmentEntity.body;
8380            bestOverlap = timePadding;
8381          }
8382        }
8383      }
8384    });
8385    return bestFragment;
8386  }
8387  isEndListAppended(type) {
8388    const lastFragmentEntity = this.endListFragments[type];
8389    return lastFragmentEntity !== undefined && (lastFragmentEntity.buffered || isPartial(lastFragmentEntity));
8390  }
8391  getState(fragment) {
8392    const fragKey = getFragmentKey(fragment);
8393    const fragmentEntity = this.fragments[fragKey];
8394    if (fragmentEntity) {
8395      if (!fragmentEntity.buffered) {
8396        return FragmentState.APPENDING;
8397      } else if (isPartial(fragmentEntity)) {
8398        return FragmentState.PARTIAL;
8399      } else {
8400        return FragmentState.OK;
8401      }
8402    }
8403    return FragmentState.NOT_LOADED;
8404  }
8405  isTimeBuffered(startPTS, endPTS, timeRange) {
8406    let startTime;
8407    let endTime;
8408    for (let i = 0; i < timeRange.length; i++) {
8409      startTime = timeRange.start(i) - this.bufferPadding;
8410      endTime = timeRange.end(i) + this.bufferPadding;
8411      if (startPTS >= startTime && endPTS <= endTime) {
8412        return true;
8413      }
8414      if (endPTS <= startTime) {
8415        // No need to check the rest of the timeRange as it is in order
8416        return false;
8417      }
8418    }
8419    return false;
8420  }
8421  onFragLoaded(event, data) {
8422    const {
8423      frag,
8424      part
8425    } = data;
8426    // don't track initsegment (for which sn is not a number)
8427    // don't track frags used for bitrateTest, they're irrelevant.
8428    if (frag.sn === 'initSegment' || frag.bitrateTest) {
8429      return;
8430    }
8431
8432    // Fragment entity `loaded` FragLoadedData is null when loading parts
8433    const loaded = part ? null : data;
8434    const fragKey = getFragmentKey(frag);
8435    this.fragments[fragKey] = {
8436      body: frag,
8437      appendedPTS: null,
8438      loaded,
8439      buffered: false,
8440      range: Object.create(null)
8441    };
8442  }
8443  onBufferAppended(event, data) {
8444    const {
8445      frag,
8446      part,
8447      timeRanges
8448    } = data;
8449    if (frag.sn === 'initSegment') {
8450      return;
8451    }
8452    const playlistType = frag.type;
8453    if (part) {
8454      let activeParts = this.activePartLists[playlistType];
8455      if (!activeParts) {
8456        this.activePartLists[playlistType] = activeParts = [];
8457      }
8458      activeParts.push(part);
8459    }
8460    // Store the latest timeRanges loaded in the buffer
8461    this.timeRanges = timeRanges;
8462    Object.keys(timeRanges).forEach(elementaryStream => {
8463      const timeRange = timeRanges[elementaryStream];
8464      this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
8465    });
8466  }
8467  onFragBuffered(event, data) {
8468    this.detectPartialFragments(data);
8469  }
8470  hasFragment(fragment) {
8471    const fragKey = getFragmentKey(fragment);
8472    return !!this.fragments[fragKey];
8473  }
8474  hasParts(type) {
8475    var _this$activePartLists;
8476    return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length);
8477  }
8478  removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly) {
8479    if (withGapOnly && !this.hasGaps) {
8480      return;
8481    }
8482    Object.keys(this.fragments).forEach(key => {
8483      const fragmentEntity = this.fragments[key];
8484      if (!fragmentEntity) {
8485        return;
8486      }
8487      const frag = fragmentEntity.body;
8488      if (frag.type !== playlistType || withGapOnly && !frag.gap) {
8489        return;
8490      }
8491      if (frag.start < end && frag.end > start && (fragmentEntity.buffered || unbufferedOnly)) {
8492        this.removeFragment(frag);
8493      }
8494    });
8495  }
8496  removeFragment(fragment) {
8497    const fragKey = getFragmentKey(fragment);
8498    fragment.stats.loaded = 0;
8499    fragment.clearElementaryStreamInfo();
8500    const activeParts = this.activePartLists[fragment.type];
8501    if (activeParts) {
8502      const snToRemove = fragment.sn;
8503      this.activePartLists[fragment.type] = activeParts.filter(part => part.fragment.sn !== snToRemove);
8504    }
8505    delete this.fragments[fragKey];
8506    if (fragment.endList) {
8507      delete this.endListFragments[fragment.type];
8508    }
8509  }
8510  removeAllFragments() {
8511    this.fragments = Object.create(null);
8512    this.endListFragments = Object.create(null);
8513    this.activePartLists = Object.create(null);
8514    this.hasGaps = false;
8515  }
8516}
8517function isPartial(fragmentEntity) {
8518  var _fragmentEntity$range, _fragmentEntity$range2, _fragmentEntity$range3;
8519  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));
8520}
8521function getFragmentKey(fragment) {
8522  return `${fragment.type}_${fragment.level}_${fragment.sn}`;
8523}
8524
8525/**
8526 * Provides methods dealing with buffer length retrieval for example.
8527 *
8528 * In general, a helper around HTML5 MediaElement TimeRanges gathered from `buffered` property.
8529 *
8530 * Also @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered
8531 */
8532
8533const noopBuffered = {
8534  length: 0,
8535  start: () => 0,
8536  end: () => 0
8537};
8538class BufferHelper {
8539  /**
8540   * Return true if `media`'s buffered include `position`
8541   */
8542  static isBuffered(media, position) {
8543    try {
8544      if (media) {
8545        const buffered = BufferHelper.getBuffered(media);
8546        for (let i = 0; i < buffered.length; i++) {
8547          if (position >= buffered.start(i) && position <= buffered.end(i)) {
8548            return true;
8549          }
8550        }
8551      }
8552    } catch (error) {
8553      // this is to catch
8554      // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
8555      // This SourceBuffer has been removed from the parent media source
8556    }
8557    return false;
8558  }
8559  static bufferInfo(media, pos, maxHoleDuration) {
8560    try {
8561      if (media) {
8562        const vbuffered = BufferHelper.getBuffered(media);
8563        const buffered = [];
8564        let i;
8565        for (i = 0; i < vbuffered.length; i++) {
8566          buffered.push({
8567            start: vbuffered.start(i),
8568            end: vbuffered.end(i)
8569          });
8570        }
8571        return this.bufferedInfo(buffered, pos, maxHoleDuration);
8572      }
8573    } catch (error) {
8574      // this is to catch
8575      // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
8576      // This SourceBuffer has been removed from the parent media source
8577    }
8578    return {
8579      len: 0,
8580      start: pos,
8581      end: pos,
8582      nextStart: undefined
8583    };
8584  }
8585  static bufferedInfo(buffered, pos, maxHoleDuration) {
8586    pos = Math.max(0, pos);
8587    // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
8588    buffered.sort(function (a, b) {
8589      const diff = a.start - b.start;
8590      if (diff) {
8591        return diff;
8592      } else {
8593        return b.end - a.end;
8594      }
8595    });
8596    let buffered2 = [];
8597    if (maxHoleDuration) {
8598      // there might be some small holes between buffer time range
8599      // consider that holes smaller than maxHoleDuration are irrelevant and build another
8600      // buffer time range representations that discards those holes
8601      for (let i = 0; i < buffered.length; i++) {
8602        const buf2len = buffered2.length;
8603        if (buf2len) {
8604          const buf2end = buffered2[buf2len - 1].end;
8605          // if small hole (value between 0 or maxHoleDuration ) or overlapping (negative)
8606          if (buffered[i].start - buf2end < maxHoleDuration) {
8607            // merge overlapping time ranges
8608            // update lastRange.end only if smaller than item.end
8609            // e.g.  [ 1, 15] with  [ 2,8] => [ 1,15] (no need to modify lastRange.end)
8610            // whereas [ 1, 8] with  [ 2,15] => [ 1,15] ( lastRange should switch from [1,8] to [1,15])
8611            if (buffered[i].end > buf2end) {
8612              buffered2[buf2len - 1].end = buffered[i].end;
8613            }
8614          } else {
8615            // big hole
8616            buffered2.push(buffered[i]);
8617          }
8618        } else {
8619          // first value
8620          buffered2.push(buffered[i]);
8621        }
8622      }
8623    } else {
8624      buffered2 = buffered;
8625    }
8626    let bufferLen = 0;
8627
8628    // bufferStartNext can possibly be undefined based on the conditional logic below
8629    let bufferStartNext;
8630
8631    // bufferStart and bufferEnd are buffer boundaries around current video position
8632    let bufferStart = pos;
8633    let bufferEnd = pos;
8634    for (let i = 0; i < buffered2.length; i++) {
8635      const start = buffered2[i].start;
8636      const end = buffered2[i].end;
8637      // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i));
8638      if (pos + maxHoleDuration >= start && pos < end) {
8639        // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length
8640        bufferStart = start;
8641        bufferEnd = end;
8642        bufferLen = bufferEnd - pos;
8643      } else if (pos + maxHoleDuration < start) {
8644        bufferStartNext = start;
8645        break;
8646      }
8647    }
8648    return {
8649      len: bufferLen,
8650      start: bufferStart || 0,
8651      end: bufferEnd || 0,
8652      nextStart: bufferStartNext
8653    };
8654  }
8655
8656  /**
8657   * Safe method to get buffered property.
8658   * SourceBuffer.buffered may throw if SourceBuffer is removed from it's MediaSource
8659   */
8660  static getBuffered(media) {
8661    try {
8662      return media.buffered;
8663    } catch (e) {
8664      logger.log('failed to get media.buffered', e);
8665      return noopBuffered;
8666    }
8667  }
8668}
8669
8670class ChunkMetadata {
8671  constructor(level, sn, id, size = 0, part = -1, partial = false) {
8672    this.level = void 0;
8673    this.sn = void 0;
8674    this.part = void 0;
8675    this.id = void 0;
8676    this.size = void 0;
8677    this.partial = void 0;
8678    this.transmuxing = getNewPerformanceTiming();
8679    this.buffering = {
8680      audio: getNewPerformanceTiming(),
8681      video: getNewPerformanceTiming(),
8682      audiovideo: getNewPerformanceTiming()
8683    };
8684    this.level = level;
8685    this.sn = sn;
8686    this.id = id;
8687    this.size = size;
8688    this.part = part;
8689    this.partial = partial;
8690  }
8691}
8692function getNewPerformanceTiming() {
8693  return {
8694    start: 0,
8695    executeStart: 0,
8696    executeEnd: 0,
8697    end: 0
8698  };
8699}
8700
8701function findFirstFragWithCC(fragments, cc) {
8702  for (let i = 0, len = fragments.length; i < len; i++) {
8703    var _fragments$i;
8704    if (((_fragments$i = fragments[i]) == null ? void 0 : _fragments$i.cc) === cc) {
8705      return fragments[i];
8706    }
8707  }
8708  return null;
8709}
8710function shouldAlignOnDiscontinuities(lastFrag, switchDetails, details) {
8711  if (switchDetails) {
8712    if (details.endCC > details.startCC || lastFrag && lastFrag.cc < details.startCC) {
8713      return true;
8714    }
8715  }
8716  return false;
8717}
8718
8719// Find the first frag in the previous level which matches the CC of the first frag of the new level
8720function findDiscontinuousReferenceFrag(prevDetails, curDetails) {
8721  const prevFrags = prevDetails.fragments;
8722  const curFrags = curDetails.fragments;
8723  if (!curFrags.length || !prevFrags.length) {
8724    logger.log('No fragments to align');
8725    return;
8726  }
8727  const prevStartFrag = findFirstFragWithCC(prevFrags, curFrags[0].cc);
8728  if (!prevStartFrag || prevStartFrag && !prevStartFrag.startPTS) {
8729    logger.log('No frag in previous level to align on');
8730    return;
8731  }
8732  return prevStartFrag;
8733}
8734function adjustFragmentStart(frag, sliding) {
8735  if (frag) {
8736    const start = frag.start + sliding;
8737    frag.start = frag.startPTS = start;
8738    frag.endPTS = start + frag.duration;
8739  }
8740}
8741function adjustSlidingStart(sliding, details) {
8742  // Update segments
8743  const fragments = details.fragments;
8744  for (let i = 0, len = fragments.length; i < len; i++) {
8745    adjustFragmentStart(fragments[i], sliding);
8746  }
8747  // Update LL-HLS parts at the end of the playlist
8748  if (details.fragmentHint) {
8749    adjustFragmentStart(details.fragmentHint, sliding);
8750  }
8751  details.alignedSliding = true;
8752}
8753
8754/**
8755 * Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a
8756 * contiguous stream with the last fragments.
8757 * The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to
8758 * download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time
8759 * and an extra download.
8760 * @param lastFrag
8761 * @param lastLevel
8762 * @param details
8763 */
8764function alignStream(lastFrag, switchDetails, details) {
8765  if (!switchDetails) {
8766    return;
8767  }
8768  alignDiscontinuities(lastFrag, details, switchDetails);
8769  if (!details.alignedSliding && switchDetails) {
8770    // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level.
8771    // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same
8772    // discontinuity sequence.
8773    alignMediaPlaylistByPDT(details, switchDetails);
8774  }
8775  if (!details.alignedSliding && switchDetails && !details.skippedSegments) {
8776    // Try to align on sn so that we pick a better start fragment.
8777    // Do not perform this on playlists with delta updates as this is only to align levels on switch
8778    // and adjustSliding only adjusts fragments after skippedSegments.
8779    adjustSliding(switchDetails, details);
8780  }
8781}
8782
8783/**
8784 * Computes the PTS if a new level's fragments using the PTS of a fragment in the last level which shares the same
8785 * discontinuity sequence.
8786 * @param lastFrag - The last Fragment which shares the same discontinuity sequence
8787 * @param lastLevel - The details of the last loaded level
8788 * @param details - The details of the new level
8789 */
8790function alignDiscontinuities(lastFrag, details, switchDetails) {
8791  if (shouldAlignOnDiscontinuities(lastFrag, switchDetails, details)) {
8792    const referenceFrag = findDiscontinuousReferenceFrag(switchDetails, details);
8793    if (referenceFrag && isFiniteNumber(referenceFrag.start)) {
8794      logger.log(`Adjusting PTS using last level due to CC increase within current level ${details.url}`);
8795      adjustSlidingStart(referenceFrag.start, details);
8796    }
8797  }
8798}
8799
8800/**
8801 * Ensures appropriate time-alignment between renditions based on PDT.
8802 * This function assumes the timelines represented in `refDetails` are accurate, including the PDTs
8803 * for the last discontinuity sequence number shared by both playlists when present,
8804 * and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation
8805 * times/timelines of `details` accordingly.
8806 * Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks,
8807 * the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks
8808 * are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should
8809 * be consistent across playlists, per the HLS spec.
8810 * @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition).
8811 * @param refDetails - The details of the reference rendition with start and PDT times for alignment.
8812 */
8813function alignMediaPlaylistByPDT(details, refDetails) {
vendor: 9,821 bytes, lines 8814-9109
8814  if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) {
8815    return;
8816  }
8817  const fragments = details.fragments;
8818  const refFragments = refDetails.fragments;
8819  if (!fragments.length || !refFragments.length) {
8820    return;
8821  }
8822
8823  // Calculate a delta to apply to all fragments according to the delta in PDT times and start times
8824  // of a fragment in the reference details, and a fragment in the target details of the same discontinuity.
8825  // If a fragment of the same discontinuity was not found use the middle fragment of both.
8826  let refFrag;
8827  let frag;
8828  const targetCC = Math.min(refDetails.endCC, details.endCC);
8829  if (refDetails.startCC < targetCC && details.startCC < targetCC) {
8830    refFrag = findFirstFragWithCC(refFragments, targetCC);
8831    frag = findFirstFragWithCC(fragments, targetCC);
8832  }
8833  if (!refFrag || !frag) {
8834    refFrag = refFragments[Math.floor(refFragments.length / 2)];
8835    frag = findFirstFragWithCC(fragments, refFrag.cc) || fragments[Math.floor(fragments.length / 2)];
8836  }
8837  const refPDT = refFrag.programDateTime;
8838  const targetPDT = frag.programDateTime;
8839  if (!refPDT || !targetPDT) {
8840    return;
8841  }
8842  const delta = (targetPDT - refPDT) / 1000 - (frag.start - refFrag.start);
8843  adjustSlidingStart(delta, details);
8844}
8845
8846const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb
8847
8848class FragmentLoader {
8849  constructor(config) {
8850    this.config = void 0;
8851    this.loader = null;
8852    this.partLoadTimeout = -1;
8853    this.config = config;
8854  }
8855  destroy() {
8856    if (this.loader) {
8857      this.loader.destroy();
8858      this.loader = null;
8859    }
8860  }
8861  abort() {
8862    if (this.loader) {
8863      // Abort the loader for current fragment. Only one may load at any given time
8864      this.loader.abort();
8865    }
8866  }
8867  load(frag, onProgress) {
8868    const url = frag.url;
8869    if (!url) {
8870      return Promise.reject(new LoadError({
8871        type: ErrorTypes.NETWORK_ERROR,
8872        details: ErrorDetails.FRAG_LOAD_ERROR,
8873        fatal: false,
8874        frag,
8875        error: new Error(`Fragment does not have a ${url ? 'part list' : 'url'}`),
8876        networkDetails: null
8877      }));
8878    }
8879    this.abort();
8880    const config = this.config;
8881    const FragmentILoader = config.fLoader;
8882    const DefaultILoader = config.loader;
8883    return new Promise((resolve, reject) => {
8884      if (this.loader) {
8885        this.loader.destroy();
8886      }
8887      if (frag.gap) {
8888        if (frag.tagList.some(tags => tags[0] === 'GAP')) {
8889          reject(createGapLoadError(frag));
8890          return;
8891        } else {
8892          // Reset temporary treatment as GAP tag
8893          frag.gap = false;
8894        }
8895      }
8896      const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
8897      const loaderContext = createLoaderContext(frag);
8898      const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
8899      const loaderConfig = {
8900        loadPolicy,
8901        timeout: loadPolicy.maxLoadTimeMs,
8902        maxRetry: 0,
8903        retryDelay: 0,
8904        maxRetryDelay: 0,
8905        highWaterMark: frag.sn === 'initSegment' ? Infinity : MIN_CHUNK_SIZE
8906      };
8907      // Assign frag stats to the loader's stats reference
8908      frag.stats = loader.stats;
8909      loader.load(loaderContext, loaderConfig, {
8910        onSuccess: (response, stats, context, networkDetails) => {
8911          this.resetLoader(frag, loader);
8912          let payload = response.data;
8913          if (context.resetIV && frag.decryptdata) {
8914            frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16));
8915            payload = payload.slice(16);
8916          }
8917          resolve({
8918            frag,
8919            part: null,
8920            payload,
8921            networkDetails
8922          });
8923        },
8924        onError: (response, context, networkDetails, stats) => {
8925          this.resetLoader(frag, loader);
8926          reject(new LoadError({
8927            type: ErrorTypes.NETWORK_ERROR,
8928            details: ErrorDetails.FRAG_LOAD_ERROR,
8929            fatal: false,
8930            frag,
8931            response: _objectSpread2({
8932              url,
8933              data: undefined
8934            }, response),
8935            error: new Error(`HTTP Error ${response.code} ${response.text}`),
8936            networkDetails,
8937            stats
8938          }));
8939        },
8940        onAbort: (stats, context, networkDetails) => {
8941          this.resetLoader(frag, loader);
8942          reject(new LoadError({
8943            type: ErrorTypes.NETWORK_ERROR,
8944            details: ErrorDetails.INTERNAL_ABORTED,
8945            fatal: false,
8946            frag,
8947            error: new Error('Aborted'),
8948            networkDetails,
8949            stats
8950          }));
8951        },
8952        onTimeout: (stats, context, networkDetails) => {
8953          this.resetLoader(frag, loader);
8954          reject(new LoadError({
8955            type: ErrorTypes.NETWORK_ERROR,
8956            details: ErrorDetails.FRAG_LOAD_TIMEOUT,
8957            fatal: false,
8958            frag,
8959            error: new Error(`Timeout after ${loaderConfig.timeout}ms`),
8960            networkDetails,
8961            stats
8962          }));
8963        },
8964        onProgress: (stats, context, data, networkDetails) => {
8965          if (onProgress) {
8966            onProgress({
8967              frag,
8968              part: null,
8969              payload: data,
8970              networkDetails
8971            });
8972          }
8973        }
8974      });
8975    });
8976  }
8977  loadPart(frag, part, onProgress) {
8978    this.abort();
8979    const config = this.config;
8980    const FragmentILoader = config.fLoader;
8981    const DefaultILoader = config.loader;
8982    return new Promise((resolve, reject) => {
8983      if (this.loader) {
8984        this.loader.destroy();
8985      }
8986      if (frag.gap || part.gap) {
8987        reject(createGapLoadError(frag, part));
8988        return;
8989      }
8990      const loader = this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
8991      const loaderContext = createLoaderContext(frag, part);
8992      // Should we define another load policy for parts?
8993      const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
8994      const loaderConfig = {
8995        loadPolicy,
8996        timeout: loadPolicy.maxLoadTimeMs,
8997        maxRetry: 0,
8998        retryDelay: 0,
8999        maxRetryDelay: 0,
9000        highWaterMark: MIN_CHUNK_SIZE
9001      };
9002      // Assign part stats to the loader's stats reference
9003      part.stats = loader.stats;
9004      loader.load(loaderContext, loaderConfig, {
9005        onSuccess: (response, stats, context, networkDetails) => {
9006          this.resetLoader(frag, loader);
9007          this.updateStatsFromPart(frag, part);
9008          const partLoadedData = {
9009            frag,
9010            part,
9011            payload: response.data,
9012            networkDetails
9013          };
9014          onProgress(partLoadedData);
9015          resolve(partLoadedData);
9016        },
9017        onError: (response, context, networkDetails, stats) => {
9018          this.resetLoader(frag, loader);
9019          reject(new LoadError({
9020            type: ErrorTypes.NETWORK_ERROR,
9021            details: ErrorDetails.FRAG_LOAD_ERROR,
9022            fatal: false,
9023            frag,
9024            part,
9025            response: _objectSpread2({
9026              url: loaderContext.url,
9027              data: undefined
9028            }, response),
9029            error: new Error(`HTTP Error ${response.code} ${response.text}`),
9030            networkDetails,
9031            stats
9032          }));
9033        },
9034        onAbort: (stats, context, networkDetails) => {
9035          frag.stats.aborted = part.stats.aborted;
9036          this.resetLoader(frag, loader);
9037          reject(new LoadError({
9038            type: ErrorTypes.NETWORK_ERROR,
9039            details: ErrorDetails.INTERNAL_ABORTED,
9040            fatal: false,
9041            frag,
9042            part,
9043            error: new Error('Aborted'),
9044            networkDetails,
9045            stats
9046          }));
9047        },
9048        onTimeout: (stats, context, networkDetails) => {
9049          this.resetLoader(frag, loader);
9050          reject(new LoadError({
9051            type: ErrorTypes.NETWORK_ERROR,
9052            details: ErrorDetails.FRAG_LOAD_TIMEOUT,
9053            fatal: false,
9054            frag,
9055            part,
9056            error: new Error(`Timeout after ${loaderConfig.timeout}ms`),
9057            networkDetails,
9058            stats
9059          }));
9060        }
9061      });
9062    });
9063  }
9064  updateStatsFromPart(frag, part) {
9065    const fragStats = frag.stats;
9066    const partStats = part.stats;
9067    const partTotal = partStats.total;
9068    fragStats.loaded += partStats.loaded;
9069    if (partTotal) {
9070      const estTotalParts = Math.round(frag.duration / part.duration);
9071      const estLoadedParts = Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts);
9072      const estRemainingParts = estTotalParts - estLoadedParts;
9073      const estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts);
9074      fragStats.total = fragStats.loaded + estRemainingBytes;
9075    } else {
9076      fragStats.total = Math.max(fragStats.loaded, fragStats.total);
9077    }
9078    const fragLoading = fragStats.loading;
9079    const partLoading = partStats.loading;
9080    if (fragLoading.start) {
9081      // add to fragment loader latency
9082      fragLoading.first += partLoading.first - partLoading.start;
9083    } else {
9084      fragLoading.start = partLoading.start;
9085      fragLoading.first = partLoading.first;
9086    }
9087    fragLoading.end = partLoading.end;
9088  }
9089  resetLoader(frag, loader) {
9090    frag.loader = null;
9091    if (this.loader === loader) {
9092      self.clearTimeout(this.partLoadTimeout);
9093      this.loader = null;
9094    }
9095    loader.destroy();
9096  }
9097}
9098function createLoaderContext(frag, part = null) {
9099  const segment = part || frag;
9100  const loaderContext = {
9101    frag,
9102    part,
9103    responseType: 'arraybuffer',
9104    url: segment.url,
9105    headers: {},
9106    rangeStart: 0,
9107    rangeEnd: 0
9108  };
9109  const start = segment.byteRangeStartOffset;
9110  const end = segment.byteRangeEndOffset;
9111  if (isFiniteNumber(start) && isFiniteNumber(end)) {
9112    var _frag$decryptdata;
9113    let byteRangeStart = start;
9114    let byteRangeEnd = end;
9115    if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
9116      // MAP segment encrypted with method 'AES-128', when served with HTTP Range,
9117      // has the unencrypted size specified in the range.
9118      // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
9119      const fragmentLen = end - start;
9120      if (fragmentLen % 16) {
9121        byteRangeEnd = end + (16 - fragmentLen % 16);
9122      }
9123      if (start !== 0) {
9124        loaderContext.resetIV = true;
9125        byteRangeStart = start - 16;
9126      }
9127    }
9128    loaderContext.rangeStart = byteRangeStart;
9129    loaderContext.rangeEnd = byteRangeEnd;
9130  }
9131  return loaderContext;
9132}
9133function createGapLoadError(frag, part) {
9134  const error = new Error(`GAP ${frag.gap ? 'tag' : 'attribute'} found`);
9135  const errorData = {
9136    type: ErrorTypes.MEDIA_ERROR,
9137    details: ErrorDetails.FRAG_GAP,
9138    fatal: false,
9139    frag,
9140    error,
9141    networkDetails: null
9142  };
9143  if (part) {
9144    errorData.part = part;
9145  }
9146  (part ? part : frag).stats.aborted = true;
9147  return new LoadError(errorData);
9148}
9149class LoadError extends Error {
9150  constructor(data) {
9151    super(data.error.message);
9152    this.data = void 0;
9153    this.data = data;
9154  }
9155}
9156
9157class AESCrypto {
9158  constructor(subtle, iv) {
9159    this.subtle = void 0;
9160    this.aesIV = void 0;
9161    this.subtle = subtle;
9162    this.aesIV = iv;
9163  }
9164  decrypt(data, key) {
9165    return this.subtle.decrypt({
9166      name: 'AES-CBC',
9167      iv: this.aesIV
9168    }, key, data);
9169  }
9170}
9171
9172class FastAESKey {
9173  constructor(subtle, key) {
9174    this.subtle = void 0;
9175    this.key = void 0;
9176    this.subtle = subtle;
9177    this.key = key;
9178  }
9179  expandKey() {
9180    return this.subtle.importKey('raw', this.key, {
9181      name: 'AES-CBC'
9182    }, false, ['encrypt', 'decrypt']);
9183  }
9184}
9185
9186// PKCS7
9187function removePadding(array) {
9188  const outputBytes = array.byteLength;
9189  const paddingBytes = outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1);
9190  if (paddingBytes) {
9191    return sliceUint8(array, 0, outputBytes - paddingBytes);
9192  }
9193  return array;
9194}
9195class AESDecryptor {
9196  constructor() {
9197    this.rcon = [0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
9198    this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
9199    this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
9200    this.sBox = new Uint32Array(256);
9201    this.invSBox = new Uint32Array(256);
9202    this.key = new Uint32Array(0);
9203    this.ksRows = 0;
9204    this.keySize = 0;
9205    this.keySchedule = void 0;
9206    this.invKeySchedule = void 0;
9207    this.initTable();
9208  }
9209
9210  // Using view.getUint32() also swaps the byte order.
9211  uint8ArrayToUint32Array_(arrayBuffer) {
9212    const view = new DataView(arrayBuffer);
9213    const newArray = new Uint32Array(4);
9214    for (let i = 0; i < 4; i++) {
9215      newArray[i] = view.getUint32(i * 4);
9216    }
9217    return newArray;
9218  }
9219  initTable() {
9220    const sBox = this.sBox;
9221    const invSBox = this.invSBox;
9222    const subMix = this.subMix;
9223    const subMix0 = subMix[0];
9224    const subMix1 = subMix[1];
9225    const subMix2 = subMix[2];
9226    const subMix3 = subMix[3];
9227    const invSubMix = this.invSubMix;
9228    const invSubMix0 = invSubMix[0];
9229    const invSubMix1 = invSubMix[1];
9230    const invSubMix2 = invSubMix[2];
9231    const invSubMix3 = invSubMix[3];
9232    const d = new Uint32Array(256);
9233    let x = 0;
9234    let xi = 0;
9235    let i = 0;
9236    for (i = 0; i < 256; i++) {
9237      if (i < 128) {
9238        d[i] = i << 1;
9239      } else {
9240        d[i] = i << 1 ^ 0x11b;
9241      }
9242    }
9243    for (i = 0; i < 256; i++) {
9244      let sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4;
9245      sx = sx >>> 8 ^ sx & 0xff ^ 0x63;
9246      sBox[x] = sx;
9247      invSBox[sx] = x;
9248
9249      // Compute multiplication
9250      const x2 = d[x];
9251      const x4 = d[x2];
9252      const x8 = d[x4];
9253
9254      // Compute sub/invSub bytes, mix columns tables
9255      let t = d[sx] * 0x101 ^ sx * 0x1010100;
9256      subMix0[x] = t << 24 | t >>> 8;
9257      subMix1[x] = t << 16 | t >>> 16;
9258      subMix2[x] = t << 8 | t >>> 24;
9259      subMix3[x] = t;
9260
9261      // Compute inv sub bytes, inv mix columns tables
vendor: 6,515 bytes, lines 9262-9433
9262      t = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
9263      invSubMix0[sx] = t << 24 | t >>> 8;
9264      invSubMix1[sx] = t << 16 | t >>> 16;
9265      invSubMix2[sx] = t << 8 | t >>> 24;
9266      invSubMix3[sx] = t;
9267
9268      // Compute next counter
9269      if (!x) {
9270        x = xi = 1;
9271      } else {
9272        x = x2 ^ d[d[d[x8 ^ x2]]];
9273        xi ^= d[d[xi]];
9274      }
9275    }
9276  }
9277  expandKey(keyBuffer) {
9278    // convert keyBuffer to Uint32Array
9279    const key = this.uint8ArrayToUint32Array_(keyBuffer);
9280    let sameKey = true;
9281    let offset = 0;
9282    while (offset < key.length && sameKey) {
9283      sameKey = key[offset] === this.key[offset];
9284      offset++;
9285    }
9286    if (sameKey) {
9287      return;
9288    }
9289    this.key = key;
9290    const keySize = this.keySize = key.length;
9291    if (keySize !== 4 && keySize !== 6 && keySize !== 8) {
9292      throw new Error('Invalid aes key size=' + keySize);
9293    }
9294    const ksRows = this.ksRows = (keySize + 6 + 1) * 4;
9295    let ksRow;
9296    let invKsRow;
9297    const keySchedule = this.keySchedule = new Uint32Array(ksRows);
9298    const invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows);
9299    const sbox = this.sBox;
9300    const rcon = this.rcon;
9301    const invSubMix = this.invSubMix;
9302    const invSubMix0 = invSubMix[0];
9303    const invSubMix1 = invSubMix[1];
9304    const invSubMix2 = invSubMix[2];
9305    const invSubMix3 = invSubMix[3];
9306    let prev;
9307    let t;
9308    for (ksRow = 0; ksRow < ksRows; ksRow++) {
9309      if (ksRow < keySize) {
9310        prev = keySchedule[ksRow] = key[ksRow];
9311        continue;
9312      }
9313      t = prev;
9314      if (ksRow % keySize === 0) {
9315        // Rot word
9316        t = t << 8 | t >>> 24;
9317
9318        // Sub word
9319        t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];
9320
9321        // Mix Rcon
9322        t ^= rcon[ksRow / keySize | 0] << 24;
9323      } else if (keySize > 6 && ksRow % keySize === 4) {
9324        // Sub word
9325        t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];
9326      }
9327      keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0;
9328    }
9329    for (invKsRow = 0; invKsRow < ksRows; invKsRow++) {
9330      ksRow = ksRows - invKsRow;
9331      if (invKsRow & 3) {
9332        t = keySchedule[ksRow];
9333      } else {
9334        t = keySchedule[ksRow - 4];
9335      }
9336      if (invKsRow < 4 || ksRow <= 4) {
9337        invKeySchedule[invKsRow] = t;
9338      } else {
9339        invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 0xff]] ^ invSubMix2[sbox[t >>> 8 & 0xff]] ^ invSubMix3[sbox[t & 0xff]];
9340      }
9341      invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0;
9342    }
9343  }
9344
9345  // Adding this as a method greatly improves performance.
9346  networkToHostOrderSwap(word) {
9347    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
9348  }
9349  decrypt(inputArrayBuffer, offset, aesIV) {
9350    const nRounds = this.keySize + 6;
9351    const invKeySchedule = this.invKeySchedule;
9352    const invSBOX = this.invSBox;
9353    const invSubMix = this.invSubMix;
9354    const invSubMix0 = invSubMix[0];
9355    const invSubMix1 = invSubMix[1];
9356    const invSubMix2 = invSubMix[2];
9357    const invSubMix3 = invSubMix[3];
9358    const initVector = this.uint8ArrayToUint32Array_(aesIV);
9359    let initVector0 = initVector[0];
9360    let initVector1 = initVector[1];
9361    let initVector2 = initVector[2];
9362    let initVector3 = initVector[3];
9363    const inputInt32 = new Int32Array(inputArrayBuffer);
9364    const outputInt32 = new Int32Array(inputInt32.length);
9365    let t0, t1, t2, t3;
9366    let s0, s1, s2, s3;
9367    let inputWords0, inputWords1, inputWords2, inputWords3;
9368    let ksRow, i;
9369    const swapWord = this.networkToHostOrderSwap;
9370    while (offset < inputInt32.length) {
9371      inputWords0 = swapWord(inputInt32[offset]);
9372      inputWords1 = swapWord(inputInt32[offset + 1]);
9373      inputWords2 = swapWord(inputInt32[offset + 2]);
9374      inputWords3 = swapWord(inputInt32[offset + 3]);
9375      s0 = inputWords0 ^ invKeySchedule[0];
9376      s1 = inputWords3 ^ invKeySchedule[1];
9377      s2 = inputWords2 ^ invKeySchedule[2];
9378      s3 = inputWords1 ^ invKeySchedule[3];
9379      ksRow = 4;
9380
9381      // Iterate through the rounds of decryption
9382      for (i = 1; i < nRounds; i++) {
9383        t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 0xff] ^ invSubMix2[s2 >> 8 & 0xff] ^ invSubMix3[s3 & 0xff] ^ invKeySchedule[ksRow];
9384        t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 0xff] ^ invSubMix2[s3 >> 8 & 0xff] ^ invSubMix3[s0 & 0xff] ^ invKeySchedule[ksRow + 1];
9385        t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 0xff] ^ invSubMix2[s0 >> 8 & 0xff] ^ invSubMix3[s1 & 0xff] ^ invKeySchedule[ksRow + 2];
9386        t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 0xff] ^ invSubMix2[s1 >> 8 & 0xff] ^ invSubMix3[s2 & 0xff] ^ invKeySchedule[ksRow + 3];
9387        // Update state
9388        s0 = t0;
9389        s1 = t1;
9390        s2 = t2;
9391        s3 = t3;
9392        ksRow = ksRow + 4;
9393      }
9394
9395      // Shift rows, sub bytes, add round key
9396      t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 0xff] << 16 ^ invSBOX[s2 >> 8 & 0xff] << 8 ^ invSBOX[s3 & 0xff] ^ invKeySchedule[ksRow];
9397      t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 0xff] << 16 ^ invSBOX[s3 >> 8 & 0xff] << 8 ^ invSBOX[s0 & 0xff] ^ invKeySchedule[ksRow + 1];
9398      t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 0xff] << 16 ^ invSBOX[s0 >> 8 & 0xff] << 8 ^ invSBOX[s1 & 0xff] ^ invKeySchedule[ksRow + 2];
9399      t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 0xff] << 16 ^ invSBOX[s1 >> 8 & 0xff] << 8 ^ invSBOX[s2 & 0xff] ^ invKeySchedule[ksRow + 3];
9400
9401      // Write
9402      outputInt32[offset] = swapWord(t0 ^ initVector0);
9403      outputInt32[offset + 1] = swapWord(t3 ^ initVector1);
9404      outputInt32[offset + 2] = swapWord(t2 ^ initVector2);
9405      outputInt32[offset + 3] = swapWord(t1 ^ initVector3);
9406
9407      // reset initVector to last 4 unsigned int
9408      initVector0 = inputWords0;
9409      initVector1 = inputWords1;
9410      initVector2 = inputWords2;
9411      initVector3 = inputWords3;
9412      offset = offset + 4;
9413    }
9414    return outputInt32.buffer;
9415  }
9416}
9417
9418const CHUNK_SIZE = 16; // 16 bytes, 128 bits
9419
9420class Decrypter {
9421  constructor(config, {
9422    removePKCS7Padding = true
9423  } = {}) {
9424    this.logEnabled = true;
9425    this.removePKCS7Padding = void 0;
9426    this.subtle = null;
9427    this.softwareDecrypter = null;
9428    this.key = null;
9429    this.fastAesKey = null;
9430    this.remainderData = null;
9431    this.currentIV = null;
9432    this.currentResult = null;
9433    this.useSoftware = void 0;
9434    this.useSoftware = config.enableSoftwareAES;
9435    this.removePKCS7Padding = removePKCS7Padding;
9436    // built in decryptor expects PKCS7 padding
9437    if (removePKCS7Padding) {
9438      try {
9439        const browserCrypto = self.crypto;
9440        if (browserCrypto) {
9441          this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
9442        }
9443      } catch (e) {
9444        /* no-op */
9445      }
9446    }
9447    this.useSoftware = !this.subtle;
9448  }
9449  destroy() {
9450    this.subtle = null;
9451    this.softwareDecrypter = null;
9452    this.key = null;
9453    this.fastAesKey = null;
9454    this.remainderData = null;
9455    this.currentIV = null;
9456    this.currentResult = null;
9457  }
9458  isSync() {
9459    return this.useSoftware;
9460  }
9461  flush() {
9462    const {
9463      currentResult,
9464      remainderData
9465    } = this;
9466    if (!currentResult || remainderData) {
9467      this.reset();
9468      return null;
9469    }
9470    const data = new Uint8Array(currentResult);
9471    this.reset();
9472    if (this.removePKCS7Padding) {
9473      return removePadding(data);
9474    }
9475    return data;
9476  }
9477  reset() {
9478    this.currentResult = null;
9479    this.currentIV = null;
9480    this.remainderData = null;
9481    if (this.softwareDecrypter) {
9482      this.softwareDecrypter = null;
9483    }
9484  }
9485  decrypt(data, key, iv) {
9486    if (this.useSoftware) {
9487      return new Promise((resolve, reject) => {
9488        this.softwareDecrypt(new Uint8Array(data), key, iv);
9489        const decryptResult = this.flush();
9490        if (decryptResult) {
9491          resolve(decryptResult.buffer);
9492        } else {
9493          reject(new Error('[softwareDecrypt] Failed to decrypt data'));
9494        }
9495      });
9496    }
9497    return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
9498  }
9499
9500  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
9501  // data is handled in the flush() call
9502  softwareDecrypt(data, key, iv) {
9503    const {
9504      currentIV,
9505      currentResult,
9506      remainderData
9507    } = this;
9508    this.logOnce('JS AES decrypt');
9509    // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
9510    // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
9511    // the end on flush(), but by that time we have already received all bytes for the segment.
9512    // Progressive decryption does not work with WebCrypto
9513
9514    if (remainderData) {
9515      data = appendUint8Array(remainderData, data);
9516      this.remainderData = null;
9517    }
9518
9519    // Byte length must be a multiple of 16 (AES-128 = 128 bit blocks = 16 bytes)
9520    const currentChunk = this.getValidChunk(data);
9521    if (!currentChunk.length) {
9522      return null;
9523    }
9524    if (currentIV) {
9525      iv = currentIV;
9526    }
9527    let softwareDecrypter = this.softwareDecrypter;
9528    if (!softwareDecrypter) {
9529      softwareDecrypter = this.softwareDecrypter = new AESDecryptor();
9530    }
9531    softwareDecrypter.expandKey(key);
9532    const result = currentResult;
9533    this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv);
9534    this.currentIV = sliceUint8(currentChunk, -16).buffer;
9535    if (!result) {
9536      return null;
9537    }
9538    return result;
9539  }
9540  webCryptoDecrypt(data, key, iv) {
9541    if (this.key !== key || !this.fastAesKey) {
9542      if (!this.subtle) {
9543        return Promise.resolve(this.onWebCryptoError(data, key, iv));
9544      }
9545      this.key = key;
9546      this.fastAesKey = new FastAESKey(this.subtle, key);
9547    }
9548    return this.fastAesKey.expandKey().then(aesKey => {
9549      // decrypt using web crypto
9550      if (!this.subtle) {
9551        return Promise.reject(new Error('web crypto not initialized'));
9552      }
9553      this.logOnce('WebCrypto AES decrypt');
9554      const crypto = new AESCrypto(this.subtle, new Uint8Array(iv));
9555      return crypto.decrypt(data.buffer, aesKey);
9556    }).catch(err => {
9557      logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
9558      return this.onWebCryptoError(data, key, iv);
9559    });
9560  }
9561  onWebCryptoError(data, key, iv) {
9562    this.useSoftware = true;
9563    this.logEnabled = true;
9564    this.softwareDecrypt(data, key, iv);
9565    const decryptResult = this.flush();
9566    if (decryptResult) {
9567      return decryptResult.buffer;
9568    }
9569    throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
9570  }
9571  getValidChunk(data) {
9572    let currentChunk = data;
9573    const splitPoint = data.length - data.length % CHUNK_SIZE;
9574    if (splitPoint !== data.length) {
9575      currentChunk = sliceUint8(data, 0, splitPoint);
9576      this.remainderData = sliceUint8(data, splitPoint);
9577    }
9578    return currentChunk;
9579  }
9580  logOnce(msg) {
9581    if (!this.logEnabled) {
9582      return;
9583    }
9584    logger.log(`[decrypter]: ${msg}`);
9585    this.logEnabled = false;
9586  }
9587}
9588
9589/**
9590 *  TimeRanges to string helper
9591 */
9592
9593const TimeRanges = {
9594  toString: function (r) {
9595    let log = '';
9596    const len = r.length;
9597    for (let i = 0; i < len; i++) {
9598      log += `[${r.start(i).toFixed(3)}-${r.end(i).toFixed(3)}]`;
9599    }
9600    return log;
9601  }
9602};
9603
9604const State = {
9605  STOPPED: 'STOPPED',
9606  IDLE: 'IDLE',
9607  KEY_LOADING: 'KEY_LOADING',
9608  FRAG_LOADING: 'FRAG_LOADING',
9609  FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',
9610  WAITING_TRACK: 'WAITING_TRACK',
9611  PARSING: 'PARSING',
9612  PARSED: 'PARSED',
9613  ENDED: 'ENDED',
9614  ERROR: 'ERROR',
9615  WAITING_INIT_PTS: 'WAITING_INIT_PTS',
9616  WAITING_LEVEL: 'WAITING_LEVEL'
9617};
9618class BaseStreamController extends TaskLoop {
9619  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
9620    super();
9621    this.hls = void 0;
9622    this.fragPrevious = null;
9623    this.fragCurrent = null;
9624    this.fragmentTracker = void 0;
9625    this.transmuxer = null;
9626    this._state = State.STOPPED;
9627    this.playlistType = void 0;
9628    this.media = null;
9629    this.mediaBuffer = null;
9630    this.config = void 0;
9631    this.bitrateTest = false;
9632    this.lastCurrentTime = 0;
9633    this.nextLoadPosition = 0;
9634    this.startPosition = 0;
9635    this.startTimeOffset = null;
9636    this.loadedmetadata = false;
9637    this.retryDate = 0;
9638    this.levels = null;
9639    this.fragmentLoader = void 0;
9640    this.keyLoader = void 0;
9641    this.levelLastLoaded = null;
9642    this.startFragRequested = false;
9643    this.decrypter = void 0;
9644    this.initPTS = [];
9645    this.onvseeking = null;
9646    this.onvended = null;
9647    this.logPrefix = '';
9648    this.log = void 0;
9649    this.warn = void 0;
9650    this.playlistType = playlistType;
9651    this.logPrefix = logPrefix;
9652    this.log = logger.log.bind(logger, `${logPrefix}:`);
9653    this.warn = logger.warn.bind(logger, `${logPrefix}:`);
9654    this.hls = hls;
9655    this.fragmentLoader = new FragmentLoader(hls.config);
9656    this.keyLoader = keyLoader;
9657    this.fragmentTracker = fragmentTracker;
9658    this.config = hls.config;
9659    this.decrypter = new Decrypter(hls.config);
9660    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9661  }
9662  doTick() {
9663    this.onTickEnd();
9664  }
9665  onTickEnd() {}
9666
9667  // eslint-disable-next-line @typescript-eslint/no-unused-vars
9668  startLoad(startPosition) {}
9669  stopLoad() {
9670    this.fragmentLoader.abort();
9671    this.keyLoader.abort(this.playlistType);
9672    const frag = this.fragCurrent;
9673    if (frag != null && frag.loader) {
9674      frag.abortRequests();
9675      this.fragmentTracker.removeFragment(frag);
9676    }
9677    this.resetTransmuxer();
9678    this.fragCurrent = null;
9679    this.fragPrevious = null;
9680    this.clearInterval();
9681    this.clearNextTick();
9682    this.state = State.STOPPED;
9683  }
9684  _streamEnded(bufferInfo, levelDetails) {
9685    // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
9686    // of nothing loading/loaded return false
9687    if (levelDetails.live || bufferInfo.nextStart || !bufferInfo.end || !this.media) {
9688      return false;
9689    }
9690    const partList = levelDetails.partList;
9691    // Since the last part isn't guaranteed to correspond to the last playlist segment for Low-Latency HLS,
9692    // check instead if the last part is buffered.
9693    if (partList != null && partList.length) {
9694      const lastPart = partList[partList.length - 1];
9695
9696      // Checking the midpoint of the part for potential margin of error and related issues.
9697      // NOTE: Technically I believe parts could yield content that is < the computed duration (including potential a duration of 0)
9698      // and still be spec-compliant, so there may still be edge cases here. Likewise, there could be issues in end of stream
9699      // part mismatches for independent audio and video playlists/segments.
9700      const lastPartBuffered = BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2);
9701      return lastPartBuffered;
9702    }
9703    const playlistType = levelDetails.fragments[levelDetails.fragments.length - 1].type;
9704    return this.fragmentTracker.isEndListAppended(playlistType);
9705  }
9706  getLevelDetails() {
9707    if (this.levels && this.levelLastLoaded !== null) {
9708      var _this$levelLastLoaded;
9709      return (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details;
9710    }
9711  }
9712  onMediaAttached(event, data) {
9713    const media = this.media = this.mediaBuffer = data.media;
9714    this.onvseeking = this.onMediaSeeking.bind(this);
9715    this.onvended = this.onMediaEnded.bind(this);
9716    media.addEventListener('seeking', this.onvseeking);
9717    media.addEventListener('ended', this.onvended);
9718    const config = this.config;
9719    if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
9720      this.startLoad(config.startPosition);
9721    }
9722  }
9723  onMediaDetaching() {
9724    const media = this.media;
9725    if (media != null && media.ended) {
9726      this.log('MSE detaching and video ended, reset startPosition');
9727      this.startPosition = this.lastCurrentTime = 0;
9728    }
9729
9730    // remove video listeners
9731    if (media && this.onvseeking && this.onvended) {
9732      media.removeEventListener('seeking', this.onvseeking);
9733      media.removeEventListener('ended', this.onvended);
9734      this.onvseeking = this.onvended = null;
9735    }
9736    if (this.keyLoader) {
9737      this.keyLoader.detach();
9738    }
9739    this.media = this.mediaBuffer = null;
9740    this.loadedmetadata = false;
vendor: 18,243 bytes, lines 9741-10192
9741    this.fragmentTracker.removeAllFragments();
9742    this.stopLoad();
9743  }
9744  onMediaSeeking() {
9745    const {
9746      config,
9747      fragCurrent,
9748      media,
9749      mediaBuffer,
9750      state
9751    } = this;
9752    const currentTime = media ? media.currentTime : 0;
9753    const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
9754    this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
9755    if (this.state === State.ENDED) {
9756      this.resetLoadingState();
9757    } else if (fragCurrent) {
9758      // Seeking while frag load is in progress
9759      const tolerance = config.maxFragLookUpTolerance;
9760      const fragStartOffset = fragCurrent.start - tolerance;
9761      const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
9762      // if seeking out of buffered range or into new one
9763      if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
9764        const pastFragment = currentTime > fragEndOffset;
9765        // if the seek position is outside the current fragment range
9766        if (currentTime < fragStartOffset || pastFragment) {
9767          if (pastFragment && fragCurrent.loader) {
9768            this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
9769            fragCurrent.abortRequests();
9770            this.resetLoadingState();
9771          }
9772          this.fragPrevious = null;
9773        }
9774      }
9775    }
9776    if (media) {
9777      // Remove gap fragments
9778      this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
9779      this.lastCurrentTime = currentTime;
9780    }
9781
9782    // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
9783    if (!this.loadedmetadata && !bufferInfo.len) {
9784      this.nextLoadPosition = this.startPosition = currentTime;
9785    }
9786
9787    // Async tick to speed up processing
9788    this.tickImmediate();
9789  }
9790  onMediaEnded() {
9791    // reset startPosition and lastCurrentTime to restart playback @ stream beginning
9792    this.startPosition = this.lastCurrentTime = 0;
9793  }
9794  onManifestLoaded(event, data) {
9795    this.startTimeOffset = data.startTimeOffset;
9796    this.initPTS = [];
9797  }
9798  onHandlerDestroying() {
9799    this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
9800    this.stopLoad();
9801    super.onHandlerDestroying();
9802    // @ts-ignore
9803    this.hls = null;
9804  }
9805  onHandlerDestroyed() {
9806    this.state = State.STOPPED;
9807    if (this.fragmentLoader) {
9808      this.fragmentLoader.destroy();
9809    }
9810    if (this.keyLoader) {
9811      this.keyLoader.destroy();
9812    }
9813    if (this.decrypter) {
9814      this.decrypter.destroy();
9815    }
9816    this.hls = this.log = this.warn = this.decrypter = this.keyLoader = this.fragmentLoader = this.fragmentTracker = null;
9817    super.onHandlerDestroyed();
9818  }
9819  loadFragment(frag, level, targetBufferTime) {
9820    this._loadFragForPlayback(frag, level, targetBufferTime);
9821  }
9822  _loadFragForPlayback(frag, level, targetBufferTime) {
9823    const progressCallback = data => {
9824      if (this.fragContextChanged(frag)) {
9825        this.warn(`Fragment ${frag.sn}${data.part ? ' p: ' + data.part.index : ''} of level ${frag.level} was dropped during download.`);
9826        this.fragmentTracker.removeFragment(frag);
9827        return;
9828      }
9829      frag.stats.chunkCount++;
9830      this._handleFragmentLoadProgress(data);
9831    };
9832    this._doFragLoad(frag, level, targetBufferTime, progressCallback).then(data => {
9833      if (!data) {
9834        // if we're here we probably needed to backtrack or are waiting for more parts
9835        return;
9836      }
9837      const state = this.state;
9838      if (this.fragContextChanged(frag)) {
9839        if (state === State.FRAG_LOADING || !this.fragCurrent && state === State.PARSING) {
9840          this.fragmentTracker.removeFragment(frag);
9841          this.state = State.IDLE;
9842        }
9843        return;
9844      }
9845      if ('payload' in data) {
9846        this.log(`Loaded fragment ${frag.sn} of level ${frag.level}`);
9847        this.hls.trigger(Events.FRAG_LOADED, data);
9848      }
9849
9850      // Pass through the whole payload; controllers not implementing progressive loading receive data from this callback
9851      this._handleFragmentLoadComplete(data);
9852    }).catch(reason => {
9853      if (this.state === State.STOPPED || this.state === State.ERROR) {
9854        return;
9855      }
9856      this.warn(`Frag error: ${(reason == null ? void 0 : reason.message) || reason}`);
9857      this.resetFragmentLoading(frag);
9858    });
9859  }
9860  clearTrackerIfNeeded(frag) {
9861    var _this$mediaBuffer;
9862    const {
9863      fragmentTracker
9864    } = this;
9865    const fragState = fragmentTracker.getState(frag);
9866    if (fragState === FragmentState.APPENDING) {
9867      // Lower the max buffer length and try again
9868      const playlistType = frag.type;
9869      const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
9870      const minForwardBufferLength = Math.max(frag.duration, bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength);
9871      // If backtracking, always remove from the tracker without reducing max buffer length
9872      const backtrackFragment = this.backtrackFragment;
9873      const backtracked = backtrackFragment ? frag.sn - backtrackFragment.sn : 0;
9874      if (backtracked === 1 || this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)) {
9875        fragmentTracker.removeFragment(frag);
9876      }
9877    } else if (((_this$mediaBuffer = this.mediaBuffer) == null ? void 0 : _this$mediaBuffer.buffered.length) === 0) {
9878      // Stop gap for bad tracker / buffer flush behavior
9879      fragmentTracker.removeAllFragments();
9880    } else if (fragmentTracker.hasParts(frag.type)) {
9881      // In low latency mode, remove fragments for which only some parts were buffered
9882      fragmentTracker.detectPartialFragments({
9883        frag,
9884        part: null,
9885        stats: frag.stats,
9886        id: frag.type
9887      });
9888      if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) {
9889        fragmentTracker.removeFragment(frag);
9890      }
9891    }
9892  }
9893  checkLiveUpdate(details) {
9894    if (details.updated && !details.live) {
9895      // Live stream ended, update fragment tracker
9896      const lastFragment = details.fragments[details.fragments.length - 1];
9897      this.fragmentTracker.detectPartialFragments({
9898        frag: lastFragment,
9899        part: null,
9900        stats: lastFragment.stats,
9901        id: lastFragment.type
9902      });
9903    }
9904    if (!details.fragments[0]) {
9905      details.deltaUpdateFailed = true;
9906    }
9907  }
9908  flushMainBuffer(startOffset, endOffset, type = null) {
9909    if (!(startOffset - endOffset)) {
9910      return;
9911    }
9912    // When alternate audio is playing, the audio-stream-controller is responsible for the audio buffer. Otherwise,
9913    // passing a null type flushes both buffers
9914    const flushScope = {
9915      startOffset,
9916      endOffset,
9917      type
9918    };
9919    this.hls.trigger(Events.BUFFER_FLUSHING, flushScope);
9920  }
9921  _loadInitSegment(frag, level) {
9922    this._doFragLoad(frag, level).then(data => {
9923      if (!data || this.fragContextChanged(frag) || !this.levels) {
9924        throw new Error('init load aborted');
9925      }
9926      return data;
9927    }).then(data => {
9928      const {
9929        hls
9930      } = this;
9931      const {
9932        payload
9933      } = data;
9934      const decryptData = frag.decryptdata;
9935
9936      // check to see if the payload needs to be decrypted
9937      if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
9938        const startTime = self.performance.now();
9939        // decrypt init segment data
9940        return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
9941          hls.trigger(Events.ERROR, {
9942            type: ErrorTypes.MEDIA_ERROR,
9943            details: ErrorDetails.FRAG_DECRYPT_ERROR,
9944            fatal: false,
9945            error: err,
9946            reason: err.message,
9947            frag
9948          });
9949          throw err;
9950        }).then(decryptedData => {
9951          const endTime = self.performance.now();
9952          hls.trigger(Events.FRAG_DECRYPTED, {
9953            frag,
9954            payload: decryptedData,
9955            stats: {
9956              tstart: startTime,
9957              tdecrypt: endTime
9958            }
9959          });
9960          data.payload = decryptedData;
9961          return this.completeInitSegmentLoad(data);
9962        });
9963      }
9964      return this.completeInitSegmentLoad(data);
9965    }).catch(reason => {
9966      if (this.state === State.STOPPED || this.state === State.ERROR) {
9967        return;
9968      }
9969      this.warn(reason);
9970      this.resetFragmentLoading(frag);
9971    });
9972  }
9973  completeInitSegmentLoad(data) {
9974    const {
9975      levels
9976    } = this;
9977    if (!levels) {
9978      throw new Error('init load aborted, missing levels');
9979    }
9980    const stats = data.frag.stats;
9981    this.state = State.IDLE;
9982    data.frag.data = new Uint8Array(data.payload);
9983    stats.parsing.start = stats.buffering.start = self.performance.now();
9984    stats.parsing.end = stats.buffering.end = self.performance.now();
9985    this.tick();
9986  }
9987  fragContextChanged(frag) {
9988    const {
9989      fragCurrent
9990    } = this;
9991    return !frag || !fragCurrent || frag.sn !== fragCurrent.sn || frag.level !== fragCurrent.level;
9992  }
9993  fragBufferedComplete(frag, part) {
9994    var _frag$startPTS, _frag$endPTS, _this$fragCurrent, _this$fragPrevious;
9995    const media = this.mediaBuffer ? this.mediaBuffer : this.media;
9996    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)'})`);
9997    if (frag.sn !== 'initSegment') {
9998      var _this$levels;
9999      if (frag.type !== PlaylistLevelType.SUBTITLE) {
10000        const el = frag.elementaryStreams;
10001        if (!Object.keys(el).some(type => !!el[type])) {
10002          // empty segment
10003          this.state = State.IDLE;
10004          return;
10005        }
10006      }
10007      const level = (_this$levels = this.levels) == null ? void 0 : _this$levels[frag.level];
10008      if (level != null && level.fragmentError) {
10009        this.log(`Resetting level fragment error count of ${level.fragmentError} on frag buffered`);
10010        level.fragmentError = 0;
10011      }
10012    }
10013    this.state = State.IDLE;
10014    if (!media) {
10015      return;
10016    }
10017    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)) {
10018      this.loadedmetadata = true;
10019      this.seekToStartPos();
10020    }
10021    this.tick();
10022  }
10023  seekToStartPos() {}
10024  _handleFragmentLoadComplete(fragLoadedEndData) {
10025    const {
10026      transmuxer
10027    } = this;
10028    if (!transmuxer) {
10029      return;
10030    }
10031    const {
10032      frag,
10033      part,
10034      partsLoaded
10035    } = fragLoadedEndData;
10036    // If we did not load parts, or loaded all parts, we have complete (not partial) fragment data
10037    const complete = !partsLoaded || partsLoaded.length === 0 || partsLoaded.some(fragLoaded => !fragLoaded);
10038    const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index : -1, !complete);
10039    transmuxer.flush(chunkMeta);
10040  }
10041
10042  // eslint-disable-next-line @typescript-eslint/no-unused-vars
10043  _handleFragmentLoadProgress(frag) {}
10044  _doFragLoad(frag, level, targetBufferTime = null, progressCallback) {
10045    var _frag$decryptdata;
10046    const details = level == null ? void 0 : level.details;
10047    if (!this.levels || !details) {
10048      throw new Error(`frag load aborted, missing level${details ? '' : ' detail'}s`);
10049    }
10050    let keyLoadingPromise = null;
10051    if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
10052      this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
10053      this.state = State.KEY_LOADING;
10054      this.fragCurrent = frag;
10055      keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
10056        if (!this.fragContextChanged(keyLoadedData.frag)) {
10057          this.hls.trigger(Events.KEY_LOADED, keyLoadedData);
10058          if (this.state === State.KEY_LOADING) {
10059            this.state = State.IDLE;
10060          }
10061          return keyLoadedData;
10062        }
10063      });
10064      this.hls.trigger(Events.KEY_LOADING, {
10065        frag
10066      });
10067      if (this.fragCurrent === null) {
10068        keyLoadingPromise = Promise.reject(new Error(`frag load aborted, context changed in KEY_LOADING`));
10069      }
10070    } else if (!frag.encrypted && details.encryptedFragments.length) {
10071      this.keyLoader.loadClear(frag, details.encryptedFragments);
10072    }
10073    targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
10074    if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
10075      const partList = details.partList;
10076      if (partList && progressCallback) {
10077        if (targetBufferTime > frag.end && details.fragmentHint) {
10078          frag = details.fragmentHint;
10079        }
10080        const partIndex = this.getNextPart(partList, frag, targetBufferTime);
10081        if (partIndex > -1) {
10082          const part = partList[partIndex];
10083          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))}`);
10084          this.nextLoadPosition = part.start + part.duration;
10085          this.state = State.FRAG_LOADING;
10086          let _result;
10087          if (keyLoadingPromise) {
10088            _result = keyLoadingPromise.then(keyLoadedData => {
10089              if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) {
10090                return null;
10091              }
10092              return this.doFragPartsLoad(frag, part, level, progressCallback);
10093            }).catch(error => this.handleFragLoadError(error));
10094          } else {
10095            _result = this.doFragPartsLoad(frag, part, level, progressCallback).catch(error => this.handleFragLoadError(error));
10096          }
10097          this.hls.trigger(Events.FRAG_LOADING, {
10098            frag,
10099            part,
10100            targetBufferTime
10101          });
10102          if (this.fragCurrent === null) {
10103            return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING parts`));
10104          }
10105          return _result;
10106        } else if (!frag.url || this.loadedEndOfParts(partList, targetBufferTime)) {
10107          // Fragment hint has no parts
10108          return Promise.resolve(null);
10109        }
10110      }
10111    }
10112    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))}`);
10113    // Don't update nextLoadPosition for fragments which are not buffered
10114    if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
10115      this.nextLoadPosition = frag.start + frag.duration;
10116    }
10117    this.state = State.FRAG_LOADING;
10118
10119    // Load key before streaming fragment data
10120    const dataOnProgress = this.config.progressive;
10121    let result;
10122    if (dataOnProgress && keyLoadingPromise) {
10123      result = keyLoadingPromise.then(keyLoadedData => {
10124        if (!keyLoadedData || this.fragContextChanged(keyLoadedData == null ? void 0 : keyLoadedData.frag)) {
10125          return null;
10126        }
10127        return this.fragmentLoader.load(frag, progressCallback);
10128      }).catch(error => this.handleFragLoadError(error));
10129    } else {
10130      // load unencrypted fragment data with progress event,
10131      // or handle fragment result after key and fragment are finished loading
10132      result = Promise.all([this.fragmentLoader.load(frag, dataOnProgress ? progressCallback : undefined), keyLoadingPromise]).then(([fragLoadedData]) => {
10133        if (!dataOnProgress && fragLoadedData && progressCallback) {
10134          progressCallback(fragLoadedData);
10135        }
10136        return fragLoadedData;
10137      }).catch(error => this.handleFragLoadError(error));
10138    }
10139    this.hls.trigger(Events.FRAG_LOADING, {
10140      frag,
10141      targetBufferTime
10142    });
10143    if (this.fragCurrent === null) {
10144      return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING`));
10145    }
10146    return result;
10147  }
10148  doFragPartsLoad(frag, fromPart, level, progressCallback) {
10149    return new Promise((resolve, reject) => {
10150      var _level$details;
10151      const partsLoaded = [];
10152      const initialPartList = (_level$details = level.details) == null ? void 0 : _level$details.partList;
10153      const loadPart = part => {
10154        this.fragmentLoader.loadPart(frag, part, progressCallback).then(partLoadedData => {
10155          partsLoaded[part.index] = partLoadedData;
10156          const loadedPart = partLoadedData.part;
10157          this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
10158          const nextPart = getPartWith(level, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
10159          if (nextPart) {
10160            loadPart(nextPart);
10161          } else {
10162            return resolve({
10163              frag,
10164              part: loadedPart,
10165              partsLoaded
10166            });
10167          }
10168        }).catch(reject);
10169      };
10170      loadPart(fromPart);
10171    });
10172  }
10173  handleFragLoadError(error) {
10174    if ('data' in error) {
10175      const data = error.data;
10176      if (error.data && data.details === ErrorDetails.INTERNAL_ABORTED) {
10177        this.handleFragLoadAborted(data.frag, data.part);
10178      } else {
10179        this.hls.trigger(Events.ERROR, data);
10180      }
10181    } else {
10182      this.hls.trigger(Events.ERROR, {
10183        type: ErrorTypes.OTHER_ERROR,
10184        details: ErrorDetails.INTERNAL_EXCEPTION,
10185        err: error,
10186        error,
10187        fatal: true
10188      });
10189    }
10190    return null;
10191  }
10192  _handleTransmuxerFlush(chunkMeta) {
10193    const context = this.getCurrentContext(chunkMeta);
10194    if (!context || this.state !== State.PARSING) {
10195      if (!this.fragCurrent && this.state !== State.STOPPED && this.state !== State.ERROR) {
10196        this.state = State.IDLE;
10197      }
10198      return;
10199    }
10200    const {
10201      frag,
10202      part,
10203      level
10204    } = context;
10205    const now = self.performance.now();
10206    frag.stats.parsing.end = now;
10207    if (part) {
10208      part.stats.parsing.end = now;
10209    }
10210    this.updateLevelTiming(frag, part, level, chunkMeta.partial);
10211  }
10212  getCurrentContext(chunkMeta) {
10213    const {
10214      levels,
10215      fragCurrent
10216    } = this;
10217    const {
10218      level: levelIndex,
10219      sn,
10220      part: partIndex
10221    } = chunkMeta;
10222    if (!(levels != null && levels[levelIndex])) {
10223      this.warn(`Levels object was unset while buffering fragment ${sn} of level ${levelIndex}. The current chunk will not be buffered.`);
10224      return null;
10225    }
10226    const level = levels[levelIndex];
10227    const part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null;
10228    const frag = part ? part.fragment : getFragmentWithSN(level, sn, fragCurrent);
10229    if (!frag) {
10230      return null;
10231    }
10232    if (fragCurrent && fragCurrent !== frag) {
10233      frag.stats = fragCurrent.stats;
10234    }
10235    return {
10236      frag,
10237      part,
10238      level
10239    };
10240  }
10241  bufferFragmentData(data, frag, part, chunkMeta, noBacktracking) {
10242    var _buffer;
10243    if (!data || this.state !== State.PARSING) {
10244      return;
10245    }
10246    const {
10247      data1,
10248      data2
10249    } = data;
10250    let buffer = data1;
10251    if (data1 && data2) {
10252      // Combine the moof + mdat so that we buffer with a single append
10253      buffer = appendUint8Array(data1, data2);
10254    }
10255    if (!((_buffer = buffer) != null && _buffer.length)) {
10256      return;
10257    }
10258    const segment = {
10259      type: data.type,
10260      frag,
10261      part,
10262      chunkMeta,
10263      parent: frag.type,
10264      data: buffer
10265    };
10266    this.hls.trigger(Events.BUFFER_APPENDING, segment);
10267    if (data.dropped && data.independent && !part) {
10268      if (noBacktracking) {
10269        return;
10270      }
10271      // Clear buffer so that we reload previous segments sequentially if required
10272      this.flushBufferGap(frag);
10273    }
10274  }
10275  flushBufferGap(frag) {
10276    const media = this.media;
10277    if (!media) {
10278      return;
10279    }
10280    // If currentTime is not buffered, clear the back buffer so that we can backtrack as much as needed
10281    if (!BufferHelper.isBuffered(media, media.currentTime)) {
10282      this.flushMainBuffer(0, frag.start);
10283      return;
10284    }
10285    // Remove back-buffer without interrupting playback to allow back tracking
10286    const currentTime = media.currentTime;
10287    const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
10288    const fragDuration = frag.duration;
10289    const segmentFraction = Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25);
10290    const start = Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction);
10291    if (frag.start - start > segmentFraction) {
10292      this.flushMainBuffer(start, frag.start);
10293    }
10294  }
10295  getFwdBufferInfo(bufferable, type) {
10296    const pos = this.getLoadPosition();
10297    if (!isFiniteNumber(pos)) {
10298      return null;
10299    }
10300    return this.getFwdBufferInfoAtPos(bufferable, pos, type);
10301  }
10302  getFwdBufferInfoAtPos(bufferable, pos, type) {
10303    const {
10304      config: {
10305        maxBufferHole
10306      }
10307    } = this;
10308    const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
10309    // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
10310    if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
10311      const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
10312      if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
10313        return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
10314      }
10315    }
10316    return bufferInfo;
10317  }
10318  getMaxBufferLength(levelBitrate) {
10319    const {
10320      config
10321    } = this;
10322    let maxBufLen;
10323    if (levelBitrate) {
10324      maxBufLen = Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength);
10325    } else {
10326      maxBufLen = config.maxBufferLength;
10327    }
10328    return Math.min(maxBufLen, config.maxMaxBufferLength);
10329  }
10330  reduceMaxBufferLength(threshold, fragDuration) {
10331    const config = this.config;
10332    const minLength = Math.max(Math.min(threshold - fragDuration, config.maxBufferLength), fragDuration);
10333    const reducedLength = Math.max(threshold - fragDuration * 3, config.maxMaxBufferLength / 2, minLength);
10334    if (reducedLength >= minLength) {
10335      // reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
10336      config.maxMaxBufferLength = reducedLength;
10337      this.warn(`Reduce max buffer length to ${reducedLength}s`);
10338      return true;
10339    }
10340    return false;
10341  }
10342  getAppendedFrag(position, playlistType = PlaylistLevelType.MAIN) {
10343    const fragOrPart = this.fragmentTracker.getAppendedFrag(position, PlaylistLevelType.MAIN);
10344    if (fragOrPart && 'fragment' in fragOrPart) {
10345      return fragOrPart.fragment;
10346    }
10347    return fragOrPart;
10348  }
10349  getNextFragment(pos, levelDetails) {
10350    const fragments = levelDetails.fragments;
10351    const fragLen = fragments.length;
10352    if (!fragLen) {
10353      return null;
10354    }
10355
10356    // find fragment index, contiguous with end of buffer position
10357    const {
10358      config
10359    } = this;
10360    const start = fragments[0].start;
10361    let frag;
10362    if (levelDetails.live) {
10363      const initialLiveManifestSize = config.initialLiveManifestSize;
10364      if (fragLen < initialLiveManifestSize) {
10365        this.warn(`Not enough fragments to start playback (have: ${fragLen}, need: ${initialLiveManifestSize})`);
10366        return null;
10367      }
10368      // The real fragment start times for a live stream are only known after the PTS range for that level is known.
10369      // In order to discover the range, we load the best matching fragment for that level and demux it.
10370      // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
10371      // we get the fragment matching that start time
10372      if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
10373        frag = this.getInitialLiveFragment(levelDetails, fragments);
10374        this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
10375      }
10376    } else if (pos <= start) {
10377      // VoD playlist: if loadPosition before start of playlist, load first fragment
10378      frag = fragments[0];
10379    }
10380
10381    // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
10382    if (!frag) {
10383      const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
10384      frag = this.getFragmentAtPosition(pos, end, levelDetails);
10385    }
10386    return this.mapToInitFragWhenRequired(frag);
10387  }
10388  isLoopLoading(frag, targetBufferTime) {
10389    const trackerState = this.fragmentTracker.getState(frag);
10390    return (trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap) && this.nextLoadPosition > targetBufferTime;
10391  }
10392  getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) {
10393    const gapStart = frag.gap;
10394    const nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
10395    if (nextFragment === null) {
10396      return nextFragment;
10397    }
10398    frag = nextFragment;
10399    if (gapStart && frag && !frag.gap && bufferInfo.nextStart) {
10400      // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
10401      const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType);
10402      if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
10403        // Returning here might result in not finding an audio and video candiate to skip to
10404        this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${frag.sn}`);
10405        return null;
10406      }
10407    }
10408    return frag;
10409  }
10410  mapToInitFragWhenRequired(frag) {
10411    // If an initSegment is present, it must be buffered first
10412    if (frag != null && frag.initSegment && !(frag != null && frag.initSegment.data) && !this.bitrateTest) {
10413      return frag.initSegment;
10414    }
10415    return frag;
10416  }
10417  getNextPart(partList, frag, targetBufferTime) {
10418    let nextPart = -1;
10419    let contiguous = false;
10420    let independentAttrOmitted = true;
10421    for (let i = 0, len = partList.length; i < len; i++) {
10422      const part = partList[i];
10423      independentAttrOmitted = independentAttrOmitted && !part.independent;
10424      if (nextPart > -1 && targetBufferTime < part.start) {
10425        break;
10426      }
10427      const loaded = part.loaded;
10428      if (loaded) {
10429        nextPart = -1;
10430      } else if ((contiguous || part.independent || independentAttrOmitted) && part.fragment === frag) {
10431        nextPart = i;
10432      }
10433      contiguous = loaded;
10434    }
10435    return nextPart;
10436  }
10437  loadedEndOfParts(partList, targetBufferTime) {
10438    const lastPart = partList[partList.length - 1];
10439    return lastPart && targetBufferTime > lastPart.start && lastPart.loaded;
10440  }
10441
10442  /*
10443   This method is used find the best matching first fragment for a live playlist. This fragment is used to calculate the
10444   "sliding" of the playlist, which is its offset from the start of playback. After sliding we can compute the real
10445   start and end times for each fragment in the playlist (after which this method will not need to be called).
10446  */
10447  getInitialLiveFragment(levelDetails, fragments) {
10448    const fragPrevious = this.fragPrevious;
10449    let frag = null;
10450    if (fragPrevious) {
10451      if (levelDetails.hasProgramDateTime) {
10452        // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
10453        this.log(`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`);
10454        frag = findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance);
10455      }
10456      if (!frag) {
10457        // SN does not need to be accurate between renditions, but depending on the packaging it may be so.
10458        const targetSN = fragPrevious.sn + 1;
10459        if (targetSN >= levelDetails.startSN && targetSN <= levelDetails.endSN) {
10460          const fragNext = fragments[targetSN - levelDetails.startSN];
10461          // Ensure that we're staying within the continuity range, since PTS resets upon a new range
10462          if (fragPrevious.cc === fragNext.cc) {
10463            frag = fragNext;
10464            this.log(`Live playlist, switching playlist, load frag with next SN: ${frag.sn}`);
10465          }
10466        }
10467        // It's important to stay within the continuity range if available; otherwise the fragments in the playlist
10468        // will have the wrong start times
10469        if (!frag) {
10470          frag = findFragWithCC(fragments, fragPrevious.cc);
10471          if (frag) {
10472            this.log(`Live playlist, switching playlist, load frag with same CC: ${frag.sn}`);
10473          }
10474        }
10475      }
10476    } else {
10477      // Find a new start fragment when fragPrevious is null
10478      const liveStart = this.hls.liveSyncPosition;
10479      if (liveStart !== null) {
10480        frag = this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd : levelDetails.edge, levelDetails);
10481      }
10482    }
10483    return frag;
10484  }
10485
10486  /*
10487  This method finds the best matching fragment given the provided position.
10488   */
10489  getFragmentAtPosition(bufferEnd, end, levelDetails) {
10490    const {
10491      config
10492    } = this;
10493    let {
10494      fragPrevious
10495    } = this;
10496    let {
10497      fragments,
10498      endSN
10499    } = levelDetails;
10500    const {
10501      fragmentHint
10502    } = levelDetails;
10503    const {
10504      maxFragLookUpTolerance
10505    } = config;
10506    const partList = levelDetails.partList;
10507    const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
10508    if (loadingParts && fragmentHint && !this.bitrateTest) {
10509      // Include incomplete fragment with parts at end
10510      fragments = fragments.concat(fragmentHint);
10511      endSN = fragmentHint.sn;
10512    }
10513    let frag;
10514    if (bufferEnd < end) {
10515      const lookupTolerance = bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance;
10516      // Remove the tolerance if it would put the bufferEnd past the actual end of stream
10517      // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
10518      frag = findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance);
10519    } else {
10520      // reach end of playlist
10521      frag = fragments[fragments.length - 1];
10522    }
10523    if (frag) {
10524      const curSNIdx = frag.sn - levelDetails.startSN;
10525      // Move fragPrevious forward to support forcing the next fragment to load
10526      // when the buffer catches up to a previously buffered range.
10527      const fragState = this.fragmentTracker.getState(frag);
10528      if (fragState === FragmentState.OK || fragState === FragmentState.PARTIAL && frag.gap) {
10529        fragPrevious = frag;
10530      }
10531      if (fragPrevious && frag.sn === fragPrevious.sn && (!loadingParts || partList[0].fragment.sn > frag.sn)) {
10532        // Force the next fragment to load if the previous one was already selected. This can occasionally happen with
10533        // non-uniform fragment durations
10534        const sameLevel = fragPrevious && frag.level === fragPrevious.level;
10535        if (sameLevel) {
10536          const nextFrag = fragments[curSNIdx + 1];
10537          if (frag.sn < endSN && this.fragmentTracker.getState(nextFrag) !== FragmentState.OK) {
10538            frag = nextFrag;
10539          } else {
10540            frag = null;
10541          }
10542        }
10543      }
10544    }
10545    return frag;
10546  }
10547  synchronizeToLiveEdge(levelDetails) {
10548    const {
10549      config,
10550      media
10551    } = this;
10552    if (!media) {
10553      return;
10554    }
10555    const liveSyncPosition = this.hls.liveSyncPosition;
10556    const currentTime = media.currentTime;
10557    const start = levelDetails.fragments[0].start;
10558    const end = levelDetails.edge;
10559    const withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end;
10560    // Continue if we can seek forward to sync position or if current time is outside of sliding window
10561    if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) {
10562      // Continue if buffer is starving or if current time is behind max latency
10563      const maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration;
10564      if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) {
10565        if (!this.loadedmetadata) {
10566          this.nextLoadPosition = liveSyncPosition;
10567        }
10568        // Only seek if ready and there is not a significant forward buffer available for playback
10569        if (media.readyState) {
10570          this.warn(`Playback: ${currentTime.toFixed(3)} is located too far from the end of live sliding playlist: ${end}, reset currentTime to : ${liveSyncPosition.toFixed(3)}`);
10571          media.currentTime = liveSyncPosition;
10572        }
10573      }
10574    }
10575  }
10576  alignPlaylists(details, previousDetails, switchDetails) {
10577    // FIXME: If not for `shouldAlignOnDiscontinuities` requiring fragPrevious.cc,
10578    //  this could all go in level-helper mergeDetails()
10579    const length = details.fragments.length;
10580    if (!length) {
10581      this.warn(`No fragments in live playlist`);
10582      return 0;
10583    }
10584    const slidingStart = details.fragments[0].start;
10585    const firstLevelLoad = !previousDetails;
10586    const aligned = details.alignedSliding && isFiniteNumber(slidingStart);
10587    if (firstLevelLoad || !aligned && !slidingStart) {
10588      const {
10589        fragPrevious
10590      } = this;
10591      alignStream(fragPrevious, switchDetails, details);
10592      const alignedSlidingStart = details.fragments[0].start;
10593      this.log(`Live playlist sliding: ${alignedSlidingStart.toFixed(2)} start-sn: ${previousDetails ? previousDetails.startSN : 'na'}->${details.startSN} prev-sn: ${fragPrevious ? fragPrevious.sn : 'na'} fragments: ${length}`);
10594      return alignedSlidingStart;
10595    }
10596    return slidingStart;
10597  }
10598  waitForCdnTuneIn(details) {
10599    // Wait for Low-Latency CDN Tune-in to get an updated playlist
10600    const advancePartLimit = 3;
10601    return details.live && details.canBlockReload && details.partTarget && details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit);
10602  }
10603  setStartPosition(details, sliding) {
10604    // compute start position if set to -1. use it straight away if value is defined
10605    let startPosition = this.startPosition;
10606    if (startPosition < sliding) {
10607      startPosition = -1;
10608    }
10609    if (startPosition === -1 || this.lastCurrentTime === -1) {
10610      // Use Playlist EXT-X-START:TIME-OFFSET when set
10611      // Prioritize Multivariant Playlist offset so that main, audio, and subtitle stream-controller start times match
10612      const offsetInMultivariantPlaylist = this.startTimeOffset !== null;
10613      const startTimeOffset = offsetInMultivariantPlaylist ? this.startTimeOffset : details.startTimeOffset;
10614      if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) {
10615        startPosition = sliding + startTimeOffset;
10616        if (startTimeOffset < 0) {
10617          startPosition += details.totalduration;
10618        }
10619        startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration);
10620        this.log(`Start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? 'multivariant' : 'media'} playlist, adjust startPosition to ${startPosition}`);
10621        this.startPosition = startPosition;
10622      } else if (details.live) {
10623        // Leave this.startPosition at -1, so that we can use `getInitialLiveFragment` logic when startPosition has
10624        // not been specified via the config or an as an argument to startLoad (#3736).
10625        startPosition = this.hls.liveSyncPosition || sliding;
10626      } else {
10627        this.startPosition = startPosition = 0;
10628      }
10629      this.lastCurrentTime = startPosition;
10630    }
10631    this.nextLoadPosition = startPosition;
10632  }
10633  getLoadPosition() {
10634    const {
10635      media
10636    } = this;
10637    // if we have not yet loaded any fragment, start loading from start position
10638    let pos = 0;
10639    if (this.loadedmetadata && media) {
10640      pos = media.currentTime;
10641    } else if (this.nextLoadPosition) {
10642      pos = this.nextLoadPosition;
10643    }
10644    return pos;
10645  }
10646  handleFragLoadAborted(frag, part) {
10647    if (this.transmuxer && frag.sn !== 'initSegment' && frag.stats.aborted) {
10648      this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} was aborted`);
10649      this.resetFragmentLoading(frag);
10650    }
10651  }
10652  resetFragmentLoading(frag) {
10653    if (!this.fragCurrent || !this.fragContextChanged(frag) && this.state !== State.FRAG_LOADING_WAITING_RETRY) {
10654      this.state = State.IDLE;
10655    }
10656  }
10657  onFragmentOrKeyLoadError(filterType, data) {
10658    if (data.chunkMeta && !data.frag) {
10659      const context = this.getCurrentContext(data.chunkMeta);
10660      if (context) {
10661        data.frag = context.frag;
10662      }
10663    }
10664    const frag = data.frag;
10665    // Handle frag error related to caller's filterType
10666    if (!frag || frag.type !== filterType || !this.levels) {
10667      return;
10668    }
10669    if (this.fragContextChanged(frag)) {
10670      var _this$fragCurrent2;
10671      this.warn(`Frag load error must match current frag to retry ${frag.url} > ${(_this$fragCurrent2 = this.fragCurrent) == null ? void 0 : _this$fragCurrent2.url}`);
10672      return;
10673    }
10674    const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
10675    if (gapTagEncountered) {
10676      this.fragmentTracker.fragBuffered(frag, true);
10677    }
10678    // keep retrying until the limit will be reached
10679    const errorAction = data.errorAction;
10680    const {
10681      action,
10682      retryCount = 0,
10683      retryConfig
10684    } = errorAction || {};
10685    if (errorAction && action === NetworkErrorAction.RetryRequest && retryConfig) {
10686      this.resetStartWhenNotLoaded(this.levelLastLoaded);
10687      const delay = getRetryDelay(retryConfig, retryCount);
10688      this.warn(`Fragment ${frag.sn} of ${filterType} ${frag.level} errored with ${data.details}, retrying loading ${retryCount + 1}/${retryConfig.maxNumRetry} in ${delay}ms`);
10689      errorAction.resolved = true;
10690      this.retryDate = self.performance.now() + delay;
10691      this.state = State.FRAG_LOADING_WAITING_RETRY;
10692    } else if (retryConfig && errorAction) {
10693      this.resetFragmentErrors(filterType);
10694      if (retryCount < retryConfig.maxNumRetry) {
10695        // Network retry is skipped when level switch is preferred
10696        if (!gapTagEncountered && action !== NetworkErrorAction.RemoveAlternatePermanently) {
10697          errorAction.resolved = true;
10698        }
10699      } else {
10700        logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
10701        return;
10702      }
10703    } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
10704      this.state = State.WAITING_LEVEL;
10705    } else {
10706      this.state = State.ERROR;
10707    }
10708    // Perform next async tick sooner to speed up error action resolution
10709    this.tickImmediate();
10710  }
10711  reduceLengthAndFlushBuffer(data) {
10712    // if in appending state
10713    if (this.state === State.PARSING || this.state === State.PARSED) {
10714      const frag = data.frag;
10715      const playlistType = data.parent;
10716      const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
10717      // 0.5 : tolerance needed as some browsers stalls playback before reaching buffered end
10718      // reduce max buf len if current position is buffered
10719      const buffered = bufferedInfo && bufferedInfo.len > 0.5;
10720      if (buffered) {
10721        this.reduceMaxBufferLength(bufferedInfo.len, (frag == null ? void 0 : frag.duration) || 10);
10722      }
10723      const flushBuffer = !buffered;
10724      if (flushBuffer) {
10725        // current position is not buffered, but browser is still complaining about buffer full error
10726        // this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708
10727        // in that case flush the whole audio buffer to recover
10728        this.warn(`Buffer full error while media.currentTime is not buffered, flush ${playlistType} buffer`);
10729      }
10730      if (frag) {
10731        this.fragmentTracker.removeFragment(frag);
10732        this.nextLoadPosition = frag.start;
10733      }
10734      this.resetLoadingState();
10735      return flushBuffer;
10736    }
10737    return false;
10738  }
10739  resetFragmentErrors(filterType) {
10740    if (filterType === PlaylistLevelType.AUDIO) {
10741      // Reset current fragment since audio track audio is essential and may not have a fail-over track
10742      this.fragCurrent = null;
10743    }
10744    // Fragment errors that result in a level switch or redundant fail-over
10745    // should reset the stream controller state to idle
10746    if (!this.loadedmetadata) {
10747      this.startFragRequested = false;
10748    }
10749    if (this.state !== State.STOPPED) {
10750      this.state = State.IDLE;
10751    }
10752  }
10753  afterBufferFlushed(media, bufferType, playlistType) {
10754    if (!media) {
10755      return;
10756    }
10757    // After successful buffer flushing, filter flushed fragments from bufferedFrags use mediaBuffered instead of media
10758    // (so that we will check against video.buffered ranges in case of alt audio track)
10759    const bufferedTimeRanges = BufferHelper.getBuffered(media);
10760    this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, playlistType);
10761    if (this.state === State.ENDED) {
10762      this.resetLoadingState();
10763    }
10764  }
10765  resetLoadingState() {
10766    this.log('Reset loading state');
10767    this.fragCurrent = null;
10768    this.fragPrevious = null;
10769    this.state = State.IDLE;
10770  }
10771  resetStartWhenNotLoaded(level) {
10772    // if loadedmetadata is not set, it means that first frag request failed
10773    // in that case, reset startFragRequested flag
10774    if (!this.loadedmetadata) {
10775      this.startFragRequested = false;
10776      const details = level ? level.details : null;
10777      if (details != null && details.live) {
10778        // Update the start position and return to IDLE to recover live start
10779        this.startPosition = -1;
10780        this.setStartPosition(details, 0);
10781        this.resetLoadingState();
10782      } else {
10783        this.nextLoadPosition = this.startPosition;
10784      }
10785    }
10786  }
10787  resetWhenMissingContext(chunkMeta) {
10788    this.warn(`The loading context changed while buffering fragment ${chunkMeta.sn} of level ${chunkMeta.level}. This chunk will not be buffered.`);
10789    this.removeUnbufferedFrags();
10790    this.resetStartWhenNotLoaded(this.levelLastLoaded);
10791    this.resetLoadingState();
10792  }
10793  removeUnbufferedFrags(start = 0) {
10794    this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true);
10795  }
10796  updateLevelTiming(frag, part, level, partial) {
10797    var _this$transmuxer;
10798    const details = level.details;
10799    if (!details) {
10800      this.warn('level.details undefined');
10801      return;
10802    }
10803    const parsed = Object.keys(frag.elementaryStreams).reduce((result, type) => {
10804      const info = frag.elementaryStreams[type];
10805      if (info) {
10806        const parsedDuration = info.endPTS - info.startPTS;
10807        if (parsedDuration <= 0) {
10808          // Destroy the transmuxer after it's next time offset failed to advance because duration was <= 0.
10809          // The new transmuxer will be configured with a time offset matching the next fragment start,
10810          // preventing the timeline from shifting.
10811          this.warn(`Could not parse fragment ${frag.sn} ${type} duration reliably (${parsedDuration})`);
10812          return result || false;
10813        }
10814        const drift = partial ? 0 : updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS);
10815        this.hls.trigger(Events.LEVEL_PTS_UPDATED, {
10816          details,
10817          level,
10818          drift,
10819          type,
10820          frag,
10821          start: info.startPTS,
10822          end: info.endPTS
10823        });
10824        return true;
10825      }
10826      return result;
10827    }, false);
10828    if (!parsed && ((_this$transmuxer = this.transmuxer) == null ? void 0 : _this$transmuxer.error) === null) {
10829      const error = new Error(`Found no media in fragment ${frag.sn} of level ${frag.level} resetting transmuxer to fallback to playlist timing`);
10830      if (level.fragmentError === 0) {
10831        // Mark and track the odd empty segment as a gap to avoid reloading
10832        level.fragmentError++;
10833        frag.gap = true;
10834        this.fragmentTracker.removeFragment(frag);
10835        this.fragmentTracker.fragBuffered(frag, true);
10836      }
10837      this.warn(error.message);
10838      this.hls.trigger(Events.ERROR, {
10839        type: ErrorTypes.MEDIA_ERROR,
10840        details: ErrorDetails.FRAG_PARSING_ERROR,
10841        fatal: false,
10842        error,
10843        frag,
10844        reason: `Found no media in msn ${frag.sn} of level "${level.url}"`
10845      });
10846      if (!this.hls) {
10847        return;
10848      }
10849      this.resetTransmuxer();
10850      // For this error fallthrough. Marking parsed will allow advancing to next fragment.
10851    }
10852    this.state = State.PARSED;
10853    this.hls.trigger(Events.FRAG_PARSED, {
10854      frag,
10855      part
10856    });
10857  }
10858  resetTransmuxer() {
10859    if (this.transmuxer) {
10860      this.transmuxer.destroy();
10861      this.transmuxer = null;
10862    }
10863  }
10864  recoverWorkerError(data) {
10865    if (data.event === 'demuxerWorker') {
10866      this.fragmentTracker.removeAllFragments();
10867      this.resetTransmuxer();
10868      this.resetStartWhenNotLoaded(this.levelLastLoaded);
10869      this.resetLoadingState();
10870    }
10871  }
10872  set state(nextState) {
10873    const previousState = this._state;
10874    if (previousState !== nextState) {
10875      this._state = nextState;
10876      this.log(`${previousState}->${nextState}`);
10877    }
10878  }
10879  get state() {
10880    return this._state;
10881  }
10882}
10883
10884class ChunkCache {
10885  constructor() {
10886    this.chunks = [];
10887    this.dataLength = 0;
10888  }
10889  push(chunk) {
10890    this.chunks.push(chunk);
10891    this.dataLength += chunk.length;
10892  }
10893  flush() {
10894    const {
10895      chunks,
10896      dataLength
10897    } = this;
10898    let result;
10899    if (!chunks.length) {
10900      return new Uint8Array(0);
10901    } else if (chunks.length === 1) {
10902      result = chunks[0];
10903    } else {
10904      result = concatUint8Arrays(chunks, dataLength);
10905    }
10906    this.reset();
10907    return result;
10908  }
10909  reset() {
10910    this.chunks.length = 0;
10911    this.dataLength = 0;
10912  }
10913}
10914function concatUint8Arrays(chunks, dataLength) {
10915  const result = new Uint8Array(dataLength);
10916  let offset = 0;
10917  for (let i = 0; i < chunks.length; i++) {
10918    const chunk = chunks[i];
10919    result.set(chunk, offset);
10920    offset += chunk.length;
10921  }
10922  return result;
10923}
10924
10925// ensure the worker ends up in the bundle
10926// If the worker should not be included this gets aliased to empty.js
10927function hasUMDWorker() {
10928  return typeof __HLS_WORKER_BUNDLE__ === 'function';
10929}
10930function injectWorker() {
10931  const blob = new self.Blob([`var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`], {
10932    type: 'text/javascript'
10933  });
10934  const objectURL = self.URL.createObjectURL(blob);
10935  const worker = new self.Worker(objectURL);
10936  return {
10937    worker,
10938    objectURL
10939  };
10940}
10941function loadWorker(path) {
10942  const scriptURL = new self.URL(path, self.location.href).href;
10943  const worker = new self.Worker(scriptURL);
10944  return {
10945    worker,
10946    scriptURL
10947  };
10948}
10949
10950function dummyTrack(type = '', inputTimeScale = 90000) {
10951  return {
10952    type,
10953    id: -1,
10954    pid: -1,
10955    inputTimeScale,
10956    sequenceNumber: -1,
10957    samples: [],
10958    dropped: 0
10959  };
10960}
10961
10962class BaseAudioDemuxer {
10963  constructor() {
10964    this._audioTrack = void 0;
10965    this._id3Track = void 0;
10966    this.frameIndex = 0;
10967    this.cachedData = null;
10968    this.basePTS = null;
10969    this.initPTS = null;
10970    this.lastPTS = null;
10971  }
10972  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
10973    this._id3Track = {
10974      type: 'id3',
10975      id: 3,
10976      pid: -1,
10977      inputTimeScale: 90000,
10978      sequenceNumber: 0,
10979      samples: [],
10980      dropped: 0
10981    };
10982  }
10983  resetTimeStamp(deaultTimestamp) {
10984    this.initPTS = deaultTimestamp;
10985    this.resetContiguity();
10986  }
10987  resetContiguity() {
10988    this.basePTS = null;
10989    this.lastPTS = null;
10990    this.frameIndex = 0;
10991  }
10992  canParse(data, offset) {
10993    return false;
10994  }
10995  appendFrame(track, data, offset) {}
10996
10997  // feed incoming data to the front of the parsing pipeline
10998  demux(data, timeOffset) {
10999    if (this.cachedData) {
11000      data = appendUint8Array(this.cachedData, data);
11001      this.cachedData = null;
11002    }
11003    let id3Data = getID3Data(data, 0);
11004    let offset = id3Data ? id3Data.length : 0;
11005    let lastDataIndex;
11006    const track = this._audioTrack;
11007    const id3Track = this._id3Track;
11008    const timestamp = id3Data ? getTimeStamp(id3Data) : undefined;
11009    const length = data.length;
11010    if (this.basePTS === null || this.frameIndex === 0 && isFiniteNumber(timestamp)) {
11011      this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS);
11012      this.lastPTS = this.basePTS;
11013    }
11014    if (this.lastPTS === null) {
11015      this.lastPTS = this.basePTS;
11016    }
11017
11018    // more expressive than alternative: id3Data?.length
11019    if (id3Data && id3Data.length > 0) {
11020      id3Track.samples.push({
11021        pts: this.lastPTS,
11022        dts: this.lastPTS,
11023        data: id3Data,
11024        type: MetadataSchema.audioId3,
11025        duration: Number.POSITIVE_INFINITY
11026      });
11027    }
11028    while (offset < length) {
11029      if (this.canParse(data, offset)) {
11030        const frame = this.appendFrame(track, data, offset);
11031        if (frame) {
11032          this.frameIndex++;
11033          this.lastPTS = frame.sample.pts;
11034          offset += frame.length;
11035          lastDataIndex = offset;
11036        } else {
11037          offset = length;
11038        }
11039      } else if (canParse$2(data, offset)) {
11040        // after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data
11041        id3Data = getID3Data(data, offset);
11042        id3Track.samples.push({
11043          pts: this.lastPTS,
11044          dts: this.lastPTS,
11045          data: id3Data,
11046          type: MetadataSchema.audioId3,
11047          duration: Number.POSITIVE_INFINITY
11048        });
11049        offset += id3Data.length;
11050        lastDataIndex = offset;
11051      } else {
11052        offset++;
11053      }
11054      if (offset === length && lastDataIndex !== length) {
11055        const partialData = sliceUint8(data, lastDataIndex);
11056        if (this.cachedData) {
11057          this.cachedData = appendUint8Array(this.cachedData, partialData);
11058        } else {
11059          this.cachedData = partialData;
11060        }
11061      }
11062    }
11063    return {
11064      audioTrack: track,
11065      videoTrack: dummyTrack(),
11066      id3Track,
11067      textTrack: dummyTrack()
11068    };
11069  }
11070  demuxSampleAes(data, keyData, timeOffset) {
11071    return Promise.reject(new Error(`[${this}] This demuxer does not support Sample-AES decryption`));
11072  }
11073  flush(timeOffset) {
11074    // Parse cache in case of remaining frames.
11075    const cachedData = this.cachedData;
11076    if (cachedData) {
11077      this.cachedData = null;
11078      this.demux(cachedData, 0);
11079    }
11080    return {
11081      audioTrack: this._audioTrack,
11082      videoTrack: dummyTrack(),
11083      id3Track: this._id3Track,
11084      textTrack: dummyTrack()
11085    };
11086  }
11087  destroy() {}
11088}
11089
11090/**
11091 * Initialize PTS
11092 * <p>
11093 *    use timestamp unless it is undefined, NaN or Infinity
11094 * </p>
11095 */
11096const initPTSFn = (timestamp, timeOffset, initPTS) => {
11097  if (isFiniteNumber(timestamp)) {
11098    return timestamp * 90;
11099  }
11100  const init90kHz = initPTS ? initPTS.baseTime * 90000 / initPTS.timescale : 0;
11101  return timeOffset * 90000 + init90kHz;
11102};
11103
11104/**
11105 * ADTS parser helper
11106 * @link https://wiki.multimedia.cx/index.php?title=ADTS
11107 */
11108function getAudioConfig(observer, data, offset, audioCodec) {
11109  let adtsObjectType;
11110  let adtsExtensionSamplingIndex;
11111  let adtsChannelConfig;
11112  let config;
11113  const userAgent = navigator.userAgent.toLowerCase();
11114  const manifestCodec = audioCodec;
11115  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
11116  // byte 2
11117  adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
11118  const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
11119  if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
11120    const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
11121    observer.emit(Events.ERROR, Events.ERROR, {
11122      type: ErrorTypes.MEDIA_ERROR,
11123      details: ErrorDetails.FRAG_PARSING_ERROR,
11124      fatal: true,
11125      error,
11126      reason: error.message
11127    });
11128    return;
11129  }
11130  adtsChannelConfig = (data[offset + 2] & 0x01) << 2;
11131  // byte 3
11132  adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
11133  logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
11134  // firefox: freq less than 24kHz = AAC SBR (HE-AAC)
11135  if (/firefox/i.test(userAgent)) {
11136    if (adtsSamplingIndex >= 6) {
11137      adtsObjectType = 5;
11138      config = new Array(4);
11139      // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
11140      // there is a factor 2 between frame sample rate and output sample rate
11141      // multiply frequency by 2 (see table below, equivalent to substract 3)
11142      adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
11143    } else {
11144      adtsObjectType = 2;
11145      config = new Array(2);
11146      adtsExtensionSamplingIndex = adtsSamplingIndex;
11147    }
11148    // Android : always use AAC
11149  } else if (userAgent.indexOf('android') !== -1) {
11150    adtsObjectType = 2;
11151    config = new Array(2);
11152    adtsExtensionSamplingIndex = adtsSamplingIndex;
11153  } else {
11154    /*  for other browsers (Chrome/Vivaldi/Opera ...)
11155        always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)
11156    */
11157    adtsObjectType = 5;
11158    config = new Array(4);
11159    // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz)
11160    if (audioCodec && (audioCodec.indexOf('mp4a.40.29') !== -1 || audioCodec.indexOf('mp4a.40.5') !== -1) || !audioCodec && adtsSamplingIndex >= 6) {
11161      // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
11162      // there is a factor 2 between frame sample rate and output sample rate
11163      // multiply frequency by 2 (see table below, equivalent to substract 3)
11164      adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
11165    } else {
11166      // if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio)
11167      // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC.  This is not a problem with stereo.
11168      if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSamplingIndex >= 6 && adtsChannelConfig === 1 || /vivaldi/i.test(userAgent)) || !audioCodec && adtsChannelConfig === 1) {
11169        adtsObjectType = 2;
11170        config = new Array(2);
11171      }
11172      adtsExtensionSamplingIndex = adtsSamplingIndex;
11173    }
11174  }
11175  /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
11176      ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig()
11177    Audio Profile / Audio Object Type
11178    0: Null
11179    1: AAC Main
11180    2: AAC LC (Low Complexity)
11181    3: AAC SSR (Scalable Sample Rate)
11182    4: AAC LTP (Long Term Prediction)
11183    5: SBR (Spectral Band Replication)
11184    6: AAC Scalable
11185   sampling freq
11186    0: 96000 Hz
11187    1: 88200 Hz
11188    2: 64000 Hz
11189    3: 48000 Hz
11190    4: 44100 Hz
11191    5: 32000 Hz
11192    6: 24000 Hz
11193    7: 22050 Hz
11194    8: 16000 Hz
11195    9: 12000 Hz
11196    10: 11025 Hz
11197    11: 8000 Hz
11198    12: 7350 Hz
11199    13: Reserved
11200    14: Reserved
11201    15: frequency is written explictly
11202    Channel Configurations
11203    These are the channel configurations:
11204    0: Defined in AOT Specifc Config
11205    1: 1 channel: front-center
11206    2: 2 channels: front-left, front-right
11207  */
11208  // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
11209  config[0] = adtsObjectType << 3;
11210  // samplingFrequencyIndex
11211  config[0] |= (adtsSamplingIndex & 0x0e) >> 1;
11212  config[1] |= (adtsSamplingIndex & 0x01) << 7;
11213  // channelConfiguration
11214  config[1] |= adtsChannelConfig << 3;
11215  if (adtsObjectType === 5) {
11216    // adtsExtensionSamplingIndex
11217    config[1] |= (adtsExtensionSamplingIndex & 0x0e) >> 1;
11218    config[2] = (adtsExtensionSamplingIndex & 0x01) << 7;
11219    // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???
11220    //    https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc
11221    config[2] |= 2 << 2;
11222    config[3] = 0;
11223  }
11224  return {
11225    config,
11226    samplerate: adtsSamplingRates[adtsSamplingIndex],
11227    channelCount: adtsChannelConfig,
11228    codec: 'mp4a.40.' + adtsObjectType,
11229    manifestCodec
11230  };
11231}
11232function isHeaderPattern$1(data, offset) {
11233  return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0;
11234}
11235function getHeaderLength(data, offset) {
11236  return data[offset + 1] & 0x01 ? 7 : 9;
11237}
11238function getFullFrameLength(data, offset) {
11239  return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xe0) >>> 5;
11240}
11241function canGetFrameLength(data, offset) {
11242  return offset + 5 < data.length;
11243}
11244function isHeader$1(data, offset) {
11245  // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
11246  // Layer bits (position 14 and 15) in header should be always 0 for ADTS
11247  // More info https://wiki.multimedia.cx/index.php?title=ADTS
11248  return offset + 1 < data.length && isHeaderPattern$1(data, offset);
11249}
11250function canParse$1(data, offset) {
11251  return canGetFrameLength(data, offset) && isHeaderPattern$1(data, offset) && getFullFrameLength(data, offset) <= data.length - offset;
11252}
11253function probe$1(data, offset) {
11254  // same as isHeader but we also check that ADTS frame follows last ADTS frame
11255  // or end of data is reached
11256  if (isHeader$1(data, offset)) {
11257    // ADTS header Length
11258    const headerLength = getHeaderLength(data, offset);
11259    if (offset + headerLength >= data.length) {
11260      return false;
vendor: 4,485 bytes, lines 11261-11429
11261    }
11262    // ADTS frame Length
11263    const frameLength = getFullFrameLength(data, offset);
11264    if (frameLength <= headerLength) {
11265      return false;
11266    }
11267    const newOffset = offset + frameLength;
11268    return newOffset === data.length || isHeader$1(data, newOffset);
11269  }
11270  return false;
11271}
11272function initTrackConfig(track, observer, data, offset, audioCodec) {
11273  if (!track.samplerate) {
11274    const config = getAudioConfig(observer, data, offset, audioCodec);
11275    if (!config) {
11276      return;
11277    }
11278    track.config = config.config;
11279    track.samplerate = config.samplerate;
11280    track.channelCount = config.channelCount;
11281    track.codec = config.codec;
11282    track.manifestCodec = config.manifestCodec;
11283    logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
11284  }
11285}
11286function getFrameDuration(samplerate) {
11287  return 1024 * 90000 / samplerate;
11288}
11289function parseFrameHeader(data, offset) {
11290  // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header
11291  const headerLength = getHeaderLength(data, offset);
11292  if (offset + headerLength <= data.length) {
11293    // retrieve frame size
11294    const frameLength = getFullFrameLength(data, offset) - headerLength;
11295    if (frameLength > 0) {
11296      // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`);
11297      return {
11298        headerLength,
11299        frameLength
11300      };
11301    }
11302  }
11303}
11304function appendFrame$2(track, data, offset, pts, frameIndex) {
11305  const frameDuration = getFrameDuration(track.samplerate);
11306  const stamp = pts + frameIndex * frameDuration;
11307  const header = parseFrameHeader(data, offset);
11308  let unit;
11309  if (header) {
11310    const {
11311      frameLength,
11312      headerLength
11313    } = header;
11314    const _length = headerLength + frameLength;
11315    const missing = Math.max(0, offset + _length - data.length);
11316    // logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`);
11317    if (missing) {
11318      unit = new Uint8Array(_length - headerLength);
11319      unit.set(data.subarray(offset + headerLength, data.length), 0);
11320    } else {
11321      unit = data.subarray(offset + headerLength, offset + _length);
11322    }
11323    const _sample = {
11324      unit,
11325      pts: stamp
11326    };
11327    if (!missing) {
11328      track.samples.push(_sample);
11329    }
11330    return {
11331      sample: _sample,
11332      length: _length,
11333      missing
11334    };
11335  }
11336  // overflow incomplete header
11337  const length = data.length - offset;
11338  unit = new Uint8Array(length);
11339  unit.set(data.subarray(offset, data.length), 0);
11340  const sample = {
11341    unit,
11342    pts: stamp
11343  };
11344  return {
11345    sample,
11346    length,
11347    missing: -1
11348  };
11349}
11350
11351/**
11352 *  MPEG parser helper
11353 */
11354
11355let chromeVersion$1 = null;
11356const 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];
11357const SamplingRateMap = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000];
11358const SamplesCoefficients = [
11359// MPEG 2.5
11360[0,
11361// Reserved
1136272,
11363// Layer3
11364144,
11365// Layer2
1136612 // Layer1
11367],
11368// Reserved
11369[0,
11370// Reserved
113710,
11372// Layer3
113730,
11374// Layer2
113750 // Layer1
11376],
11377// MPEG 2
11378[0,
11379// Reserved
1138072,
11381// Layer3
11382144,
11383// Layer2
1138412 // Layer1
11385],
11386// MPEG 1
11387[0,
11388// Reserved
11389144,
11390// Layer3
11391144,
11392// Layer2
1139312 // Layer1
11394]];
11395const BytesInSlot = [0,
11396// Reserved
113971,
11398// Layer3
113991,
11400// Layer2
114014 // Layer1
11402];
11403function appendFrame$1(track, data, offset, pts, frameIndex) {
11404  // Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference
11405  if (offset + 24 > data.length) {
11406    return;
11407  }
11408  const header = parseHeader(data, offset);
11409  if (header && offset + header.frameLength <= data.length) {
11410    const frameDuration = header.samplesPerFrame * 90000 / header.sampleRate;
11411    const stamp = pts + frameIndex * frameDuration;
11412    const sample = {
11413      unit: data.subarray(offset, offset + header.frameLength),
11414      pts: stamp,
11415      dts: stamp
11416    };
11417    track.config = [];
11418    track.channelCount = header.channelCount;
11419    track.samplerate = header.sampleRate;
11420    track.samples.push(sample);
11421    return {
11422      sample,
11423      length: header.frameLength,
11424      missing: 0
11425    };
11426  }
11427}
11428function parseHeader(data, offset) {
11429  const mpegVersion = data[offset + 1] >> 3 & 3;
vendor: 4,249 bytes, lines 11430-11535
11430  const mpegLayer = data[offset + 1] >> 1 & 3;
11431  const bitRateIndex = data[offset + 2] >> 4 & 15;
11432  const sampleRateIndex = data[offset + 2] >> 2 & 3;
11433  if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) {
11434    const paddingBit = data[offset + 2] >> 1 & 1;
11435    const channelMode = data[offset + 3] >> 6;
11436    const columnInBitrates = mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4;
11437    const bitRate = BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000;
11438    const columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2;
11439    const sampleRate = SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex];
11440    const channelCount = channelMode === 3 ? 1 : 2; // If bits of channel mode are `11` then it is a single channel (Mono)
11441    const sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer];
11442    const bytesInSlot = BytesInSlot[mpegLayer];
11443    const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot;
11444    const frameLength = Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot;
11445    if (chromeVersion$1 === null) {
11446      const userAgent = navigator.userAgent || '';
11447      const result = userAgent.match(/Chrome\/(\d+)/i);
11448      chromeVersion$1 = result ? parseInt(result[1]) : 0;
11449    }
11450    const needChromeFix = !!chromeVersion$1 && chromeVersion$1 <= 87;
11451    if (needChromeFix && mpegLayer === 2 && bitRate >= 224000 && channelMode === 0) {
11452      // Work around bug in Chromium by setting channelMode to dual-channel (01) instead of stereo (00)
11453      data[offset + 3] = data[offset + 3] | 0x80;
11454    }
11455    return {
11456      sampleRate,
11457      channelCount,
11458      frameLength,
11459      samplesPerFrame
11460    };
11461  }
11462}
11463function isHeaderPattern(data, offset) {
11464  return data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00;
11465}
11466function isHeader(data, offset) {
11467  // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
11468  // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
11469  // More info http://www.mp3-tech.org/programmer/frame_header.html
11470  return offset + 1 < data.length && isHeaderPattern(data, offset);
11471}
11472function canParse(data, offset) {
11473  const headerSize = 4;
11474  return isHeaderPattern(data, offset) && headerSize <= data.length - offset;
11475}
11476function probe(data, offset) {
11477  // same as isHeader but we also check that MPEG frame follows last MPEG frame
11478  // or end of data is reached
11479  if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
11480    // MPEG header Length
11481    const headerLength = 4;
11482    // MPEG frame Length
11483    const header = parseHeader(data, offset);
11484    let frameLength = headerLength;
11485    if (header != null && header.frameLength) {
11486      frameLength = header.frameLength;
11487    }
11488    const newOffset = offset + frameLength;
11489    return newOffset === data.length || isHeader(data, newOffset);
11490  }
11491  return false;
11492}
11493
11494/**
11495 * AAC demuxer
11496 */
11497class AACDemuxer extends BaseAudioDemuxer {
11498  constructor(observer, config) {
11499    super();
11500    this.observer = void 0;
11501    this.config = void 0;
11502    this.observer = observer;
11503    this.config = config;
11504  }
11505  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11506    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
11507    this._audioTrack = {
11508      container: 'audio/adts',
11509      type: 'audio',
11510      id: 2,
11511      pid: -1,
11512      sequenceNumber: 0,
11513      segmentCodec: 'aac',
11514      samples: [],
11515      manifestCodec: audioCodec,
11516      duration: trackDuration,
11517      inputTimeScale: 90000,
11518      dropped: 0
11519    };
11520  }
11521
11522  // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS
11523  static probe(data) {
11524    if (!data) {
11525      return false;
11526    }
11527
11528    // Check for the ADTS sync word
11529    // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
11530    // Layer bits (position 14 and 15) in header should be always 0 for ADTS
11531    // More info https://wiki.multimedia.cx/index.php?title=ADTS
11532    const id3Data = getID3Data(data, 0);
11533    let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
11534    if (probe(data, offset)) {
11535      return false;
vendor: 8,286 bytes, lines 11536-11783
11536    }
11537    for (let length = data.length; offset < length; offset++) {
11538      if (probe$1(data, offset)) {
11539        logger.log('ADTS sync word found !');
11540        return true;
11541      }
11542    }
11543    return false;
11544  }
11545  canParse(data, offset) {
11546    return canParse$1(data, offset);
11547  }
11548  appendFrame(track, data, offset) {
11549    initTrackConfig(track, this.observer, data, offset, track.manifestCodec);
11550    const frame = appendFrame$2(track, data, offset, this.basePTS, this.frameIndex);
11551    if (frame && frame.missing === 0) {
11552      return frame;
11553    }
11554  }
11555}
11556
11557const emsgSchemePattern = /\/emsg[-/]ID3/i;
11558class MP4Demuxer {
11559  constructor(observer, config) {
11560    this.remainderData = null;
11561    this.timeOffset = 0;
11562    this.config = void 0;
11563    this.videoTrack = void 0;
11564    this.audioTrack = void 0;
11565    this.id3Track = void 0;
11566    this.txtTrack = void 0;
11567    this.config = config;
11568  }
11569  resetTimeStamp() {}
11570  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11571    const videoTrack = this.videoTrack = dummyTrack('video', 1);
11572    const audioTrack = this.audioTrack = dummyTrack('audio', 1);
11573    const captionTrack = this.txtTrack = dummyTrack('text', 1);
11574    this.id3Track = dummyTrack('id3', 1);
11575    this.timeOffset = 0;
11576    if (!(initSegment != null && initSegment.byteLength)) {
11577      return;
11578    }
11579    const initData = parseInitSegment(initSegment);
11580    if (initData.video) {
11581      const {
11582        id,
11583        timescale,
11584        codec
11585      } = initData.video;
11586      videoTrack.id = id;
11587      videoTrack.timescale = captionTrack.timescale = timescale;
11588      videoTrack.codec = codec;
11589    }
11590    if (initData.audio) {
11591      const {
11592        id,
11593        timescale,
11594        codec
11595      } = initData.audio;
11596      audioTrack.id = id;
11597      audioTrack.timescale = timescale;
11598      audioTrack.codec = codec;
11599    }
11600    captionTrack.id = RemuxerTrackIdConfig.text;
11601    videoTrack.sampleDuration = 0;
11602    videoTrack.duration = audioTrack.duration = trackDuration;
11603  }
11604  resetContiguity() {
11605    this.remainderData = null;
11606  }
11607  static probe(data) {
11608    return hasMoofData(data);
11609  }
11610  demux(data, timeOffset) {
11611    this.timeOffset = timeOffset;
11612    // 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
11613    let videoSamples = data;
11614    const videoTrack = this.videoTrack;
11615    const textTrack = this.txtTrack;
11616    if (this.config.progressive) {
11617      // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else.
11618      // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee
11619      // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception.
11620      if (this.remainderData) {
11621        videoSamples = appendUint8Array(this.remainderData, data);
11622      }
11623      const segmentedData = segmentValidRange(videoSamples);
11624      this.remainderData = segmentedData.remainder;
11625      videoTrack.samples = segmentedData.valid || new Uint8Array();
11626    } else {
11627      videoTrack.samples = videoSamples;
11628    }
11629    const id3Track = this.extractID3Track(videoTrack, timeOffset);
11630    textTrack.samples = parseSamples(timeOffset, videoTrack);
11631    return {
11632      videoTrack,
11633      audioTrack: this.audioTrack,
11634      id3Track,
11635      textTrack: this.txtTrack
11636    };
11637  }
11638  flush() {
11639    const timeOffset = this.timeOffset;
11640    const videoTrack = this.videoTrack;
11641    const textTrack = this.txtTrack;
11642    videoTrack.samples = this.remainderData || new Uint8Array();
11643    this.remainderData = null;
11644    const id3Track = this.extractID3Track(videoTrack, this.timeOffset);
11645    textTrack.samples = parseSamples(timeOffset, videoTrack);
11646    return {
11647      videoTrack,
11648      audioTrack: dummyTrack(),
11649      id3Track,
11650      textTrack: dummyTrack()
11651    };
11652  }
11653  extractID3Track(videoTrack, timeOffset) {
11654    const id3Track = this.id3Track;
11655    if (videoTrack.samples.length) {
11656      const emsgs = findBox(videoTrack.samples, ['emsg']);
11657      if (emsgs) {
11658        emsgs.forEach(data => {
11659          const emsgInfo = parseEmsg(data);
11660          if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) {
11661            const pts = isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale : timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale;
11662            let duration = emsgInfo.eventDuration === 0xffffffff ? Number.POSITIVE_INFINITY : emsgInfo.eventDuration / emsgInfo.timeScale;
11663            // Safari takes anything <= 0.001 seconds and maps it to Infinity
11664            if (duration <= 0.001) {
11665              duration = Number.POSITIVE_INFINITY;
11666            }
11667            const payload = emsgInfo.payload;
11668            id3Track.samples.push({
11669              data: payload,
11670              len: payload.byteLength,
11671              dts: pts,
11672              pts: pts,
11673              type: MetadataSchema.emsg,
11674              duration: duration
11675            });
11676          }
11677        });
11678      }
11679    }
11680    return id3Track;
11681  }
11682  demuxSampleAes(data, keyData, timeOffset) {
11683    return Promise.reject(new Error('The MP4 demuxer does not support SAMPLE-AES decryption'));
11684  }
11685  destroy() {}
11686}
11687
11688const getAudioBSID = (data, offset) => {
11689  // check the bsid to confirm ac-3 | ec-3
11690  let bsid = 0;
11691  let numBits = 5;
11692  offset += numBits;
11693  const temp = new Uint32Array(1); // unsigned 32 bit for temporary storage
11694  const mask = new Uint32Array(1); // unsigned 32 bit mask value
11695  const byte = new Uint8Array(1); // unsigned 8 bit for temporary storage
11696  while (numBits > 0) {
11697    byte[0] = data[offset];
11698    // read remaining bits, upto 8 bits at a time
11699    const bits = Math.min(numBits, 8);
11700    const shift = 8 - bits;
11701    mask[0] = 0xff000000 >>> 24 + shift << shift;
11702    temp[0] = (byte[0] & mask[0]) >> shift;
11703    bsid = !bsid ? temp[0] : bsid << bits | temp[0];
11704    offset += 1;
11705    numBits -= bits;
11706  }
11707  return bsid;
11708};
11709
11710class AC3Demuxer extends BaseAudioDemuxer {
11711  constructor(observer) {
11712    super();
11713    this.observer = void 0;
11714    this.observer = observer;
11715  }
11716  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11717    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
11718    this._audioTrack = {
11719      container: 'audio/ac-3',
11720      type: 'audio',
11721      id: 2,
11722      pid: -1,
11723      sequenceNumber: 0,
11724      segmentCodec: 'ac3',
11725      samples: [],
11726      manifestCodec: audioCodec,
11727      duration: trackDuration,
11728      inputTimeScale: 90000,
11729      dropped: 0
11730    };
11731  }
11732  canParse(data, offset) {
11733    return offset + 64 < data.length;
11734  }
11735  appendFrame(track, data, offset) {
11736    const frameLength = appendFrame(track, data, offset, this.basePTS, this.frameIndex);
11737    if (frameLength !== -1) {
11738      const sample = track.samples[track.samples.length - 1];
11739      return {
11740        sample,
11741        length: frameLength,
11742        missing: 0
11743      };
11744    }
11745  }
11746  static probe(data) {
11747    if (!data) {
11748      return false;
11749    }
11750    const id3Data = getID3Data(data, 0);
11751    if (!id3Data) {
11752      return false;
11753    }
11754
11755    // look for the ac-3 sync bytes
11756    const offset = id3Data.length;
11757    if (data[offset] === 0x0b && data[offset + 1] === 0x77 && getTimeStamp(id3Data) !== undefined &&
11758    // check the bsid to confirm ac-3
11759    getAudioBSID(data, offset) < 16) {
11760      return true;
11761    }
11762    return false;
11763  }
11764}
11765function appendFrame(track, data, start, pts, frameIndex) {
11766  if (start + 8 > data.length) {
11767    return -1; // not enough bytes left
11768  }
11769  if (data[start] !== 0x0b || data[start + 1] !== 0x77) {
11770    return -1; // invalid magic
11771  }
11772
11773  // get sample rate
11774  const samplingRateCode = data[start + 4] >> 6;
11775  if (samplingRateCode >= 3) {
11776    return -1; // invalid sampling rate
11777  }
11778  const samplingRateMap = [48000, 44100, 32000];
11779  const sampleRate = samplingRateMap[samplingRateCode];
11780
11781  // get frame size
11782  const frameSizeCode = data[start + 4] & 0x3f;
11783  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, 5
vendor: 5,407 bytes, lines 11783-11956
1178376, 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];
11784  const frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2;
11785  if (start + frameLength > data.length) {
11786    return -1;
11787  }
11788
11789  // get channel count
11790  const channelMode = data[start + 6] >> 5;
11791  let skipCount = 0;
11792  if (channelMode === 2) {
11793    skipCount += 2;
11794  } else {
11795    if (channelMode & 1 && channelMode !== 1) {
11796      skipCount += 2;
11797    }
11798    if (channelMode & 4) {
11799      skipCount += 2;
11800    }
11801  }
11802  const lfeon = (data[start + 6] << 8 | data[start + 7]) >> 12 - skipCount & 1;
11803  const channelsMap = [2, 1, 2, 3, 3, 4, 4, 5];
11804  const channelCount = channelsMap[channelMode] + lfeon;
11805
11806  // build dac3 box
11807  const bsid = data[start + 5] >> 3;
11808  const bsmod = data[start + 5] & 7;
11809  const config = new Uint8Array([samplingRateCode << 6 | bsid << 1 | bsmod >> 2, (bsmod & 3) << 6 | channelMode << 3 | lfeon << 2 | frameSizeCode >> 4, frameSizeCode << 4 & 0xe0]);
11810  const frameDuration = 1536 / sampleRate * 90000;
11811  const stamp = pts + frameIndex * frameDuration;
11812  const unit = data.subarray(start, start + frameLength);
11813  track.config = config;
11814  track.channelCount = channelCount;
11815  track.samplerate = sampleRate;
11816  track.samples.push({
11817    unit,
11818    pts: stamp
11819  });
11820  return frameLength;
11821}
11822
11823class BaseVideoParser {
11824  constructor() {
11825    this.VideoSample = null;
11826  }
11827  createVideoSample(key, pts, dts, debug) {
11828    return {
11829      key,
11830      frame: false,
11831      pts,
11832      dts,
11833      units: [],
11834      debug,
11835      length: 0
11836    };
11837  }
11838  getLastNalUnit(samples) {
11839    var _VideoSample;
11840    let VideoSample = this.VideoSample;
11841    let lastUnit;
11842    // try to fallback to previous sample if current one is empty
11843    if (!VideoSample || VideoSample.units.length === 0) {
11844      VideoSample = samples[samples.length - 1];
11845    }
11846    if ((_VideoSample = VideoSample) != null && _VideoSample.units) {
11847      const units = VideoSample.units;
11848      lastUnit = units[units.length - 1];
11849    }
11850    return lastUnit;
11851  }
11852  pushAccessUnit(VideoSample, videoTrack) {
11853    if (VideoSample.units.length && VideoSample.frame) {
11854      // if sample does not have PTS/DTS, patch with last sample PTS/DTS
11855      if (VideoSample.pts === undefined) {
11856        const samples = videoTrack.samples;
11857        const nbSamples = samples.length;
11858        if (nbSamples) {
11859          const lastSample = samples[nbSamples - 1];
11860          VideoSample.pts = lastSample.pts;
11861          VideoSample.dts = lastSample.dts;
11862        } else {
11863          // dropping samples, no timestamp found
11864          videoTrack.dropped++;
11865          return;
11866        }
11867      }
11868      videoTrack.samples.push(VideoSample);
11869    }
11870    if (VideoSample.debug.length) {
11871      logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
11872    }
11873  }
11874}
11875
11876/**
11877 * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
11878 */
11879
11880class ExpGolomb {
11881  constructor(data) {
11882    this.data = void 0;
11883    this.bytesAvailable = void 0;
11884    this.word = void 0;
11885    this.bitsAvailable = void 0;
11886    this.data = data;
11887    // the number of bytes left to examine in this.data
11888    this.bytesAvailable = data.byteLength;
11889    // the current word being examined
11890    this.word = 0; // :uint
11891    // the number of bits left to examine in the current word
11892    this.bitsAvailable = 0; // :uint
11893  }
11894
11895  // ():void
11896  loadWord() {
11897    const data = this.data;
11898    const bytesAvailable = this.bytesAvailable;
11899    const position = data.byteLength - bytesAvailable;
11900    const workingBytes = new Uint8Array(4);
11901    const availableBytes = Math.min(4, bytesAvailable);
11902    if (availableBytes === 0) {
11903      throw new Error('no bytes available');
11904    }
11905    workingBytes.set(data.subarray(position, position + availableBytes));
11906    this.word = new DataView(workingBytes.buffer).getUint32(0);
11907    // track the amount of this.data that has been processed
11908    this.bitsAvailable = availableBytes * 8;
11909    this.bytesAvailable -= availableBytes;
11910  }
11911
11912  // (count:int):void
11913  skipBits(count) {
11914    let skipBytes; // :int
11915    count = Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable);
11916    if (this.bitsAvailable > count) {
11917      this.word <<= count;
11918      this.bitsAvailable -= count;
11919    } else {
11920      count -= this.bitsAvailable;
11921      skipBytes = count >> 3;
11922      count -= skipBytes << 3;
11923      this.bytesAvailable -= skipBytes;
11924      this.loadWord();
11925      this.word <<= count;
11926      this.bitsAvailable -= count;
11927    }
11928  }
11929
11930  // (size:int):uint
11931  readBits(size) {
11932    let bits = Math.min(this.bitsAvailable, size); // :uint
11933    const valu = this.word >>> 32 - bits; // :uint
11934    if (size > 32) {
11935      logger.error('Cannot read more than 32 bits at a time');
11936    }
11937    this.bitsAvailable -= bits;
11938    if (this.bitsAvailable > 0) {
11939      this.word <<= bits;
11940    } else if (this.bytesAvailable > 0) {
11941      this.loadWord();
11942    } else {
11943      throw new Error('no bits available');
11944    }
11945    bits = size - bits;
11946    if (bits > 0 && this.bitsAvailable) {
11947      return valu << bits | this.readBits(bits);
11948    } else {
11949      return valu;
11950    }
11951  }
11952
11953  // ():uint
11954  skipLZ() {
11955    let leadingZeroCount; // :uint
11956    for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable;
vendor: 7,497 bytes, lines 11956-12206
11956 ++leadingZeroCount) {
11957      if ((this.word & 0x80000000 >>> leadingZeroCount) !== 0) {
11958        // the first bit of working word is 1
11959        this.word <<= leadingZeroCount;
11960        this.bitsAvailable -= leadingZeroCount;
11961        return leadingZeroCount;
11962      }
11963    }
11964    // we exhausted word and still have not found a 1
11965    this.loadWord();
11966    return leadingZeroCount + this.skipLZ();
11967  }
11968
11969  // ():void
11970  skipUEG() {
11971    this.skipBits(1 + this.skipLZ());
11972  }
11973
11974  // ():void
11975  skipEG() {
11976    this.skipBits(1 + this.skipLZ());
11977  }
11978
11979  // ():uint
11980  readUEG() {
11981    const clz = this.skipLZ(); // :uint
11982    return this.readBits(clz + 1) - 1;
11983  }
11984
11985  // ():int
11986  readEG() {
11987    const valu = this.readUEG(); // :int
11988    if (0x01 & valu) {
11989      // the number is odd if the low order bit is set
11990      return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
11991    } else {
11992      return -1 * (valu >>> 1); // divide by two then make it negative
11993    }
11994  }
11995
11996  // Some convenience functions
11997  // :Boolean
11998  readBoolean() {
11999    return this.readBits(1) === 1;
12000  }
12001
12002  // ():int
12003  readUByte() {
12004    return this.readBits(8);
12005  }
12006
12007  // ():int
12008  readUShort() {
12009    return this.readBits(16);
12010  }
12011
12012  // ():int
12013  readUInt() {
12014    return this.readBits(32);
12015  }
12016
12017  /**
12018   * Advance the ExpGolomb decoder past a scaling list. The scaling
12019   * list is optionally transmitted as part of a sequence parameter
12020   * set and is not relevant to transmuxing.
12021   * @param count the number of entries in this scaling list
12022   * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
12023   */
12024  skipScalingList(count) {
12025    let lastScale = 8;
12026    let nextScale = 8;
12027    let deltaScale;
12028    for (let j = 0; j < count; j++) {
12029      if (nextScale !== 0) {
12030        deltaScale = this.readEG();
12031        nextScale = (lastScale + deltaScale + 256) % 256;
12032      }
12033      lastScale = nextScale === 0 ? lastScale : nextScale;
12034    }
12035  }
12036
12037  /**
12038   * Read a sequence parameter set and return some interesting video
12039   * properties. A sequence parameter set is the H264 metadata that
12040   * describes the properties of upcoming video frames.
12041   * @returns an object with configuration parsed from the
12042   * sequence parameter set, including the dimensions of the
12043   * associated video frames.
12044   */
12045  readSPS() {
12046    let frameCropLeftOffset = 0;
12047    let frameCropRightOffset = 0;
12048    let frameCropTopOffset = 0;
12049    let frameCropBottomOffset = 0;
12050    let numRefFramesInPicOrderCntCycle;
12051    let scalingListCount;
12052    let i;
12053    const readUByte = this.readUByte.bind(this);
12054    const readBits = this.readBits.bind(this);
12055    const readUEG = this.readUEG.bind(this);
12056    const readBoolean = this.readBoolean.bind(this);
12057    const skipBits = this.skipBits.bind(this);
12058    const skipEG = this.skipEG.bind(this);
12059    const skipUEG = this.skipUEG.bind(this);
12060    const skipScalingList = this.skipScalingList.bind(this);
12061    readUByte();
12062    const profileIdc = readUByte(); // profile_idc
12063    readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
12064    skipBits(3); // reserved_zero_3bits u(3),
12065    readUByte(); // level_idc u(8)
12066    skipUEG(); // seq_parameter_set_id
12067    // some profiles have more optional data we don't need
12068    if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
12069      const chromaFormatIdc = readUEG();
12070      if (chromaFormatIdc === 3) {
12071        skipBits(1);
12072      } // separate_colour_plane_flag
12073
12074      skipUEG(); // bit_depth_luma_minus8
12075      skipUEG(); // bit_depth_chroma_minus8
12076      skipBits(1); // qpprime_y_zero_transform_bypass_flag
12077      if (readBoolean()) {
12078        // seq_scaling_matrix_present_flag
12079        scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
12080        for (i = 0; i < scalingListCount; i++) {
12081          if (readBoolean()) {
12082            // seq_scaling_list_present_flag[ i ]
12083            if (i < 6) {
12084              skipScalingList(16);
12085            } else {
12086              skipScalingList(64);
12087            }
12088          }
12089        }
12090      }
12091    }
12092    skipUEG(); // log2_max_frame_num_minus4
12093    const picOrderCntType = readUEG();
12094    if (picOrderCntType === 0) {
12095      readUEG(); // log2_max_pic_order_cnt_lsb_minus4
12096    } else if (picOrderCntType === 1) {
12097      skipBits(1); // delta_pic_order_always_zero_flag
12098      skipEG(); // offset_for_non_ref_pic
12099      skipEG(); // offset_for_top_to_bottom_field
12100      numRefFramesInPicOrderCntCycle = readUEG();
12101      for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
12102        skipEG();
12103      } // offset_for_ref_frame[ i ]
12104    }
12105    skipUEG(); // max_num_ref_frames
12106    skipBits(1); // gaps_in_frame_num_value_allowed_flag
12107    const picWidthInMbsMinus1 = readUEG();
12108    const picHeightInMapUnitsMinus1 = readUEG();
12109    const frameMbsOnlyFlag = readBits(1);
12110    if (frameMbsOnlyFlag === 0) {
12111      skipBits(1);
12112    } // mb_adaptive_frame_field_flag
12113
12114    skipBits(1); // direct_8x8_inference_flag
12115    if (readBoolean()) {
12116      // frame_cropping_flag
12117      frameCropLeftOffset = readUEG();
12118      frameCropRightOffset = readUEG();
12119      frameCropTopOffset = readUEG();
12120      frameCropBottomOffset = readUEG();
12121    }
12122    let pixelRatio = [1, 1];
12123    if (readBoolean()) {
12124      // vui_parameters_present_flag
12125      if (readBoolean()) {
12126        // aspect_ratio_info_present_flag
12127        const aspectRatioIdc = readUByte();
12128        switch (aspectRatioIdc) {
12129          case 1:
12130            pixelRatio = [1, 1];
12131            break;
12132          case 2:
12133            pixelRatio = [12, 11];
12134            break;
12135          case 3:
12136            pixelRatio = [10, 11];
12137            break;
12138          case 4:
12139            pixelRatio = [16, 11];
12140            break;
12141          case 5:
12142            pixelRatio = [40, 33];
12143            break;
12144          case 6:
12145            pixelRatio = [24, 11];
12146            break;
12147          case 7:
12148            pixelRatio = [20, 11];
12149            break;
12150          case 8:
12151            pixelRatio = [32, 11];
12152            break;
12153          case 9:
12154            pixelRatio = [80, 33];
12155            break;
12156          case 10:
12157            pixelRatio = [18, 11];
12158            break;
12159          case 11:
12160            pixelRatio = [15, 11];
12161            break;
12162          case 12:
12163            pixelRatio = [64, 33];
12164            break;
12165          case 13:
12166            pixelRatio = [160, 99];
12167            break;
12168          case 14:
12169            pixelRatio = [4, 3];
12170            break;
12171          case 15:
12172            pixelRatio = [3, 2];
12173            break;
12174          case 16:
12175            pixelRatio = [2, 1];
12176            break;
12177          case 255:
12178            {
12179              pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
12180              break;
12181            }
12182        }
12183      }
12184    }
12185    return {
12186      width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
12187      height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
12188      pixelRatio: pixelRatio
12189    };
12190  }
12191  readSliceType() {
12192    // skip NALu type
12193    this.readUByte();
12194    // discard first_mb_in_slice
12195    this.readUEG();
12196    // return slice_type
12197    return this.readUEG();
12198  }
12199}
12200
12201class AvcVideoParser extends BaseVideoParser {
12202  parseAVCPES(track, textTrack, pes, last, duration) {
12203    const units = this.parseAVCNALu(track, pes.data);
12204    let VideoSample = this.VideoSample;
12205    let push;
12206    let spsfound = false;
12207    // free pes.data to save up some memory
12208    pes.data = null;
12209
12210    // if new NAL units found and last sample still there, let's push ...
12211    // this helps parsing streams with missing AUD (only do this if AUD never found)
12212    if (VideoSample && units.length && !track.audFound) {
12213      this.pushAccessUnit(VideoSample, track);
12214      VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
12215    }
12216    units.forEach(unit => {
12217      var _VideoSample2;
12218      switch (unit.type) {
12219        // NDR
12220        case 1:
12221          {
12222            let iskey = false;
12223            push = true;
12224            const data = unit.data;
12225            // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
12226            if (spsfound && data.length > 4) {
12227              // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
12228              const sliceType = new ExpGolomb(data).readSliceType();
12229              // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
12230              // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
12231              // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
12232              // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
12233              // if (sliceType === 2 || sliceType === 7) {
12234              if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
12235                iskey = true;
12236              }
12237            }
12238            if (iskey) {
12239              var _VideoSample;
12240              // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
12241              if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12242                this.pushAccessUnit(VideoSample, track);
12243                VideoSample = this.VideoSample = null;
12244              }
12245            }
12246            if (!VideoSample) {
12247              VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12248            }
12249            VideoSample.frame = true;
12250            VideoSample.key = iskey;
12251            break;
12252            // IDR
12253          }
12254        case 5:
12255          push = true;
12256          // handle PES not starting with AUD
12257          // if we have frame data already, that cannot belong to the same frame, so force a push
12258          if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
12259            this.pushAccessUnit(VideoSample, track);
12260            VideoSample = this.VideoSample = null;
12261          }
12262          if (!VideoSample) {
12263            VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
12264          }
12265          VideoSample.key = true;
12266          VideoSample.frame = true;
12267          break;
12268        // SEI
12269        case 6:
12270          {
12271            push = true;
12272            parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
12273            break;
12274            // SPS
12275          }
12276        case 7:
12277          {
12278            var _track$pixelRatio, _track$pixelRatio2;
12279            push = true;
12280            spsfound = true;
12281            const sps = unit.data;
12282            const expGolombDecoder = new ExpGolomb(sps);
12283            const config = expGolombDecoder.readSPS();
12284            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]) {
12285              track.width = config.width;
12286              track.height = config.height;
12287              track.pixelRatio = config.pixelRatio;
12288              track.sps = [sps];
12289              track.duration = duration;
12290              const codecarray = sps.subarray(1, 4);
12291              let codecstring = 'avc1.';
12292              for (let i = 0; i < 3; i++) {
12293                let h = codecarray[i].toString(16);
12294                if (h.length < 2) {
12295                  h = '0' + h;
12296                }
12297                codecstring += h;
12298              }
12299              track.codec = codecstring;
12300            }
12301            break;
12302          }
12303        // PPS
12304        case 8:
12305          push = true;
12306          track.pps = [unit.data];
12307          break;
12308        // AUD
12309        case 9:
12310          push = true;
12311          track.audFound = true;
12312          if (VideoSample) {
12313            this.pushAccessUnit(VideoSample, track);
12314          }
12315          VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pe
vendor: 8,181 bytes, lines 12315-12552
12315s.dts, '');
12316          break;
12317        // Filler Data
12318        case 12:
12319          push = true;
12320          break;
12321        default:
12322          push = false;
12323          if (VideoSample) {
12324            VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
12325          }
12326          break;
12327      }
12328      if (VideoSample && push) {
12329        const units = VideoSample.units;
12330        units.push(unit);
12331      }
12332    });
12333    // if last PES packet, push samples
12334    if (last && VideoSample) {
12335      this.pushAccessUnit(VideoSample, track);
12336      this.VideoSample = null;
12337    }
12338  }
12339  parseAVCNALu(track, array) {
12340    const len = array.byteLength;
12341    let state = track.naluState || 0;
12342    const lastState = state;
12343    const units = [];
12344    let i = 0;
12345    let value;
12346    let overflow;
12347    let unitType;
12348    let lastUnitStart = -1;
12349    let lastUnitType = 0;
12350    // logger.log('PES:' + Hex.hexDump(array));
12351
12352    if (state === -1) {
12353      // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
12354      lastUnitStart = 0;
12355      // NALu type is value read from offset 0
12356      lastUnitType = array[0] & 0x1f;
12357      state = 0;
12358      i = 1;
12359    }
12360    while (i < len) {
12361      value = array[i++];
12362      // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
12363      if (!state) {
12364        state = value ? 0 : 1;
12365        continue;
12366      }
12367      if (state === 1) {
12368        state = value ? 0 : 2;
12369        continue;
12370      }
12371      // here we have state either equal to 2 or 3
12372      if (!value) {
12373        state = 3;
12374      } else if (value === 1) {
12375        overflow = i - state - 1;
12376        if (lastUnitStart >= 0) {
12377          const unit = {
12378            data: array.subarray(lastUnitStart, overflow),
12379            type: lastUnitType
12380          };
12381          // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
12382          units.push(unit);
12383        } else {
12384          // lastUnitStart is undefined => this is the first start code found in this PES packet
12385          // first check if start code delimiter is overlapping between 2 PES packets,
12386          // ie it started in last packet (lastState not zero)
12387          // and ended at the beginning of this PES packet (i <= 4 - lastState)
12388          const lastUnit = this.getLastNalUnit(track.samples);
12389          if (lastUnit) {
12390            if (lastState && i <= 4 - lastState) {
12391              // start delimiter overlapping between PES packets
12392              // strip start delimiter bytes from the end of last NAL unit
12393              // check if lastUnit had a state different from zero
12394              if (lastUnit.state) {
12395                // strip last bytes
12396                lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
12397              }
12398            }
12399            // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
12400
12401            if (overflow > 0) {
12402              // logger.log('first NALU found with overflow:' + overflow);
12403              lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
12404              lastUnit.state = 0;
12405            }
12406          }
12407        }
12408        // check if we can read unit type
12409        if (i < len) {
12410          unitType = array[i] & 0x1f;
12411          // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
12412          lastUnitStart = i;
12413          lastUnitType = unitType;
12414          state = 0;
12415        } else {
12416          // not enough byte to read unit type. let's read it on next PES parsing
12417          state = -1;
12418        }
12419      } else {
12420        state = 0;
12421      }
12422    }
12423    if (lastUnitStart >= 0 && state >= 0) {
12424      const unit = {
12425        data: array.subarray(lastUnitStart, len),
12426        type: lastUnitType,
12427        state: state
12428      };
12429      units.push(unit);
12430      // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
12431    }
12432    // no NALu found
12433    if (units.length === 0) {
12434      // append pes.data to previous NAL unit
12435      const lastUnit = this.getLastNalUnit(track.samples);
12436      if (lastUnit) {
12437        lastUnit.data = appendUint8Array(lastUnit.data, array);
12438      }
12439    }
12440    track.naluState = state;
12441    return units;
12442  }
12443}
12444
12445/**
12446 * SAMPLE-AES decrypter
12447 */
12448
12449class SampleAesDecrypter {
12450  constructor(observer, config, keyData) {
12451    this.keyData = void 0;
12452    this.decrypter = void 0;
12453    this.keyData = keyData;
12454    this.decrypter = new Decrypter(config, {
12455      removePKCS7Padding: false
12456    });
12457  }
12458  decryptBuffer(encryptedData) {
12459    return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
12460  }
12461
12462  // AAC - encrypt all full 16 bytes blocks starting from offset 16
12463  decryptAacSample(samples, sampleIndex, callback) {
12464    const curUnit = samples[sampleIndex].unit;
12465    if (curUnit.length <= 16) {
12466      // No encrypted portion in this sample (first 16 bytes is not
12467      // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html),
12468      return;
12469    }
12470    const encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16);
12471    const encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length);
12472    this.decryptBuffer(encryptedBuffer).then(decryptedBuffer => {
12473      const decryptedData = new Uint8Array(decryptedBuffer);
12474      curUnit.set(decryptedData, 16);
12475      if (!this.decrypter.isSync()) {
12476        this.decryptAacSamples(samples, sampleIndex + 1, callback);
12477      }
12478    });
12479  }
12480  decryptAacSamples(samples, sampleIndex, callback) {
12481    for (;; sampleIndex++) {
12482      if (sampleIndex >= samples.length) {
12483        callback();
12484        return;
12485      }
12486      if (samples[sampleIndex].unit.length < 32) {
12487        continue;
12488      }
12489      this.decryptAacSample(samples, sampleIndex, callback);
12490      if (!this.decrypter.isSync()) {
12491        return;
12492      }
12493    }
12494  }
12495
12496  // AVC - encrypt one 16 bytes block out of ten, starting from offset 32
12497  getAvcEncryptedData(decodedData) {
12498    const encryptedDataLen = Math.floor((decodedData.length - 48) / 160) * 16 + 16;
12499    const encryptedData = new Int8Array(encryptedDataLen);
12500    let outputPos = 0;
12501    for (let inputPos = 32; inputPos < decodedData.length - 16; inputPos += 160, outputPos += 16) {
12502      encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos);
12503    }
12504    return encryptedData;
12505  }
12506  getAvcDecryptedUnit(decodedData, decryptedData) {
12507    const uint8DecryptedData = new Uint8Array(decryptedData);
12508    let inputPos = 0;
12509    for (let outputPos = 32; outputPos < decodedData.length - 16; outputPos += 160, inputPos += 16) {
12510      decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos);
12511    }
12512    return decodedData;
12513  }
12514  decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit) {
12515    const decodedData = discardEPB(curUnit.data);
12516    const encryptedData = this.getAvcEncryptedData(decodedData);
12517    this.decryptBuffer(encryptedData.buffer).then(decryptedBuffer => {
12518      curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer);
12519      if (!this.decrypter.isSync()) {
12520        this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback);
12521      }
12522    });
12523  }
12524  decryptAvcSamples(samples, sampleIndex, unitIndex, callback) {
12525    if (samples instanceof Uint8Array) {
12526      throw new Error('Cannot decrypt samples of type Uint8Array');
12527    }
12528    for (;; sampleIndex++, unitIndex = 0) {
12529      if (sampleIndex >= samples.length) {
12530        callback();
12531        return;
12532      }
12533      const curUnits = samples[sampleIndex].units;
12534      for (;; unitIndex++) {
12535        if (unitIndex >= curUnits.length) {
12536          break;
12537        }
12538        const curUnit = curUnits[unitIndex];
12539        if (curUnit.data.length <= 48 || curUnit.type !== 1 && curUnit.type !== 5) {
12540          continue;
12541        }
12542        this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit);
12543        if (!this.decrypter.isSync()) {
12544          return;
12545        }
12546      }
12547    }
12548  }
12549}
12550
12551const PACKET_LENGTH = 188;
12552class TSDemuxer {
vendor: 5,984 bytes, lines 12553-12737
12553  constructor(observer, config, typeSupported) {
12554    this.observer = void 0;
12555    this.config = void 0;
12556    this.typeSupported = void 0;
12557    this.sampleAes = null;
12558    this.pmtParsed = false;
12559    this.audioCodec = void 0;
12560    this.videoCodec = void 0;
12561    this._duration = 0;
12562    this._pmtId = -1;
12563    this._videoTrack = void 0;
12564    this._audioTrack = void 0;
12565    this._id3Track = void 0;
12566    this._txtTrack = void 0;
12567    this.aacOverFlow = null;
12568    this.remainderData = null;
12569    this.videoParser = void 0;
12570    this.observer = observer;
12571    this.config = config;
12572    this.typeSupported = typeSupported;
12573    this.videoParser = new AvcVideoParser();
12574  }
12575  static probe(data) {
12576    const syncOffset = TSDemuxer.syncOffset(data);
12577    if (syncOffset > 0) {
12578      logger.warn(`MPEG2-TS detected but first sync word found @ offset ${syncOffset}`);
12579    }
12580    return syncOffset !== -1;
12581  }
12582  static syncOffset(data) {
12583    const length = data.length;
12584    let scanwindow = Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1;
12585    let i = 0;
12586    while (i < scanwindow) {
12587      // a TS init segment should contain at least 2 TS packets: PAT and PMT, each starting with 0x47
12588      let foundPat = false;
12589      let packetStart = -1;
12590      let tsPackets = 0;
12591      for (let j = i; j < length; j += PACKET_LENGTH) {
12592        if (data[j] === 0x47 && (length - j === PACKET_LENGTH || data[j + PACKET_LENGTH] === 0x47)) {
12593          tsPackets++;
12594          if (packetStart === -1) {
12595            packetStart = j;
12596            // First sync word found at offset, increase scan length (#5251)
12597            if (packetStart !== 0) {
12598              scanwindow = Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1;
12599            }
12600          }
12601          if (!foundPat) {
12602            foundPat = parsePID(data, j) === 0;
12603          }
12604          // Sync word found at 0 with 3 packets, or found at offset least 2 packets up to scanwindow (#5501)
12605          if (foundPat && tsPackets > 1 && (packetStart === 0 && tsPackets > 2 || j + PACKET_LENGTH > scanwindow)) {
12606            return packetStart;
12607          }
12608        } else if (tsPackets) {
12609          // Exit if sync word found, but does not contain contiguous packets
12610          return -1;
12611        } else {
12612          break;
12613        }
12614      }
12615      i++;
12616    }
12617    return -1;
12618  }
12619
12620  /**
12621   * Creates a track model internal to demuxer used to drive remuxing input
12622   */
12623  static createTrack(type, duration) {
12624    return {
12625      container: type === 'video' || type === 'audio' ? 'video/mp2t' : undefined,
12626      type,
12627      id: RemuxerTrackIdConfig[type],
12628      pid: -1,
12629      inputTimeScale: 90000,
12630      sequenceNumber: 0,
12631      samples: [],
12632      dropped: 0,
12633      duration: type === 'audio' ? duration : undefined
12634    };
12635  }
12636
12637  /**
12638   * Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start)
12639   * Resets all internal track instances of the demuxer.
12640   */
12641  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
12642    this.pmtParsed = false;
12643    this._pmtId = -1;
12644    this._videoTrack = TSDemuxer.createTrack('video');
12645    this._audioTrack = TSDemuxer.createTrack('audio', trackDuration);
12646    this._id3Track = TSDemuxer.createTrack('id3');
12647    this._txtTrack = TSDemuxer.createTrack('text');
12648    this._audioTrack.segmentCodec = 'aac';
12649
12650    // flush any partial content
12651    this.aacOverFlow = null;
12652    this.remainderData = null;
12653    this.audioCodec = audioCodec;
12654    this.videoCodec = videoCodec;
12655    this._duration = trackDuration;
12656  }
12657  resetTimeStamp() {}
12658  resetContiguity() {
12659    const {
12660      _audioTrack,
12661      _videoTrack,
12662      _id3Track
12663    } = this;
12664    if (_audioTrack) {
12665      _audioTrack.pesData = null;
12666    }
12667    if (_videoTrack) {
12668      _videoTrack.pesData = null;
12669    }
12670    if (_id3Track) {
12671      _id3Track.pesData = null;
12672    }
12673    this.aacOverFlow = null;
12674    this.remainderData = null;
12675  }
12676  demux(data, timeOffset, isSampleAes = false, flush = false) {
12677    if (!isSampleAes) {
12678      this.sampleAes = null;
12679    }
12680    let pes;
12681    const videoTrack = this._videoTrack;
12682    const audioTrack = this._audioTrack;
12683    const id3Track = this._id3Track;
12684    const textTrack = this._txtTrack;
12685    let videoPid = videoTrack.pid;
12686    let videoData = videoTrack.pesData;
12687    let audioPid = audioTrack.pid;
12688    let id3Pid = id3Track.pid;
12689    let audioData = audioTrack.pesData;
12690    let id3Data = id3Track.pesData;
12691    let unknownPID = null;
12692    let pmtParsed = this.pmtParsed;
12693    let pmtId = this._pmtId;
12694    let len = data.length;
12695    if (this.remainderData) {
12696      data = appendUint8Array(this.remainderData, data);
12697      len = data.length;
12698      this.remainderData = null;
12699    }
12700    if (len < PACKET_LENGTH && !flush) {
12701      this.remainderData = data;
12702      return {
12703        audioTrack,
12704        videoTrack,
12705        id3Track,
12706        textTrack
12707      };
12708    }
12709    const syncOffset = Math.max(0, TSDemuxer.syncOffset(data));
12710    len -= (len - syncOffset) % PACKET_LENGTH;
12711    if (len < data.byteLength && !flush) {
12712      this.remainderData = new Uint8Array(data.buffer, len, data.buffer.byteLength - len);
12713    }
12714
12715    // loop through TS packets
12716    let tsPacketErrors = 0;
12717    for (let start = syncOffset; start < len; start += PACKET_LENGTH) {
12718      if (data[start] === 0x47) {
12719        const stt = !!(data[start + 1] & 0x40);
12720        const pid = parsePID(data, start);
12721        const atf = (data[start + 3] & 0x30) >> 4;
12722
12723        // if an adaption field is present, its length is specified by the fifth byte of the TS packet header.
12724        let offset;
12725        if (atf > 1) {
12726          offset = start + 5 + data[start + 4];
12727          // continue if there is only adaptation field
12728          if (offset === start + PACKET_LENGTH) {
12729            continue;
12730          }
12731        } else {
12732          offset = start + 4;
12733        }
12734        switch (pid) {
12735          case videoPid:
12736            if (stt) {
12737              if (videoData && (pes = parsePES(videoData))) {
12738                this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
12739              }
12740              videoData = {
12741                data: [],
12742                size: 0
12743              };
12744            }
12745            if (videoData) {
12746              videoData.data.push(data.subarray(offset, start + PACKET_LENGTH));
12747              videoData.size += start + PACKET_LENGTH - offset;
12748            }
12749            break;
12750          case audioPid:
12751            if (stt) {
12752              if (audioData && (pes = parsePES(audioData))) {
12753                switch (audioTrack.segmentCodec) {
12754                  case 'aac':
12755                    this.parseAACPES(audioTrack, pes);
12756                    break;
12757                  case 'mp3':
12758                    this.parseMPEGPES(audioTrack, pes);
12759                    break;
12760                  case 'ac3':
12761                    {
12762                      this.parseAC3PES(audioTrack, pes);
12763                    }
12764                    break;
12765                }
12766              }
12767              audioData = {
12768                data: [],
12769                size: 0
12770              };
12771            }
12772            if (audioData) {
12773              audioData.data.push(data.subarray(offset, start + PACKET_LENGTH));
12774              audioData.size += start + PACKET_LENGTH - offset;
12775            }
12776            break;
12777          case id3Pid:
12778            if (stt) {
12779              if (id3Data && (pes = parsePES(id3Data))) {
12780                this.parseID3PES(id3Track, pes);
12781              }
12782              id3Data = {
12783                data: [],
12784                size: 0
12785              };
12786            }
12787            if (id3Data) {
12788              id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH));
12789              id3Data.size += start + PACKET_LENGTH - offset;
12790            }
12791            break;
12792          case 0:
12793            if (stt) {
12794              offset += data[offset] + 1;
12795            }
12796            pmtId = this._pmtId = parsePAT(data, offset);
12797            // logger.log('PMT PID:'  + this._pmtId);
12798            break;
12799          case pmtId:
12800            {
12801              if (stt) {
12802                offset += data[offset] + 1;
12803              }
12804              const parsedPIDs = parsePMT(data, offset, this.typeSupported, isSampleAes, this.observer);
12805
12806              // only update track id if track PID found while parsing PMT
12807              // this is to avoid resetting the PID to -1 in case
12808              // track PID transiently disappears from the stream
12809              // this could happen in case of transient missing audio samples for example
12810              // NOTE this is only the PID of the track as found in TS,
12811              // but we are not using this for MP4 track IDs.
12812              videoPid = parsedPIDs.videoPid;
12813              if (videoPid > 0) {
12814                videoTrack.pid = videoPid;
12815                videoTrack.segmentCodec = parsedPIDs.segmentVideoCodec;
12816              }
12817              audioPid = parsedPIDs.audioPid;
12818              if (audioPid > 0) {
12819                audioTrack.pid = audioPid;
12820                audioTrack.segmentCodec = parsedPIDs.segmentAudioCodec;
12821              }
12822              id3Pid = parsedPIDs.id3Pid;
12823              if (id3Pid > 0) {
12824                id3Track.pid = id3Pid;
12825              }
12826              if (unknownPID !== null && !pmtParsed) {
12827                logger.warn(`MPEG-TS PMT found at ${start} after unknown PID '${unknownPID}'. Backtracking to sync byte @${syncOffset} to parse all TS packets.`);
12828                unknownPID = null;
12829                // we set it to -188, the += 188 in the for loop will reset start to 0
12830                start = syncOffset - 188;
12831              }
12832              pmtParsed = this.pmtParsed = true;
12833              break;
12834            }
12835          case 0x11:
12836          case 0x1fff:
12837            break;
12838          default:
12839            unknownPID = pid;
12840            break;
12841        }
12842      } else {
12843        tsPacketErrors++;
12844      }
12845    }
12846    if (tsPacketErrors > 0) {
12847      emitParsingError(this.observer, new Error(`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`));
12848    }
12849    videoTrack.pesData = videoData;
12850    audioTrack.pesData = audioData;
12851    id3Track.pesData = id3Data;
12852    const demuxResult = {
12853      audioTrack,
12854      videoTrack,
12855      id3Track,
12856      textTrack
12857    };
12858    if (flush) {
12859      this.extractRemainingSamples(demuxResult);
12860    }
12861    return demuxResult;
12862  }
12863  flush() {
12864    const {
12865      remainderData
12866    } = this;
12867    this.remainderData = null;
12868    let result;
12869    if (remainderData) {
12870      result = this.demux(remainderData, -1, false, true);
12871    } else {
12872      result = {
12873        videoTrack: this._videoTrack,
12874        audioTrack: this._audioTrack,
12875        id3Track: this._id3Track,
12876        textTrack: this._txtTrack
12877      };
12878    }
12879    this.extractRemainingSamples(result);
12880    if (this.sampleAes) {
12881      return this.decrypt(result, this.sampleAes);
12882    }
12883    return result;
12884  }
12885  extractRemainingSamples(demuxResult) {
12886    const {
12887      audioTrack,
12888      videoTrack,
12889      id3Track,
12890      textTrack
12891    } = demuxResult;
12892    const videoData = videoTrack.pesData;
12893    const audioData = audioTrack.pesData;
12894    const id3Data = id3Track.pesData;
12895    // try to parse last PES packets
12896    let pes;
12897    if (videoData && (pes = parsePES(videoData))) {
vendor: 6,356 bytes, lines 12898-13098
12898      this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
12899      videoTrack.pesData = null;
12900    } else {
12901      // either avcData null or PES truncated, keep it for next frag parsing
12902      videoTrack.pesData = videoData;
12903    }
12904    if (audioData && (pes = parsePES(audioData))) {
12905      switch (audioTrack.segmentCodec) {
12906        case 'aac':
12907          this.parseAACPES(audioTrack, pes);
12908          break;
12909        case 'mp3':
12910          this.parseMPEGPES(audioTrack, pes);
12911          break;
12912        case 'ac3':
12913          {
12914            this.parseAC3PES(audioTrack, pes);
12915          }
12916          break;
12917      }
12918      audioTrack.pesData = null;
12919    } else {
12920      if (audioData != null && audioData.size) {
12921        logger.log('last AAC PES packet truncated,might overlap between fragments');
12922      }
12923
12924      // either audioData null or PES truncated, keep it for next frag parsing
12925      audioTrack.pesData = audioData;
12926    }
12927    if (id3Data && (pes = parsePES(id3Data))) {
12928      this.parseID3PES(id3Track, pes);
12929      id3Track.pesData = null;
12930    } else {
12931      // either id3Data null or PES truncated, keep it for next frag parsing
12932      id3Track.pesData = id3Data;
12933    }
12934  }
12935  demuxSampleAes(data, keyData, timeOffset) {
12936    const demuxResult = this.demux(data, timeOffset, true, !this.config.progressive);
12937    const sampleAes = this.sampleAes = new SampleAesDecrypter(this.observer, this.config, keyData);
12938    return this.decrypt(demuxResult, sampleAes);
12939  }
12940  decrypt(demuxResult, sampleAes) {
12941    return new Promise(resolve => {
12942      const {
12943        audioTrack,
12944        videoTrack
12945      } = demuxResult;
12946      if (audioTrack.samples && audioTrack.segmentCodec === 'aac') {
12947        sampleAes.decryptAacSamples(audioTrack.samples, 0, () => {
12948          if (videoTrack.samples) {
12949            sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
12950              resolve(demuxResult);
12951            });
12952          } else {
12953            resolve(demuxResult);
12954          }
12955        });
12956      } else if (videoTrack.samples) {
12957        sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
12958          resolve(demuxResult);
12959        });
12960      }
12961    });
12962  }
12963  destroy() {
12964    this._duration = 0;
12965  }
12966  parseAACPES(track, pes) {
12967    let startOffset = 0;
12968    const aacOverFlow = this.aacOverFlow;
12969    let data = pes.data;
12970    if (aacOverFlow) {
12971      this.aacOverFlow = null;
12972      const frameMissingBytes = aacOverFlow.missing;
12973      const sampleLength = aacOverFlow.sample.unit.byteLength;
12974      // logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`);
12975      if (frameMissingBytes === -1) {
12976        data = appendUint8Array(aacOverFlow.sample.unit, data);
12977      } else {
12978        const frameOverflowBytes = sampleLength - frameMissingBytes;
12979        aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes);
12980        track.samples.push(aacOverFlow.sample);
12981        startOffset = aacOverFlow.missing;
12982      }
12983    }
12984    // look for ADTS header (0xFFFx)
12985    let offset;
12986    let len;
12987    for (offset = startOffset, len = data.length; offset < len - 1; offset++) {
12988      if (isHeader$1(data, offset)) {
12989        break;
12990      }
12991    }
12992    // if ADTS header does not start straight from the beginning of the PES payload, raise an error
12993    if (offset !== startOffset) {
12994      let reason;
12995      const recoverable = offset < len - 1;
12996      if (recoverable) {
12997        reason = `AAC PES did not start with ADTS header,offset:${offset}`;
12998      } else {
12999        reason = 'No ADTS header found in AAC PES';
13000      }
13001      emitParsingError(this.observer, new Error(reason), recoverable);
13002      if (!recoverable) {
13003        return;
13004      }
13005    }
13006    initTrackConfig(track, this.observer, data, offset, this.audioCodec);
13007    let pts;
13008    if (pes.pts !== undefined) {
13009      pts = pes.pts;
13010    } else if (aacOverFlow) {
13011      // if last AAC frame is overflowing, we should ensure timestamps are contiguous:
13012      // first sample PTS should be equal to last sample PTS + frameDuration
13013      const frameDuration = getFrameDuration(track.samplerate);
13014      pts = aacOverFlow.sample.pts + frameDuration;
13015    } else {
13016      logger.warn('[tsdemuxer]: AAC PES unknown PTS');
13017      return;
13018    }
13019
13020    // scan for aac samples
13021    let frameIndex = 0;
13022    let frame;
13023    while (offset < len) {
13024      frame = appendFrame$2(track, data, offset, pts, frameIndex);
13025      offset += frame.length;
13026      if (!frame.missing) {
13027        frameIndex++;
13028        for (; offset < len - 1; offset++) {
13029          if (isHeader$1(data, offset)) {
13030            break;
13031          }
13032        }
13033      } else {
13034        this.aacOverFlow = frame;
13035        break;
13036      }
13037    }
13038  }
13039  parseMPEGPES(track, pes) {
13040    const data = pes.data;
13041    const length = data.length;
13042    let frameIndex = 0;
13043    let offset = 0;
13044    const pts = pes.pts;
13045    if (pts === undefined) {
13046      logger.warn('[tsdemuxer]: MPEG PES unknown PTS');
13047      return;
13048    }
13049    while (offset < length) {
13050      if (isHeader(data, offset)) {
13051        const frame = appendFrame$1(track, data, offset, pts, frameIndex);
13052        if (frame) {
13053          offset += frame.length;
13054          frameIndex++;
13055        } else {
13056          // logger.log('Unable to parse Mpeg audio frame');
13057          break;
13058        }
13059      } else {
13060        // nothing found, keep looking
13061        offset++;
13062      }
13063    }
13064  }
13065  parseAC3PES(track, pes) {
13066    {
13067      const data = pes.data;
13068      const pts = pes.pts;
13069      if (pts === undefined) {
13070        logger.warn('[tsdemuxer]: AC3 PES unknown PTS');
13071        return;
13072      }
13073      const length = data.length;
13074      let frameIndex = 0;
13075      let offset = 0;
13076      let parsed;
13077      while (offset < length && (parsed = appendFrame(track, data, offset, pts, frameIndex++)) > 0) {
13078        offset += parsed;
13079      }
13080    }
13081  }
13082  parseID3PES(id3Track, pes) {
13083    if (pes.pts === undefined) {
13084      logger.warn('[tsdemuxer]: ID3 PES unknown PTS');
13085      return;
13086    }
13087    const id3Sample = _extends({}, pes, {
13088      type: this._videoTrack ? MetadataSchema.emsg : MetadataSchema.audioId3,
13089      duration: Number.POSITIVE_INFINITY
13090    });
13091    id3Track.samples.push(id3Sample);
13092  }
13093}
13094function parsePID(data, offset) {
13095  // pid is a 13-bit field starting at the last bit of TS[1]
13096  return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2];
13097}
13098function parsePAT(data, offset) {
vendor: 5,884 bytes, lines 13099-13272
13099  // skip the PSI header and parse the first PMT entry
13100  return (data[offset + 10] & 0x1f) << 8 | data[offset + 11];
13101}
13102function parsePMT(data, offset, typeSupported, isSampleAes, observer) {
13103  const result = {
13104    audioPid: -1,
13105    videoPid: -1,
13106    id3Pid: -1,
13107    segmentVideoCodec: 'avc',
13108    segmentAudioCodec: 'aac'
13109  };
13110  const sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2];
13111  const tableEnd = offset + 3 + sectionLength - 4;
13112  // to determine where the table is, we have to figure out how
13113  // long the program info descriptors are
13114  const programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11];
13115  // advance the offset to the first entry in the mapping table
13116  offset += 12 + programInfoLength;
13117  while (offset < tableEnd) {
13118    const pid = parsePID(data, offset);
13119    const esInfoLength = (data[offset + 3] & 0x0f) << 8 | data[offset + 4];
13120    switch (data[offset]) {
13121      case 0xcf:
13122        // SAMPLE-AES AAC
13123        if (!isSampleAes) {
13124          logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC');
13125          break;
13126        }
13127      /* falls through */
13128      case 0x0f:
13129        // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
13130        // logger.log('AAC PID:'  + pid);
13131        if (result.audioPid === -1) {
13132          result.audioPid = pid;
13133        }
13134        break;
13135
13136      // Packetized metadata (ID3)
13137      case 0x15:
13138        // logger.log('ID3 PID:'  + pid);
13139        if (result.id3Pid === -1) {
13140          result.id3Pid = pid;
13141        }
13142        break;
13143      case 0xdb:
13144        // SAMPLE-AES AVC
13145        if (!isSampleAes) {
13146          logEncryptedSamplesFoundInUnencryptedStream('H.264');
13147          break;
13148        }
13149      /* falls through */
13150      case 0x1b:
13151        // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
13152        // logger.log('AVC PID:'  + pid);
13153        if (result.videoPid === -1) {
13154          result.videoPid = pid;
13155          result.segmentVideoCodec = 'avc';
13156        }
13157        break;
13158
13159      // ISO/IEC 11172-3 (MPEG-1 audio)
13160      // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
13161      case 0x03:
13162      case 0x04:
13163        // logger.log('MPEG PID:'  + pid);
13164        if (!typeSupported.mpeg && !typeSupported.mp3) {
13165          logger.log('MPEG audio found, not supported in this browser');
13166        } else if (result.audioPid === -1) {
13167          result.audioPid = pid;
13168          result.segmentAudioCodec = 'mp3';
13169        }
13170        break;
13171      case 0xc1:
13172        // SAMPLE-AES AC3
13173        if (!isSampleAes) {
13174          logEncryptedSamplesFoundInUnencryptedStream('AC-3');
13175          break;
13176        }
13177      /* falls through */
13178      case 0x81:
13179        {
13180          if (!typeSupported.ac3) {
13181            logger.log('AC-3 audio found, not supported in this browser');
13182          } else if (result.audioPid === -1) {
13183            result.audioPid = pid;
13184            result.segmentAudioCodec = 'ac3';
13185          }
13186        }
13187        break;
13188      case 0x06:
13189        // stream_type 6 can mean a lot of different things in case of DVB.
13190        // We need to look at the descriptors. Right now, we're only interested
13191        // in AC-3 audio, so we do the descriptor parsing only when we don't have
13192        // an audio PID yet.
13193        if (result.audioPid === -1 && esInfoLength > 0) {
13194          let parsePos = offset + 5;
13195          let remaining = esInfoLength;
13196          while (remaining > 2) {
13197            const descriptorId = data[parsePos];
13198            switch (descriptorId) {
13199              case 0x6a:
13200                // DVB Descriptor for AC-3
13201                {
13202                  if (typeSupported.ac3 !== true) {
13203                    logger.log('AC-3 audio found, not supported in this browser for now');
13204                  } else {
13205                    result.audioPid = pid;
13206                    result.segmentAudioCodec = 'ac3';
13207                  }
13208                }
13209                break;
13210            }
13211            const descriptorLen = data[parsePos + 1] + 2;
13212            parsePos += descriptorLen;
13213            remaining -= descriptorLen;
13214          }
13215        }
13216        break;
13217      case 0xc2: // SAMPLE-AES EC3
13218      /* falls through */
13219      case 0x87:
13220        emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found'));
13221        return result;
13222      case 0x24:
13223        emitParsingError(observer, new Error('Unsupported HEVC in M2TS found'));
13224        return result;
13225    }
13226    // move to the next table entry
13227    // skip past the elementary stream descriptors, if present
13228    offset += esInfoLength + 5;
13229  }
13230  return result;
13231}
13232function emitParsingError(observer, error, levelRetry) {
13233  logger.warn(`parsing error: ${error.message}`);
13234  observer.emit(Events.ERROR, Events.ERROR, {
13235    type: ErrorTypes.MEDIA_ERROR,
13236    details: ErrorDetails.FRAG_PARSING_ERROR,
13237    fatal: false,
13238    levelRetry,
13239    error,
13240    reason: error.message
13241  });
13242}
13243function logEncryptedSamplesFoundInUnencryptedStream(type) {
13244  logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`);
13245}
13246function parsePES(stream) {
13247  let i = 0;
13248  let frag;
13249  let pesLen;
13250  let pesHdrLen;
13251  let pesPts;
13252  let pesDts;
13253  const data = stream.data;
13254  // safety check
13255  if (!stream || stream.size === 0) {
13256    return null;
13257  }
13258
13259  // we might need up to 19 bytes to read PES header
13260  // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes
13261  // usually only one merge is needed (and this is rare ...)
13262  while (data[0].length < 19 && data.length > 1) {
13263    data[0] = appendUint8Array(data[0], data[1]);
13264    data.splice(1, 1);
13265  }
13266  // retrieve PTS/DTS from first fragment
13267  frag = data[0];
13268  const pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
13269  if (pesPrefix === 1) {
13270    pesLen = (frag[4] << 8) + frag[5];
13271    // if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated
13272    // minus 6 : PES header size
vendor: 16,135 bytes, lines 13273-13743
13273    if (pesLen && pesLen > stream.size - 6) {
13274      return null;
13275    }
13276    const pesFlags = frag[7];
13277    if (pesFlags & 0xc0) {
13278      /* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
13279          as PTS / DTS is 33 bit we cannot use bitwise operator in JS,
13280          as Bitwise operators treat their operands as a sequence of 32 bits */
13281      pesPts = (frag[9] & 0x0e) * 536870912 +
13282      // 1 << 29
13283      (frag[10] & 0xff) * 4194304 +
13284      // 1 << 22
13285      (frag[11] & 0xfe) * 16384 +
13286      // 1 << 14
13287      (frag[12] & 0xff) * 128 +
13288      // 1 << 7
13289      (frag[13] & 0xfe) / 2;
13290      if (pesFlags & 0x40) {
13291        pesDts = (frag[14] & 0x0e) * 536870912 +
13292        // 1 << 29
13293        (frag[15] & 0xff) * 4194304 +
13294        // 1 << 22
13295        (frag[16] & 0xfe) * 16384 +
13296        // 1 << 14
13297        (frag[17] & 0xff) * 128 +
13298        // 1 << 7
13299        (frag[18] & 0xfe) / 2;
13300        if (pesPts - pesDts > 60 * 90000) {
13301          logger.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`);
13302          pesPts = pesDts;
13303        }
13304      } else {
13305        pesDts = pesPts;
13306      }
13307    }
13308    pesHdrLen = frag[8];
13309    // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
13310    let payloadStartOffset = pesHdrLen + 9;
13311    if (stream.size <= payloadStartOffset) {
13312      return null;
13313    }
13314    stream.size -= payloadStartOffset;
13315    // reassemble PES packet
13316    const pesData = new Uint8Array(stream.size);
13317    for (let j = 0, dataLen = data.length; j < dataLen; j++) {
13318      frag = data[j];
13319      let len = frag.byteLength;
13320      if (payloadStartOffset) {
13321        if (payloadStartOffset > len) {
13322          // trim full frag if PES header bigger than frag
13323          payloadStartOffset -= len;
13324          continue;
13325        } else {
13326          // trim partial frag if PES header smaller than frag
13327          frag = frag.subarray(payloadStartOffset);
13328          len -= payloadStartOffset;
13329          payloadStartOffset = 0;
13330        }
13331      }
13332      pesData.set(frag, i);
13333      i += len;
13334    }
13335    if (pesLen) {
13336      // payload size : remove PES header + PES extension
13337      pesLen -= pesHdrLen + 3;
13338    }
13339    return {
13340      data: pesData,
13341      pts: pesPts,
13342      dts: pesDts,
13343      len: pesLen
13344    };
13345  }
13346  return null;
13347}
13348
13349/**
13350 * MP3 demuxer
13351 */
13352class MP3Demuxer extends BaseAudioDemuxer {
13353  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
13354    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
13355    this._audioTrack = {
13356      container: 'audio/mpeg',
13357      type: 'audio',
13358      id: 2,
13359      pid: -1,
13360      sequenceNumber: 0,
13361      segmentCodec: 'mp3',
13362      samples: [],
13363      manifestCodec: audioCodec,
13364      duration: trackDuration,
13365      inputTimeScale: 90000,
13366      dropped: 0
13367    };
13368  }
13369  static probe(data) {
13370    if (!data) {
13371      return false;
13372    }
13373
13374    // check if data contains ID3 timestamp and MPEG sync word
13375    // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
13376    // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
13377    // More info http://www.mp3-tech.org/programmer/frame_header.html
13378    const id3Data = getID3Data(data, 0);
13379    let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
13380
13381    // Check for ac-3|ec-3 sync bytes and return false if present
13382    if (id3Data && data[offset] === 0x0b && data[offset + 1] === 0x77 && getTimeStamp(id3Data) !== undefined &&
13383    // check the bsid to confirm ac-3 or ec-3 (not mp3)
13384    getAudioBSID(data, offset) <= 16) {
13385      return false;
13386    }
13387    for (let length = data.length; offset < length; offset++) {
13388      if (probe(data, offset)) {
13389        logger.log('MPEG Audio sync word found !');
13390        return true;
13391      }
13392    }
13393    return false;
13394  }
13395  canParse(data, offset) {
13396    return canParse(data, offset);
13397  }
13398  appendFrame(track, data, offset) {
13399    if (this.basePTS === null) {
13400      return;
13401    }
13402    return appendFrame$1(track, data, offset, this.basePTS, this.frameIndex);
13403  }
13404}
13405
13406/**
13407 *  AAC helper
13408 */
13409
13410class AAC {
13411  static getSilentFrame(codec, channelCount) {
13412    switch (codec) {
13413      case 'mp4a.40.2':
13414        if (channelCount === 1) {
13415          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]);
13416        } else if (channelCount === 2) {
13417          return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]);
13418        } else if (channelCount === 3) {
13419          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]);
13420        } else if (channelCount === 4) {
13421          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]);
13422        } else if (channelCount === 5) {
13423          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]);
13424        } else if (channelCount === 6) {
13425          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]);
13426        }
13427        break;
13428      // handle HE-AAC below (mp4a.40.5 / mp4a.40.29)
13429      default:
13430        if (channelCount === 1) {
13431          // 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
13432          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]);
13433        } else if (channelCount === 2) {
13434          // 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
13435          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]);
13436        } else if (channelCount === 3) {
13437          // 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
13438          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]);
13439        }
13440        break;
13441    }
13442    return undefined;
13443  }
13444}
13445
13446/**
13447 * Generate MP4 Box
13448 */
13449
13450const UINT32_MAX = Math.pow(2, 32) - 1;
13451class MP4 {
13452  static init() {
13453    MP4.types = {
13454      avc1: [],
13455      // codingname
13456      avcC: [],
13457      btrt: [],
13458      dinf: [],
13459      dref: [],
13460      esds: [],
13461      ftyp: [],
13462      hdlr: [],
13463      mdat: [],
13464      mdhd: [],
13465      mdia: [],
13466      mfhd: [],
13467      minf: [],
13468      moof: [],
13469      moov: [],
13470      mp4a: [],
13471      '.mp3': [],
13472      dac3: [],
13473      'ac-3': [],
13474      mvex: [],
13475      mvhd: [],
13476      pasp: [],
13477      sdtp: [],
13478      stbl: [],
13479      stco: [],
13480      stsc: [],
13481      stsd: [],
13482      stsz: [],
13483      stts: [],
13484      tfdt: [],
13485      tfhd: [],
13486      traf: [],
13487      trak: [],
13488      trun: [],
13489      trex: [],
13490      tkhd: [],
13491      vmhd: [],
13492      smhd: []
13493    };
13494    let i;
13495    for (i in MP4.types) {
13496      if (MP4.types.hasOwnProperty(i)) {
13497        MP4.types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
13498      }
13499    }
13500    const videoHdlr = new Uint8Array([0x00,
13501    // version 0
13502    0x00, 0x00, 0x00,
13503    // flags
13504    0x00, 0x00, 0x00, 0x00,
13505    // pre_defined
13506    0x76, 0x69, 0x64, 0x65,
13507    // handler_type: 'vide'
13508    0x00, 0x00, 0x00, 0x00,
13509    // reserved
13510    0x00, 0x00, 0x00, 0x00,
13511    // reserved
13512    0x00, 0x00, 0x00, 0x00,
13513    // reserved
13514    0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
13515    ]);
13516    const audioHdlr = new Uint8Array([0x00,
13517    // version 0
13518    0x00, 0x00, 0x00,
13519    // flags
13520    0x00, 0x00, 0x00, 0x00,
13521    // pre_defined
13522    0x73, 0x6f, 0x75, 0x6e,
13523    // handler_type: 'soun'
13524    0x00, 0x00, 0x00, 0x00,
13525    // reserved
13526    0x00, 0x00, 0x00, 0x00,
13527    // reserved
13528    0x00, 0x00, 0x00, 0x00,
13529    // reserved
13530    0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
13531    ]);
13532    MP4.HDLR_TYPES = {
13533      video: videoHdlr,
13534      audio: audioHdlr
13535    };
13536    const dref = new Uint8Array([0x00,
13537    // version 0
13538    0x00, 0x00, 0x00,
13539    // flags
13540    0x00, 0x00, 0x00, 0x01,
13541    // entry_count
13542    0x00, 0x00, 0x00, 0x0c,
13543    // entry_size
13544    0x75, 0x72, 0x6c, 0x20,
13545    // 'url' type
13546    0x00,
13547    // version 0
13548    0x00, 0x00, 0x01 // entry_flags
13549    ]);
13550    const stco = new Uint8Array([0x00,
13551    // version
13552    0x00, 0x00, 0x00,
13553    // flags
13554    0x00, 0x00, 0x00, 0x00 // entry_count
13555    ]);
13556    MP4.STTS = MP4.STSC = MP4.STCO = stco;
13557    MP4.STSZ = new Uint8Array([0x00,
13558    // version
13559    0x00, 0x00, 0x00,
13560    // flags
13561    0x00, 0x00, 0x00, 0x00,
13562    // sample_size
13563    0x00, 0x00, 0x00, 0x00 // sample_count
13564    ]);
13565    MP4.VMHD = new Uint8Array([0x00,
13566    // version
13567    0x00, 0x00, 0x01,
13568    // flags
13569    0x00, 0x00,
13570    // graphicsmode
13571    0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
13572    ]);
13573    MP4.SMHD = new Uint8Array([0x00,
13574    // version
13575    0x00, 0x00, 0x00,
13576    // flags
13577    0x00, 0x00,
13578    // balance
13579    0x00, 0x00 // reserved
13580    ]);
13581    MP4.STSD = new Uint8Array([0x00,
13582    // version 0
13583    0x00, 0x00, 0x00,
13584    // flags
13585    0x00, 0x00, 0x00, 0x01]); // entry_count
13586
13587    const majorBrand = new Uint8Array([105, 115, 111, 109]); // isom
13588    const avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1
13589    const minorVersion = new Uint8Array([0, 0, 0, 1]);
13590    MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand);
13591    MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));
13592  }
13593  static box(type, ...payload) {
13594    let size = 8;
13595    let i = payload.length;
13596    const len = i;
13597    // calculate the total size we need to allocate
13598    while (i--) {
13599      size += payload[i].byteLength;
13600    }
13601    const result = new Uint8Array(size);
13602    result[0] = size >> 24 & 0xff;
13603    result[1] = size >> 16 & 0xff;
13604    result[2] = size >> 8 & 0xff;
13605    result[3] = size & 0xff;
13606    result.set(type, 4);
13607    // copy the payload into the result
13608    for (i = 0, size = 8; i < len; i++) {
13609      // copy payload[i] array @ offset size
13610      result.set(payload[i], size);
13611      size += payload[i].byteLength;
13612    }
13613    return result;
13614  }
13615  static hdlr(type) {
13616    return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]);
13617  }
13618  static mdat(data) {
13619    return MP4.box(MP4.types.mdat, data);
13620  }
13621  static mdhd(timescale, duration) {
13622    duration *= timescale;
13623    const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
13624    const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
13625    return MP4.box(MP4.types.mdhd, new Uint8Array([0x01,
13626    // version 1
13627    0x00, 0x00, 0x00,
13628    // flags
13629    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
13630    // creation_time
13631    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
13632    // modification_time
13633    timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,
13634    // timescale
13635    upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x55, 0xc4,
13636    // 'und' language (undetermined)
13637    0x00, 0x00]));
13638  }
13639  static mdia(track) {
13640    return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track));
13641  }
13642  static mfhd(sequenceNumber) {
13643    return MP4.box(MP4.types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,
13644    // flags
13645    sequenceNumber >> 24, sequenceNumber >> 16 & 0xff, sequenceNumber >> 8 & 0xff, sequenceNumber & 0xff // sequence_number
13646    ]));
13647  }
13648  static minf(track) {
13649    if (track.type === 'audio') {
13650      return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track));
13651    } else {
13652      return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));
13653    }
13654  }
13655  static moof(sn, baseMediaDecodeTime, track) {
13656    return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime));
13657  }
13658  static moov(tracks) {
13659    let i = tracks.length;
13660    const boxes = [];
13661    while (i--) {
13662      boxes[i] = MP4.trak(tracks[i]);
13663    }
13664    return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)].concat(boxes).concat(MP4.mvex(tracks)));
13665  }
13666  static mvex(tracks) {
13667    let i = tracks.length;
13668    const boxes = [];
13669    while (i--) {
13670      boxes[i] = MP4.trex(tracks[i]);
13671    }
13672    return MP4.box.apply(null, [MP4.types.mvex, ...boxes]);
13673  }
13674  static mvhd(timescale, duration) {
13675    duration *= timescale;
13676    const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
13677    const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
13678    const bytes = new Uint8Array([0x01,
13679    // version 1
13680    0x00, 0x00, 0x00,
13681    // flags
13682    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
13683    // creation_time
13684    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
13685    // modification_time
13686    timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,
13687    // timescale
13688    upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x01, 0x00, 0x00,
13689    // 1.0 rate
13690    0x01, 0x00,
13691    // 1.0 volume
13692    0x00, 0x00,
13693    // reserved
13694    0x00, 0x00, 0x00, 0x00,
13695    // reserved
13696    0x00, 0x00, 0x00, 0x00,
13697    // reserved
13698    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,
13699    // transformation: unity matrix
13700    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13701    // pre_defined
13702    0xff, 0xff, 0xff, 0xff // next_track_ID
13703    ]);
13704    return MP4.box(MP4.types.mvhd, bytes);
13705  }
13706  static sdtp(track) {
13707    const samples = track.samples || [];
13708    const bytes = new Uint8Array(4 + samples.length);
13709    let i;
13710    let flags;
13711    // leave the full box header (4 bytes) all zero
13712    // write the sample table
13713    for (i = 0; i < samples.length; i++) {
13714      flags = samples[i].flags;
13715      bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
13716    }
13717    return MP4.box(MP4.types.sdtp, bytes);
13718  }
13719  static stbl(track) {
13720    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));
13721  }
13722  static avc1(track) {
13723    let sps = [];
13724    let pps = [];
13725    let i;
13726    let data;
13727    let len;
13728    // assemble the SPSs
13729
13730    for (i = 0; i < track.sps.length; i++) {
13731      data = track.sps[i];
13732      len = data.byteLength;
13733      sps.push(len >>> 8 & 0xff);
13734      sps.push(len & 0xff);
13735
13736      // SPS
13737      sps = sps.concat(Array.prototype.slice.call(data));
13738    }
13739
13740    // assemble the PPSs
13741    for (i = 0; i < track.pps.length; i++) {
13742      data = track.pps[i];
13743      len = data.byteLength;
13744      pps.push(len >>> 8 & 0xff);
13745      pps.push(len & 0xff);
13746      pps = pps.concat(Array.prototype.slice.call(data));
13747    }
13748    const avcc = MP4.box(MP4.types.avcC, new Uint8Array([0x01,
13749    // version
13750    sps[3],
13751    // profile
13752    sps[4],
13753    // profile compat
13754    sps[5],
13755    // level
13756    0xfc | 3,
13757    // lengthSizeMinusOne, hard-coded to 4 bytes
13758    0xe0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets
13759    ].concat(sps).concat([track.pps.length // numOfPictureParameterSets
13760    ]).concat(pps))); // "PPS"
13761    const width = track.width;
13762    const height = track.height;
13763    const hSpacing = track.pixelRatio[0];
13764    const vSpacing = track.pixelRatio[1];
13765    return MP4.box(MP4.types.avc1, new Uint8Array([0x00, 0x00, 0x00,
13766    // reserved
13767    0x00, 0x00, 0x00,
13768    // reserved
13769    0x00, 0x01,
13770    // data_reference_index
13771    0x00, 0x00,
13772    // pre_defined
13773    0x00, 0x00,
13774    // reserved
13775    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13776    // pre_defined
13777    width >> 8 & 0xff, width & 0xff,
13778    // width
13779    height >> 8 & 0xff, height & 0xff,
13780    // height
13781    0x00, 0x48, 0x00, 0x00,
13782    // horizresolution
13783    0x00, 0x48, 0x00, 0x00,
13784    // vertresolution
13785    0x00, 0x00, 0x00, 0x00,
13786    // reserved
13787    0x00, 0x01,
13788    // frame_count
13789    0x12, 0x64, 0x61, 0x69, 0x6c,
13790    // dailymotion/hls.js
13791    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,
13792    // compressorname
13793    0x00, 0x18,
13794    // depth = 24
13795    0x11, 0x11]),
13796    // pre_defined = -1
13797    avcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
13798    // bufferSizeDB
13799    0x00, 0x2d, 0xc6, 0xc0,
13800    // maxBitrate
13801    0x00, 0x2d, 0xc6, 0xc0])),
13802    // avgBitrate
13803    MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
13804    // hSpacing
13805    hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
13806    // vSpacing
13807    vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
13808  }
13809  static esds(track) {
13810    const configlen = track.config.length;
13811    return new Uint8Array([0x00,
13812    // version 0
13813    0x00, 0x00, 0x00,
13814    // flags
13815
13816    0x03,
13817    // descriptor_type
13818    0x17 + configlen,
13819    // length
13820    0x00, 0x01,
13821    // es_id
13822    0x00,
13823    // stream_priority
13824
13825    0x04,
13826    // descriptor_type
13827    0x0f + configlen,
13828    // length
13829    0x40,
13830    // codec : mpeg4_audio
13831    0x15,
13832    // stream_type
13833    0x00, 0x00, 0x00,
13834    // buffer_size
13835    0x00, 0x00, 0x00, 0x00,
13836    // maxBitrate
13837    0x00, 0x00, 0x00, 0x00,
13838    // avgBitrate
13839
13840    0x05 // descriptor_type
13841    ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor
13842  }
13843  static audioStsd(track) {
13844    const samplerate = track.samplerate;
13845    return new Uint8Array([0x00, 0x00, 0x00,
13846    // reserved
13847    0x00, 0x00, 0x00,
13848    // reserved
13849    0x00, 0x01,
13850    // data_reference_index
13851    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
13852    // reserved
13853    0x00, track.channelCount,
13854    // channelcount
13855    0x00, 0x10,
13856    // sampleSize:16bits
13857    0x00, 0x00, 0x00, 0x00,
13858    // reserved2
13859    samplerate >> 8 & 0xff, samplerate & 0xff,
13860    //
13861    0x00, 0x00]);
13862  }
13863  static mp4a(track) {
13864    return MP4.box(MP4.types.mp4a, MP4.audioStsd(track), MP4.box(MP4.types.esds, MP4.esds(track)));
13865  }
13866  static mp3(track) {
13867    return MP4.box(MP4.types['.mp3'], MP4.audioStsd(track));
13868  }
13869  static ac3(track) {
13870    return MP4.box(MP4.types['ac-3'], MP4.audioStsd(track), MP4.box(MP4.types.dac3, track.config));
13871  }
13872  static stsd(track) {
13873    if (track.type === 'audio') {
13874      if (track.segmentCodec === 'mp3' && track.codec === 'mp3') {
13875        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track));
13876      }
13877      if (track.segmentCodec === 'ac3') {
13878        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
13879      }
13880      return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
13881    } else {
13882      return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
13883    }
13884  }
13885  static tkhd(track) {
13886    const id = track.id;
13887    const duration = track.duration * track.timescale;
13888    const width = track.width;
13889    const height = track.height;
13890    const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
13891    const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
13892    return MP4.box(MP4.types.tkhd, new Uint8Array([0x01,
13893    // version 1
13894    0x00, 0x00, 0x07,
13895    // flags
13896    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
13897    // creation_time
13898    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
13899    // modification_time
13900    id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,
13901    // track_ID
13902    0x00, 0x00, 0x00, 0x00,
13903    // reserved
13904    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,
13905    // reserved
13906    0x00, 0x00,
13907    // layer
13908    0x00, 0x00,
13909    // alternate_group
13910    0x00, 0x00,
13911    // non-audio track volume
13912    0x00, 0x00,
13913    // reserved
13914    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,
13915    // transformation: unity matrix
13916    width >> 8 & 0xff, width & 0xff, 0x00, 0x00,
13917    // width
13918    height >> 8 & 0xff, height & 0xff, 0x00, 0x00 // height
13919    ]));
13920  }
13921  static traf(track, baseMediaDecodeTime) {
13922    const sampleDependencyTable = MP4.sdtp(track);
13923    const id = track.id;
13924    const upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1));
13925    const lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));
13926    return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([0x00,
13927    // version 0
13928    0x00, 0x00, 0x00,
13929    // flags
13930    id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff // track_ID
13931    ])), MP4.box(MP4.types.tfdt, new Uint8Array([0x01,
13932    // version 1
13933    0x00, 0x00, 0x00,
13934    // flags
13935    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 +
13936    // tfhd
13937    20 +
13938    // tfdt
13939    8 +
13940    // traf header
13941    16 +
13942    // mfhd
13943    8 +
13944    // moof header
13945    8),
13946    // mdat header
13947    sampleDependencyTable);
13948  }
13949
13950  /**
13951   * Generate a track box.
13952   * @param track a track definition
13953   */
13954  static trak(track) {
13955    track.duration = track.duration || 0xffffffff;
13956    return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
13957  }
13958  static trex(track) {
13959    const id = track.id;
13960    return MP4.box(MP4.types.trex, new Uint8Array([0x00,
13961    // version 0
13962    0x00, 0x00, 0x00,
13963    // flags
13964    id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,
13965    // track_ID
13966    0x00, 0x00, 0x00, 0x01,
13967    // default_sample_description_index
13968    0x00, 0x00, 0x00, 0x00,
13969    // default_sample_duration
13970    0x00, 0x00, 0x00, 0x00,
13971    // default_sample_size
13972    0x00, 0x01, 0x00, 0x01 // default_sample_flags
13973    ]));
13974  }
13975  static trun(track, offset) {
13976    const samples = track.samples || [];
13977    const len = samples.length;
13978    const arraylen = 12 + 16 * len;
13979    const array = new Uint8Array(arraylen);
13980    let i;
13981    let sample;
13982    let duration;
13983    let size;
13984    let flags;
13985    let cts;
13986    offset += 8 + arraylen;
13987    array.set([track.type === 'video' ? 0x01 : 0x00,
13988    // version 1 for video with signed-int sample_composition_time_offset
13989    0x00, 0x0f, 0x01,
13990    // flags
13991    len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,
13992    // sample_count
13993    offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset
13994    ], 0);
13995    for (i = 0; i < len; i++) {
13996      sample = samples[i];
13997      duration = sample.duration;
13998      size = sample.size;
13999      flags = sample.flags;
14000      cts = sample.cts;
14001      array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,
14002      // sample_duration
14003      size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,
14004      // sample_size
14005      flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,
14006      // sample_flags
14007      cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset
14008      ], 12 + 16 * i);
14009    }
14010    return MP4.box(MP4.types.trun, array);
14011  }
14012  static initSegment(tracks) {
14013    if (!MP4.types) {
14014      MP4.init();
14015    }
14016    const movie = MP4.moov(tracks);
14017    const result = appendUint8Array(MP4.FTYP, movie);
14018    return result;
14019  }
14020}
14021MP4.types = void 0;
14022MP4.HDLR_TYPES = void 0;
14023MP4.STTS = void 0;
14024MP4.STSC = void 0;
14025MP4.STCO = void 0;
14026MP4.STSZ = void 0;
14027MP4.VMHD = void 0;
14028MP4.SMHD = void 0;
14029MP4.STSD = void 0;
14030MP4.FTYP = void 0;
14031MP4.DINF = void 0;
14032
14033const MPEG_TS_CLOCK_FREQ_HZ = 90000;
14034function toTimescaleFromBase(baseTime, destScale, srcBase = 1, round = false) {
14035  const result = baseTime * destScale * srcBase; // equivalent to `(value * scale) / (1 / base)`
14036  return round ? Math.round(result) : result;
14037}
14038function toTimescaleFromScale(baseTime, destScale, srcScale = 1, round = false) {
14039  return toTimescaleFromBase(baseTime, destScale, 1 / srcScale, round);
14040}
14041function toMsFromMpegTsClock(baseTime, round = false) {
14042  return toTimescaleFromBase(baseTime, 1000, 1 / MPEG_TS_CLOCK_FREQ_HZ, round);
14043}
14044function toMpegTsClockFromTimescale(baseTime, srcScale = 1) {
14045  return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale);
14046}
14047
14048const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds
14049const AAC_SAMPLES_PER_FRAME = 1024;
14050const MPEG_AUDIO_SAMPLE_PER_FRAME = 1152;
14051const AC3_SAMPLES_PER_FRAME = 1536;
14052let chromeVersion = null;
14053let safariWebkitVersion = null;
14054class MP4Remuxer {
14055  constructor(observer, config, typeSupported, vendor = '') {
14056    this.observer = void 0;
14057    this.config = void 0;
14058    this.typeSupported = void 0;
14059    this.ISGenerated = false;
14060    this._initPTS = null;
14061    this._initDTS = null;
14062    this.nextAvcDts = null;
14063    this.nextAudioPts = null;
14064    this.videoSampleDuration = null;
14065    this.isAudioContiguous = false;
14066    this.isVideoContiguous = false;
14067    this.videoTrackConfig = void 0;
14068    this.observer = observer;
14069    this.config = config;
14070    this.typeSupported = typeSupported;
14071    this.ISGenerated = false;
14072    if (chromeVersion === null) {
14073      const userAgent = navigator.userAgent || '';
14074      const result = userAgent.match(/Chrome\/(\d+)/i);
14075      chromeVersion = result ? parseInt(result[1]) : 0;
14076    }
14077    if (safariWebkitVersion === null) {
14078      const result = navigator.userAgent.match(/Safari\/(\d+)/i);
14079      safariWebkitVersion = result ? parseInt(result[1]) : 0;
14080    }
14081  }
14082  destroy() {
14083    // @ts-ignore
14084    this.config = this.videoTrackConfig = this._initPTS = this._initDTS = null;
14085  }
14086  resetTimeStamp(defaultTimeStamp) {
14087    logger.log('[mp4-remuxer]: initPTS & initDTS reset');
14088    this._initPTS = this._initDTS = defaultTimeStamp;
14089  }
14090  resetNextTimestamp() {
14091    logger.log('[mp4-remuxer]: reset next timestamp');
14092    this.isVideoContiguous = false;
14093    this.isAudioContiguous = false;
14094  }
14095  resetInitSegment() {
14096    logger.log('[mp4-remuxer]: ISGenerated flag reset');
14097    this.ISGenerated = false;
14098    this.videoTrackConfig = undefined;
14099  }
14100  getVideoStartPts(videoSamples) {
14101    let rolloverDetected = false;
14102    const startPTS = videoSamples.reduce((minPTS, sample) => {
14103      const delta = sample.pts - minPTS;
14104      if (delta < -4294967296) {
14105        // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation
14106        rolloverDetected = true;
14107        return normalizePts(minPTS, sample.pts);
14108      } else if (delta > 0) {
14109        return minPTS;
14110      } else {
14111        return sample.pts;
14112      }
14113    }, videoSamples[0].pts);
14114    if (rolloverDetected) {
14115      logger.debug('PTS rollover detected');
14116    }
14117    return startPTS;
14118  }
14119  remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType) {
14120    let video;
14121    let audio;
14122    let initSegment;
14123    let text;
14124    let id3;
14125    let independent;
14126    let audioTimeOffset = timeOffset;
14127    let videoTimeOffset = timeOffset;
14128
14129    // If we're remuxing audio and video progressively, wait until we've received enough samples for each track before proceeding.
14130    // This is done to synchronize the audio and video streams. We know if the current segment will have samples if the "pid"
14131    // parameter is greater than -1. The pid is set when the PMT is parsed, which contains the tracks list.
14132    // However, if the initSegment has already been generated, or we've reached the end of a segment (flush),
14133    // then we can remux one track without waiting for the other.
14134    const hasAudio = audioTrack.pid > -1;
14135    const hasVideo = videoTrack.pid > -1;
14136    const length = videoTrack.samples.length;
14137    const enoughAudioSamples = audioTrack.samples.length > 0;
14138    const enoughVideoSamples = flush && length > 0 || length > 1;
14139    const canRemuxAvc = (!hasAudio || enoughAudioSamples) && (!hasVideo || enoughVideoSamples) || this.ISGenerated || flush;
14140    if (canRemuxAvc) {
14141      if (this.ISGenerated) {
14142        var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2;
14143        const config = this.videoTrackConfig;
14144        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]))) {
14145          this.resetInitSegment();
14146        }
14147      } else {
14148        initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
14149      }
14150      const isVideoContiguous = this.isVideoContiguous;
14151      let firstKeyFrameIndex = -1;
14152      let firstKeyFramePTS;
14153      if (enoughVideoSamples) {
14154        firstKeyFrameIndex = findKeyframeIndex(videoTrack.samples);
14155        if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) {
14156          independent = true;
14157          if (firstKeyFrameIndex > 0) {
14158            logger.warn(`[mp4-remuxer]: Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`);
14159            const startPTS = this.getVideoStartPts(videoTrack.samples);
14160            videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex);
14161            videoTrack.dropped += firstKeyFrameIndex;
14162            videoTimeOffset += (videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale;
14163            firstKeyFramePTS = videoTimeOffset;
14164          } else if (firstKeyFrameIndex === -1) {
14165            logger.warn(`[mp4-remuxer]: No keyframe found out of ${length} video samples`);
14166            independent = false;
14167          }
14168        }
14169      }
14170      if (this.ISGenerated) {
14171        if (enoughAudioSamples && enoughVideoSamples) {
14172          // timeOffset is expected to be the offset of the first timestamp of this fragment (first DTS)
14173          // if first audio DTS is not aligned with first video DTS then we need to take that into account
14174          // when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small
14175          // drift between audio and video streams
14176          const startPTS = this.getVideoStartPts(videoTrack.samples);
14177          const tsDelta = normalizePts(audioTrack.samples[0].pts, startPTS) - startPTS;
14178          const audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale;
14179          audioTimeOffset += Math.max(0, audiovideoTimestampDelta);
14180          videoTimeOffset += Math.max(0, -audiovideoTimestampDelta);
14181        }
14182
14183        // Purposefully remuxing audio before video, so that remuxVideo can use nextAudioPts, which is calculated in remuxAudio.
14184        if (enoughAudioSamples) {
vendor: 4,806 bytes, lines 14185-14301
14185          // if initSegment was generated without audio samples, regenerate it again
14186          if (!audioTrack.samplerate) {
14187            logger.warn('[mp4-remuxer]: regenerate InitSegment as audio detected');
14188            initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
14189          }
14190          audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === PlaylistLevelType.AUDIO ? videoTimeOffset : undefined);
14191          if (enoughVideoSamples) {
14192            const audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0;
14193            // if initSegment was generated without video samples, regenerate it again
14194            if (!videoTrack.inputTimeScale) {
14195              logger.warn('[mp4-remuxer]: regenerate InitSegment as video detected');
14196              initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
14197            }
14198            video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength);
14199          }
14200        } else if (enoughVideoSamples) {
14201          video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0);
14202        }
14203        if (video) {
14204          video.firstKeyFrame = firstKeyFrameIndex;
14205          video.independent = firstKeyFrameIndex !== -1;
14206          video.firstKeyFramePTS = firstKeyFramePTS;
14207        }
14208      }
14209    }
14210
14211    // Allow ID3 and text to remux, even if more audio/video samples are required
14212    if (this.ISGenerated && this._initPTS && this._initDTS) {
14213      if (id3Track.samples.length) {
14214        id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS);
14215      }
14216      if (textTrack.samples.length) {
14217        text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS);
14218      }
14219    }
14220    return {
14221      audio,
14222      video,
14223      initSegment,
14224      independent,
14225      text,
14226      id3
14227    };
14228  }
14229  generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset) {
14230    const audioSamples = audioTrack.samples;
14231    const videoSamples = videoTrack.samples;
14232    const typeSupported = this.typeSupported;
14233    const tracks = {};
14234    const _initPTS = this._initPTS;
14235    let computePTSDTS = !_initPTS || accurateTimeOffset;
14236    let container = 'audio/mp4';
14237    let initPTS;
14238    let initDTS;
14239    let timescale;
14240    if (computePTSDTS) {
14241      initPTS = initDTS = Infinity;
14242    }
14243    if (audioTrack.config && audioSamples.length) {
14244      // let's use audio sampling rate as MP4 time scale.
14245      // rationale is that there is a integer nb of audio frames per audio sample (1024 for AAC)
14246      // using audio sampling rate here helps having an integer MP4 frame duration
14247      // this avoids potential rounding issue and AV sync issue
14248      audioTrack.timescale = audioTrack.samplerate;
14249      switch (audioTrack.segmentCodec) {
14250        case 'mp3':
14251          if (typeSupported.mpeg) {
14252            // Chrome and Safari
14253            container = 'audio/mpeg';
14254            audioTrack.codec = '';
14255          } else if (typeSupported.mp3) {
14256            // Firefox
14257            audioTrack.codec = 'mp3';
14258          }
14259          break;
14260        case 'ac3':
14261          audioTrack.codec = 'ac-3';
14262          break;
14263      }
14264      tracks.audio = {
14265        id: 'audio',
14266        container: container,
14267        codec: audioTrack.codec,
14268        initSegment: audioTrack.segmentCodec === 'mp3' && typeSupported.mpeg ? new Uint8Array(0) : MP4.initSegment([audioTrack]),
14269        metadata: {
14270          channelCount: audioTrack.channelCount
14271        }
14272      };
14273      if (computePTSDTS) {
14274        timescale = audioTrack.inputTimeScale;
14275        if (!_initPTS || timescale !== _initPTS.timescale) {
14276          // remember first PTS of this demuxing context. for audio, PTS = DTS
14277          initPTS = initDTS = audioSamples[0].pts - Math.round(timescale * timeOffset);
14278        } else {
14279          computePTSDTS = false;
14280        }
14281      }
14282    }
14283    if (videoTrack.sps && videoTrack.pps && videoSamples.length) {
14284      // let's use input time scale as MP4 video timescale
14285      // we use input time scale straight away to avoid rounding issues on frame duration / cts computation
14286      videoTrack.timescale = videoTrack.inputTimeScale;
14287      tracks.video = {
14288        id: 'main',
14289        container: 'video/mp4',
14290        codec: videoTrack.codec,
14291        initSegment: MP4.initSegment([videoTrack]),
14292        metadata: {
14293          width: videoTrack.width,
14294          height: videoTrack.height
14295        }
14296      };
14297      if (computePTSDTS) {
14298        timescale = videoTrack.inputTimeScale;
14299        if (!_initPTS || timescale !== _initPTS.timescale) {
14300          const startPTS = this.getVideoStartPts(videoSamples);
14301          const startOffset = Math.round(timescale * timeOffset);
14302          initDTS = Math.min(initDTS, normalizePts(videoSamples[0].dts, startPTS) - startOffset);
14303          initPTS = Math.min(initPTS, startPTS - startOffset);
14304        } else {
14305          computePTSDTS = false;
14306        }
14307      }
14308      this.videoTrackConfig = {
14309        width: videoTrack.width,
14310        height: videoTrack.height,
14311        pixelRatio: videoTrack.pixelRatio
14312      };
14313    }
14314    if (Object.keys(tracks).length) {
14315      this.ISGenerated = true;
14316      if (computePTSDTS) {
14317        this._initPTS = {
14318          baseTime: initPTS,
14319          timescale: timescale
14320        };
14321        this._initDTS = {
14322          baseTime: initDTS,
14323          timescale: timescale
14324        };
14325      } else {
14326        initPTS = timescale = undefined;
14327      }
14328      return {
14329        tracks,
14330        initPTS,
14331        timescale
14332      };
14333    }
14334  }
14335  remuxVideo(track, timeOffset, contiguous, audioTrackLength) {
14336    const timeScale = track.inputTimeScale;
14337    const inputSamples = track.samples;
14338    const outputSamples = [];
14339    const nbSamples = inputSamples.length;
14340    const initPTS = this._initPTS;
14341    let nextAvcDts = this.nextAvcDts;
14342    let offset = 8;
14343    let mp4SampleDuration = this.videoSampleDuration;
14344    let firstDTS;
14345    let lastDTS;
14346    let minPTS = Number.POSITIVE_INFINITY;
14347    let maxPTS = Number.NEGATIVE_INFINITY;
14348    let sortSamples = false;
14349
14350    // if parsed fragment is contiguous with last one, let's use last DTS value as reference
14351    if (!contiguous || nextAvcDts === null) {
14352      const pts = timeOffset * timeScale;
14353      const cts = inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts);
14354      if (chromeVersion && nextAvcDts !== null && Math.abs(pts - cts - nextAvcDts) < 15000) {
14355        // treat as contigous to adjust samples that would otherwise produce video buffer gaps in Chrome
14356        contiguous = true;
14357      } else {
14358        // if not contiguous, let's use target timeOffset
14359        nextAvcDts = pts - cts;
14360      }
14361    }
14362
14363    // PTS is coded on 33bits, and can loop from -2^32 to 2^32
14364    // PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value
14365    const initTime = initPTS.baseTime * timeScale / initPTS.timescale;
14366    for (let i = 0; i < nbSamples; i++) {
14367      const sample = inputSamples[i];
14368      sample.pts = normalizePts(sample.pts - initTime, nextAvcDts);
14369      sample.dts = normalizePts(sample.dts - initTime, nextAvcDts);
14370      if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) {
14371        sortSamples = true;
14372      }
14373    }
14374
14375    // sort video samples by DTS then PTS then demux id order
14376    if (sortSamples) {
14377      inputSamples.sort(function (a, b) {
14378        const deltadts = a.dts - b.dts;
14379        const deltapts = a.pts - b.pts;
14380        return deltadts || deltapts;
14381      });
14382    }
14383
14384    // Get first/last DTS
14385    firstDTS = inputSamples[0].dts;
14386    lastDTS = inputSamples[inputSamples.length - 1].dts;
14387
14388    // Sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS
14389    // set this constant duration as being the avg delta between consecutive DTS.
14390    const inputDuration = lastDTS - firstDTS;
14391    const averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) : mp4SampleDuration || track.inputTimeScale / 30;
14392
14393    // if fragment are contiguous, detect hole/overlapping between fragments
14394    if (contiguous) {
14395      // check timestamp continuity across consecutive fragments (this is to remove inter-fragment gap/hole)
14396      const delta = firstDTS - nextAvcDts;
14397      const foundHole = delta > averageSampleDuration;
14398      const foundOverlap = delta < -1;
14399      if (foundHole || foundOverlap) {
14400        if (foundHole) {
14401          logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
14402        } else {
14403          logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
14404        }
14405        if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
14406          firstDTS = nextAvcDts;
14407          const firstPTS = inputSamples[0].pts - delta;
14408          if (foundHole) {
14409            inputSamples[0].dts = firstDTS;
14410            inputSamples[0].pts = firstPTS;
14411          } else {
14412            for (let i = 0; i < inputSamples.length; i++) {
14413              if (inputSamples[i].dts > firstPTS) {
14414                break;
14415              }
14416              inputSamples[i].dts -= delta;
14417              inputSamples[i].pts -= delta;
14418            }
14419          }
14420          logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
14421        }
14422      }
14423    }
14424    firstDTS = Math.max(0, firstDTS);
14425    let nbNalu = 0;
14426    let naluLen = 0;
14427    let dtsStep = firstDTS;
14428    for (let i = 0; i < nbSamples; i++) {
14429      // compute total/avc sample length and nb of NAL units
14430      const sample = inputSamples[i];
vendor: 5,197 bytes, lines 14431-14545
14431      const units = sample.units;
14432      const nbUnits = units.length;
14433      let sampleLen = 0;
14434      for (let j = 0; j < nbUnits; j++) {
14435        sampleLen += units[j].data.length;
14436      }
14437      naluLen += sampleLen;
14438      nbNalu += nbUnits;
14439      sample.length = sampleLen;
14440
14441      // ensure sample monotonic DTS
14442      if (sample.dts < dtsStep) {
14443        sample.dts = dtsStep;
14444        dtsStep += averageSampleDuration / 4 | 0 || 1;
14445      } else {
14446        dtsStep = sample.dts;
14447      }
14448      minPTS = Math.min(sample.pts, minPTS);
14449      maxPTS = Math.max(sample.pts, maxPTS);
14450    }
14451    lastDTS = inputSamples[nbSamples - 1].dts;
14452
14453    /* concatenate the video data and construct the mdat in place
14454      (need 8 more bytes to fill length and mpdat type) */
14455    const mdatSize = naluLen + 4 * nbNalu + 8;
14456    let mdat;
14457    try {
14458      mdat = new Uint8Array(mdatSize);
14459    } catch (err) {
14460      this.observer.emit(Events.ERROR, Events.ERROR, {
14461        type: ErrorTypes.MUX_ERROR,
14462        details: ErrorDetails.REMUX_ALLOC_ERROR,
14463        fatal: false,
14464        error: err,
14465        bytes: mdatSize,
14466        reason: `fail allocating video mdat ${mdatSize}`
14467      });
14468      return;
14469    }
14470    const view = new DataView(mdat.buffer);
14471    view.setUint32(0, mdatSize);
14472    mdat.set(MP4.types.mdat, 4);
14473    let stretchedLastFrame = false;
14474    let minDtsDelta = Number.POSITIVE_INFINITY;
14475    let minPtsDelta = Number.POSITIVE_INFINITY;
14476    let maxDtsDelta = Number.NEGATIVE_INFINITY;
14477    let maxPtsDelta = Number.NEGATIVE_INFINITY;
14478    for (let i = 0; i < nbSamples; i++) {
14479      const VideoSample = inputSamples[i];
14480      const VideoSampleUnits = VideoSample.units;
14481      let mp4SampleLength = 0;
14482      // convert NALU bitstream to MP4 format (prepend NALU with size field)
14483      for (let j = 0, nbUnits = VideoSampleUnits.length; j < nbUnits; j++) {
14484        const unit = VideoSampleUnits[j];
14485        const unitData = unit.data;
14486        const unitDataLen = unit.data.byteLength;
14487        view.setUint32(offset, unitDataLen);
14488        offset += 4;
14489        mdat.set(unitData, offset);
14490        offset += unitDataLen;
14491        mp4SampleLength += 4 + unitDataLen;
14492      }
14493
14494      // expected sample duration is the Decoding Timestamp diff of consecutive samples
14495      let ptsDelta;
14496      if (i < nbSamples - 1) {
14497        mp4SampleDuration = inputSamples[i + 1].dts - VideoSample.dts;
14498        ptsDelta = inputSamples[i + 1].pts - VideoSample.pts;
14499      } else {
14500        const config = this.config;
14501        const lastFrameDuration = i > 0 ? VideoSample.dts - inputSamples[i - 1].dts : averageSampleDuration;
14502        ptsDelta = i > 0 ? VideoSample.pts - inputSamples[i - 1].pts : averageSampleDuration;
14503        if (config.stretchShortVideoTrack && this.nextAudioPts !== null) {
14504          // In some cases, a segment's audio track duration may exceed the video track duration.
14505          // Since we've already remuxed audio, and we know how long the audio track is, we look to
14506          // see if the delta to the next segment is longer than maxBufferHole.
14507          // If so, playback would potentially get stuck, so we artificially inflate
14508          // the duration of the last frame to minimize any potential gap between segments.
14509          const gapTolerance = Math.floor(config.maxBufferHole * timeScale);
14510          const deltaToFrameEnd = (audioTrackLength ? minPTS + audioTrackLength * timeScale : this.nextAudioPts) - VideoSample.pts;
14511          if (deltaToFrameEnd > gapTolerance) {
14512            // We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video
14513            // frame overlap. maxBufferHole should be >> lastFrameDuration anyway.
14514            mp4SampleDuration = deltaToFrameEnd - lastFrameDuration;
14515            if (mp4SampleDuration < 0) {
14516              mp4SampleDuration = lastFrameDuration;
14517            } else {
14518              stretchedLastFrame = true;
14519            }
14520            logger.log(`[mp4-remuxer]: It is approximately ${deltaToFrameEnd / 90} ms to the next segment; using duration ${mp4SampleDuration / 90} ms for the last video frame.`);
14521          } else {
14522            mp4SampleDuration = lastFrameDuration;
14523          }
14524        } else {
14525          mp4SampleDuration = lastFrameDuration;
14526        }
14527      }
14528      const compositionTimeOffset = Math.round(VideoSample.pts - VideoSample.dts);
14529      minDtsDelta = Math.min(minDtsDelta, mp4SampleDuration);
14530      maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration);
14531      minPtsDelta = Math.min(minPtsDelta, ptsDelta);
14532      maxPtsDelta = Math.max(maxPtsDelta, ptsDelta);
14533      outputSamples.push(new Mp4Sample(VideoSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset));
14534    }
14535    if (outputSamples.length) {
14536      if (chromeVersion) {
14537        if (chromeVersion < 70) {
14538          // Chrome workaround, mark first sample as being a Random Access Point (keyframe) to avoid sourcebuffer append issue
14539          // https://code.google.com/p/chromium/issues/detail?id=229412
14540          const flags = outputSamples[0].flags;
14541          flags.dependsOn = 2;
14542          flags.isNonSync = 0;
14543        }
14544      } else if (safariWebkitVersion) {
14545        // Fix for "CNN special report, with CC" in test-streams (Safari browser only
14545)
14546        // Ignore DTS when frame durations are irregular. Safari MSE does not handle this leading to gaps.
14547        if (maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta && averageSampleDuration / maxDtsDelta < 0.025 && outputSamples[0].cts === 0) {
14548          logger.warn('Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.');
14549          let dts = firstDTS;
14550          for (let i = 0, len = outputSamples.length; i < len; i++) {
14551            const nextDts = dts + outputSamples[i].duration;
14552            const pts = dts + outputSamples[i].cts;
14553            if (i < len - 1) {
14554              const nextPts = nextDts + outputSamples[i + 1].cts;
14555              outputSamples[i].duration = nextPts - pts;
14556            } else {
14557              outputSamples[i].duration = i ? outputSamples[i - 1].duration : averageSampleDuration;
14558            }
14559            outputSamples[i].cts = 0;
14560            dts = nextDts;
14561          }
14562        }
14563      }
14564    }
14565    // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
14566    mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
14567    this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
14568    this.videoSampleDuration = mp4SampleDuration;
14569    this.isVideoContiguous = true;
14570    const moof = MP4.moof(track.sequenceNumber++, firstDTS, _extends({}, track, {
14571      samples: outputSamples
14572    }));
14573    const type = 'video';
14574    const data = {
14575      data1: moof,
14576      data2: mdat,
14577      startPTS: minPTS / timeScale,
14578      endPTS: (maxPTS + mp4SampleDuration) / timeScale,
14579      startDTS: firstDTS / timeScale,
14580      endDTS: nextAvcDts / timeScale,
14581      type,
14582      hasAudio: false,
14583      hasVideo: true,
14584      nb: outputSamples.length,
14585      dropped: track.dropped
14586    };
14587    track.samples = [];
14588    track.dropped = 0;
14589    return data;
14590  }
14591  getSamplesPerFrame(track) {
14592    switch (track.segmentCodec) {
14593      case 'mp3':
14594        return MPEG_AUDIO_SAMPLE_PER_FRAME;
14595      case 'ac3':
14596        return AC3_SAMPLES_PER_FRAME;
14597      default:
14598        return AAC_SAMPLES_PER_FRAME;
14599    }
14600  }
14601  remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset) {
14602    const inputTimeScale = track.inputTimeScale;
14603    const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale;
14604    const scaleFactor = inputTimeScale / mp4timeScale;
14605    const mp4SampleDuration = this.getSamplesPerFrame(track);
14606    const inputSampleDuration = mp4SampleDuration * scaleFactor;
14607    const initPTS = this._initPTS;
14608    const rawMPEG = track.segmentCodec === 'mp3' && this.typeSupported.mpeg;
14609    const outputSamples = [];
14610    const alignedWithVideo = videoTimeOffset !== undefined;
14611    let inputSamples = track.samples;
14612    let offset = rawMPEG ? 0 : 8;
14613    let nextAudioPts = this.nextAudioPts || -1;
14614
14615    // window.audioSamples ? window.audioSamples.push(inputSamples.map(s => s.pts)) : (window.audioSamples = [inputSamples.map(s => s.pts)]);
14616
14617    // for audio samples, also consider consecutive fragments as being contiguous (even if a level switch occurs),
14618    // for sake of clarity:
14619    // consecutive fragments are frags with
14620    //  - less than 100ms gaps between new time offset (if accurate) and next expected PTS OR
14621    //  - less than 20 audio frames distance
14622    // contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1)
14623    // this helps ensuring audio continuity
14624    // and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame
14625    const timeOffsetMpegTS = timeOffset * inputTimeScale;
14626    const initTime = initPTS.baseTime * inputTimeScale / initPTS.timescale;
14627    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);
14628
14629    // compute normalized PTS
14630    inputSamples.forEach(function (sample) {
14631      sample.pts = normalizePts(sample.pts - initTime, timeOffsetMpegTS);
14632    });
14633    if (!contiguous || nextAudioPts < 0) {
14634      // filter out sample with negative PTS that are not playable anyway
14635      // if we don't remove these negative samples, they will shift all audio samples forward.
14636      // leading to audio overlap between current / next fragment
14637      inputSamples = inputSamples.filter(sample => sample.pts >= 0);
14638
14639      // in case all samples have negative PTS, and have been filtered out, return now
14640      if (!inputSamples.length) {
14641        return;
14642      }
14643      if (videoTimeOffset === 0) {
14644        // Set the start to 0 to match video so that start gaps larger than inputSampleDuration are filled with silence
14645        nextAudioPts = 0;
14646      } else if (accurateTimeOffset && !alignedWithVideo) {
14647        // When not seeking, not live, and LevelDetails.PTSKnown, use fragment start as predicted next audio PTS
14648        nextAudioPts = Math.max(0, timeOffsetMpegTS);
14649      } else {
14650        // if frags are not contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS
14651        nextAudioPts = inputSamples[0].pts;
14652      }
14653    }
14654
14655    // If the audio track is missing samples, the frames seem to get "left-shifted" within the
14656    // resulting mp4 segment, causing sync issues and leaving gaps at the end of the audio segment.
14657    // In an effort to prevent this from happening, we inject frames here where there are gaps.
14658    // When possible, we inject a silent frame; when that's not possible, we duplicate the last
14659    // frame.
14660
14661    if (track.segmentCodec === 'aac') {
14662      const maxAudioFramesDrift = this.config.maxAudioFramesDrift;
14663      for (let i = 0, nextPts = nextAudioPts; i < inputSamples.length; i++) {
14664        // First, let's see how far off this frame is from where we expect it to be
14665        const sample = inputSamples[i];
14666        const pts = sample.pts;
14667        const delta = pts - nextPts;
14668        const duration = Math.abs(1000 * delta / inputTimeScale);
14669
14670        // When remuxing with video, if we're overlapping by more than a duration, drop this sample to stay in sync
14671        if (delta <= -maxAudioFramesDrift * inputSampleDuration && alignedWithVideo) {
14672          if (i === 0) {
14673            logger.warn(`Audio frame @ ${(pts / inputTimeScale).toFixed(3)}s overlaps nextAudioPts by ${Math.round(1000 * delta / inputTimeScale)} ms.`);
14674            this.nextAudioPts = nextAudioPts = nextPts = pts;
14675          }
14676        } // eslint-disable-line brace-style
14677
14678        // Insert missing frames if:
14679        // 1: We're more than maxAudioFramesDrift frame away
14680        // 2: Not more than MAX_SILENT_FRAME_DURATION away
14681        // 3: currentTime (aka nextPtsNorm) is not 0
14682        // 4: remuxing with video (videoTimeOffset !== undefined)
14683        else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && alignedWithVideo) {
14684          let missing = Math.round(delta / inputSampleDuration);
14685          // Adjust nextPts so that silent samples are aligned with media pts. This will prevent media samples from
14686          // later being shifted if nextPts is based on timeOffset and delta is not a multiple of inputSampleDuration.
14687          nextPts = pts - missing * inputSampleDuration;
14688          if (nextPts < 0) {
14689            missing--;
14690            nextPts += inputSampleDuration;
14691          }
14692          if (i === 0) {
14693            this.nextAudioPts = nextAudioPts = nextPts;
14694          }
14695          logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
14696          for (let j = 0; j < missing; j++) {
14697            const newStamp = Math.max(nextPts, 0);
14698            let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14699            if (!fillFrame) {
14700              logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
14701              fillFrame = sample.unit.subarray();
14702            }
14703            inputSamples.splice(i, 0, {
14704              unit: fillFrame,
14705              pts: newStamp
14706            });
14707            nextPts += inputSampleDuration;
14708            i++;
14709          }
14710        }
14711        sample.pts = nextPts;
14712        nextPts += inputSampleDuration;
14713      }
14714    }
14715    let firstPTS = null;
14716    let lastPTS = null;
14717    let mdat;
14718    let mdatSize = 0;
14719    let sampleLength = inputSamples.length;
14720    while (sampleLength--) {
14721      mdatSize += inputSamples[sampleLength].unit.byteLength;
14722    }
14723    for (let j = 0, _nbSamples = inputSamples.length; j < _nbSamples; j++) {
14724      const audioSample = inputSamples[j];
14725      const unit = audioSample.unit;
14726      let pts = audioSample.pts;
14727      if (lastPTS !== null) {
14728        // If we have more than one sample, set the duration of the sample to the "real" duration; the PTS diff with
14729        // the previous sample
14730        const prevSample = outputSamples[j - 1];
14731        prevSample.duration = Math.round((pts - lastPTS) / scaleFactor);
14732      } else {
14733        if (contiguous && track.segmentCodec === 'aac') {
14734          // set PTS/DTS to expected PTS/DTS
14735          pts = nextAudioPts;
14736        }
14737        // remember first PTS of our audioSamples
14738        firstPTS = pts;
14739        if (mdatSize > 0) {
14740          /* concatenate the audio data and construct the mdat in place
14741            (need 8 more bytes to fill length and mdat type) */
14742          mdatSize += offset;
14743          try {
14744            mdat = new Uint8Array(mdatSize);
14745          } catch (err) {
14746            this.observer.emit(Events.ERROR, Events.ERROR, {
14747              type: ErrorTypes.MUX_ERROR,
14748              details: ErrorDetails.REMUX_ALLOC_ERROR,
14749              fatal: false,
14750              error: err,
14751              bytes: mdatSize,
14752              reason: `fail allocating audio mdat ${mdatSize}`
14753            });
14754            return;
14755          }
14756          if (!rawMPEG) {
14757            const view = new DataView(mdat.buffer);
14758            view.setUint32(0, mdatSize);
14759            mdat.set(MP4.types.mdat, 4);
14760          }
14761        } else {
14762          // no audio samples
14763          return;
14764        }
14765      }
14766      mdat.set(unit, offset);
14767      const unitLen = unit.byteLength;
14768      offset += unitLen;
14769      // Default the sample's duration to the computed mp4SampleDuration, which will either be 1024 for AAC or 1152 for MPEG
14770      // In the case that we have 1 sample, this will be the duration. If we have more than one sample, the duration
14771      // becomes the PTS diff with the previous sample
14772      outputSamples.push(new Mp4Sample(true, mp4SampleDuration, unitLen, 0));
14773      lastPTS = pts;
14774    }
14775
14776    // We could end up with no audio samples if all input samples were overlapping with the previously remuxed ones
14777    const nbSamples = outputSamples.length;
14778    if (!nbSamples) {
14779      return;
14780    }
14781
14782    // The next audio sample PTS should be equal to last sample PTS + duration
14783    const lastSample = outputSamples[outputSamples.length - 1];
14784    this.nextAudioPts = nextAudioPts = lastPTS + scaleFactor * lastSample.duration;
14785
14786    // Set the track samples from inputSamples to outputSamples before remuxing
14787    const moof = rawMPEG ? new Uint8Array(0) : MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, {
14788      samples: outputSamples
14789    }));
14790
14791    // Clear the track samples. This also clears the samples array in the demuxer, since the reference is shared
14792    track.samples = [];
14793    const start = firstPTS / inputTimeScale;
14794    const end = nextAudioPts / inputTimeScale;
14795    const type = 'audio';
14796    const audioData = {
14797      data1: moof,
14798      data2: mdat,
14799      startPTS: start,
14800      endPTS: end,
14801      startDTS: start,
14802      endDTS: end,
14803      type,
14804      hasAudio: true,
14805      hasVideo: false,
14806      nb: nbSamples
14807    };
14808    this.isAudioContiguous = true;
14809    return audioData;
14810  }
14811  remuxEmptyAudio(track, timeOffset, contiguous, videoData) {
14812    const inputTimeScale = track.inputTimeScale;
14813    const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale;
14814    const scaleFactor = inputTimeScale / mp4timeScale;
14815    const nextAudioPts = this.nextAudioPts;
14816    // sync with video's timestamp
14817    const initDTS = this._initDTS;
14818    const init90kHz = initDTS.baseTime * 90000 / initDTS.timescale;
14819    const startDTS = (nextAudioPts !== null ? nextAudioPts : videoData.startDTS * inputTimeScale) + init90kHz;
14820    const endDTS = videoData.endDTS * inputTimeScale + init90kHz;
14821    // one sample's duration value
14822    const frameDuration = scaleFactor * AAC_SAMPLES_PER_FRAME;
14823    // samples count of this segment's duration
14824    const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
14825    // silent frame
14826    const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
14827    logger.warn('[mp4-remuxer]: remux empty Audio');
14828    // Can't remux if we can't generate a silent frame...
14829    if (!silentFrame) {
14830      logger.trace('[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec');
14831      return;
14832    }
14833    const samples = [];
14834    for (let i = 0; i < nbSamples; i++) {
14835      const stamp = startDTS + i * frameDuration;
14836      samples.push({
14837        unit: silentFrame,
14838        pts: stamp,
14839        dts: stamp
14840      });
14841    }
14842    track.samples = samples;
14843    return this.remuxAudio(track, timeOffset, contiguous, false);
14844  }
14845}
14846function normalizePts(value, reference) {
14847  let offset;
14848  if (reference === null) {
14849    return value;
14850  }
14851  if (reference < value) {
14852    // - 2^33
14853    offset = -8589934592;
14854  } else {
14855    // + 2^33
14856    offset = 8589934592;
14857  }
14858  /* PTS is 33bit (from 0 to 2^33 -1)
14859    if diff between value and reference is bigger than half of the amplitude (2^32) then it means that
14860    PTS looping occured. fill the gap */
14861  while (Math.abs(value - reference) > 4294967296) {
14862    value += offset;
14863  }
14864  return value;
14865}
14866function findKeyframeIndex(samples) {
14867  for (let i = 0; i < samples.length; i++) {
14868    if (samples[i].key) {
14869      return i;
14870    }
14871  }
14872  return -1;
14873}
14874function flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS) {
14875  const length = track.samples.length;
14876  if (!length) {
14877    return;
14878  }
14879  const inputTimeScale = track.inputTimeScale;
14880  for (let index = 0; index < length; index++) {
14881    const sample = track.samples[index];
14882    // setting id3 pts, dts to relative time
14883    // using this._initPTS and this._initDTS to calculate relative time
14884    sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
14885    sample.dts = normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
14886  }
14887  const samples = track.samples;
14888  track.samples = [];
14889  return {
14890    samples
14891  };
14892}
14893function flushTextTrackUserdataCueSamples(track, timeOffset, initPTS) {
14894  const length = track.samples.length;
14895  if (!length) {
14896    return;
14897  }
14898  const inputTimeScale = track.inputTimeScale;
14899  for (let index = 0; index < length; index++) {
14900    const sample = track.samples[index];
14901    // setting text pts, dts to relative time
14902    // using this._initPTS and this._initDTS to calculate relative time
14903    sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
14904  }
14905  track.samples.sort((a, b) => a.pts - b.pts);
14906  const samples = track.samples;
14907  track.samples = [];
14908  return {
14909    samples
14910  };
14911}
14912class Mp4Sample {
14913  constructor(isKeyframe, duration, size, cts) {
14914    this.size = void 0;
14915    this.duration = void 0;
14916    this.cts = void 0;
14917    this.flags = void 0;
14918    this.duration = duration;
14919    this.size = size;
14920    this.cts = cts;
14921    this.flags = {
14922      isLeading: 0,
14923      isDependedOn: 0,
14924      hasRedundancy: 0,
14925      degradPrio: 0,
14926      dependsOn: isKeyframe ? 2 : 1,
14927      isNonSync: isKeyframe ? 0 : 1
14928    };
14929  }
14930}
14931
14932class PassThroughRemuxer {
14933  constructor() {
14934    this.emitInitSegment = false;
14935    this.audioCodec = void 0;
14936    this.videoCodec = void 0;
14937    this.initData = void 0;
14938    this.initPTS = null;
14939    this.initTracks = void 0;
14940    this.lastEndTime = null;
14941  }
14942  destroy() {}
14943  resetTimeStamp(defaultInitPTS) {
14944    this.initPTS = defaultInitPTS;
14945    this.lastEndTime = null;
14946  }
14947  resetNextTimestamp() {
14948    this.lastEndTime = null;
14949  }
14950  resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata) {
14951    this.audioCodec = audioCodec;
14952    this.videoCodec = videoCodec;
14953    this.generateInitSegment(patchEncyptionData(initSegment, decryptdata));
14954    this.emitInitSegment = true;
14955  }
14956  generateInitSegment(initSegment) {
14957    let {
14958      audioCodec,
14959      videoCodec
14960    } = this;
14961    if (!(initSegment != null && initSegment.byteLength)) {
14962      this.initTracks = undefined;
14963      this.initData = undefined;
14964      return;
14965    }
14966    const initData = this.initData = parseInitSegment(initSegment);
14967
14968    // Get codec from initSegment or fallback to default
14969    if (initData.audio) {
14970      audioCodec = getParsedTrackCodec(initData.audio, ElementaryStreamTypes.AUDIO);
14971    }
14972    if (initData.video) {
14973      videoCodec = getParsedTrackCodec(initData.video, ElementaryStreamTypes.VIDEO);
14974    }
14975    const tracks = {};
14976    if (initData.audio && initData.video) {
14977      tracks.audiovideo = {
14978        container: 'video/mp4',
14979        codec: audioCodec + ',' + videoCodec,
14980        initSegment,
14981        id: 'main'
14982      };
14983    } else if (initData.audio) {
14984      tracks.audio = {
14985        container: 'audio/mp4',
14986        codec: audioCodec,
14987        initSegment,
14988        id: 'audio'
14989      };
14990    } else if (initData.video) {
14991      tracks.video = {
14992        container: 'video/mp4',
14993        codec: videoCodec,
14994        initSegment,
14995        id: 'main'
14996      };
14997    } else {
14998      logger.warn('[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes.');
14999    }
15000    this.initTracks = tracks;
15001  }
15002  remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOff
15002set) {
15003    var _initData, _initData2;
15004    let {
15005      initPTS,
15006      lastEndTime
15007    } = this;
15008    const result = {
15009      audio: undefined,
15010      video: undefined,
15011      text: textTrack,
15012      id3: id3Track,
15013      initSegment: undefined
15014    };
15015
15016    // If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the
15017    // lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update
15018    // the media duration (which is what timeOffset is provided as) before we need to process the next chunk.
15019    if (!isFiniteNumber(lastEndTime)) {
15020      lastEndTime = this.lastEndTime = timeOffset || 0;
15021    }
15022
15023    // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only
15024    // audio or video (or both); adding it to video was an arbitrary choice.
15025    const data = videoTrack.samples;
15026    if (!(data != null && data.length)) {
15027      return result;
15028    }
15029    const initSegment = {
15030      initPTS: undefined,
15031      timescale: 1
15032    };
15033    let initData = this.initData;
15034    if (!((_initData = initData) != null && _initData.length)) {
15035      this.generateInitSegment(data);
15036      initData = this.initData;
15037    }
15038    if (!((_initData2 = initData) != null && _initData2.length)) {
15039      // We can't remux if the initSegment could not be generated
15040      logger.warn('[passthrough-remuxer.ts]: Failed to generate initSegment.');
15041      return result;
15042    }
15043    if (this.emitInitSegment) {
15044      initSegment.tracks = this.initTracks;
15045      this.emitInitSegment = false;
15046    }
15047    const duration = getDuration(data, initData);
15048    const startDTS = getStartDTS(initData, data);
15049    const decodeTime = startDTS === null ? timeOffset : startDTS;
15050    if (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || initSegment.timescale !== initPTS.timescale && accurateTimeOffset) {
15051      initSegment.initPTS = decodeTime - timeOffset;
15052      if (initPTS && initPTS.timescale === 1) {
15053        logger.warn(`Adjusting initPTS by ${initSegment.initPTS - initPTS.baseTime}`);
15054      }
15055      this.initPTS = initPTS = {
15056        baseTime: initSegment.initPTS,
15057        timescale: 1
15058      };
15059    }
15060    const startTime = audioTrack ? decodeTime - initPTS.baseTime / initPTS.timescale : lastEndTime;
15061    const endTime = startTime + duration;
15062    offsetStartDTS(initData, data, initPTS.baseTime / initPTS.timescale);
15063    if (duration > 0) {
15064      this.lastEndTime = endTime;
15065    } else {
15066      logger.warn('Duration parsed from mp4 should be greater than zero');
15067      this.resetNextTimestamp();
15068    }
15069    const hasAudio = !!initData.audio;
15070    const hasVideo = !!initData.video;
15071    let type = '';
15072    if (hasAudio) {
15073      type += 'audio';
15074    }
15075    if (hasVideo) {
15076      type += 'video';
15077    }
15078    const track = {
15079      data1: data,
15080      startPTS: startTime,
15081      startDTS: startTime,
15082      endPTS: endTime,
15083      endDTS: endTime,
15084      type,
15085      hasAudio,
15086      hasVideo,
15087      nb: 1,
15088      dropped: 0
15089    };
15090    result.audio = track.type === 'audio' ? track : undefined;
15091    result.video = track.type !== 'audio' ? track : undefined;
15092    result.initSegment = initSegment;
15093    result.id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS);
15094    if (textTrack.samples.length) {
15095      result.text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS);
15096    }
15097    return result;
15098  }
15099}
15100function isInvalidInitPts(initPTS, startDTS, timeOffset, duration) {
15101  if (initPTS === null) {
15102    return true;
15103  }
15104  // InitPTS is invalid when distance from program would be more than segment duration or a minimum of one second
15105  const minDuration = Math.max(duration, 1);
15106  const startTime = startDTS - initPTS.baseTime / initPTS.timescale;
15107  return Math.abs(startTime - timeOffset) > minDuration;
15108}
15109function getParsedTrackCodec(track, type) {
15110  const parsedCodec = track == null ? void 0 : track.codec;
15111  if (parsedCodec && parsedCodec.length > 4) {
15112    return parsedCodec;
15113  }
15114  if (type === ElementaryStreamTypes.AUDIO) {
15115    if (parsedCodec === 'ec-3' || parsedCodec === 'ac-3' || parsedCodec === 'alac') {
15116      return parsedCodec;
15117    }
15118    if (parsedCodec === 'fLaC' || parsedCodec === 'Opus') {
15119      // Opting not to get `preferManagedMediaSource` from player config for isSupported() check for simplicity
15120      const preferManagedMediaSource = false;
15121      return getCodecCompatibleName(parsedCodec, preferManagedMediaSource);
15122    }
15123    const result = 'mp4a.40.5';
15124    logger.info(`Parsed audio codec "${parsedCodec}" or audio object type not handled. Using "${result}"`);
15125    return result;
15126  }
15127  // Provide defaults based on codec type
15128  // This allows for some playback of some fmp4 playlists without CODECS defined in manifest
15129  logger.warn(`Unhandled video codec "${parsedCodec}"`);
15130  if (parsedCodec === 'hvc1' || parsedCodec === 'hev1') {
15131    return 'hvc1.1.6.L120.90';
15132  }
15133  if (parsedCodec === 'av01') {
15134    return 'av01.0.04M.08';
15135  }
15136  return 'avc1.42e01e';
15137}
15138
15139let now;
15140// performance.now() not available on WebWorker, at least on Safari Desktop
15141try {
15142  now = self.performance.now.bind(self.performance);
15143} catch (err) {
15144  logger.debug('Unable to use Performance API on this environment');
15145  now = optionalSelf == null ? void 0 : optionalSelf.Date.now;
15146}
15147const muxConfig = [{
15148  demux: MP4Demuxer,
15149  remux: PassThroughRemuxer
15150}, {
15151  demux: TSDemuxer,
15152  remux: MP4Remuxer
15153}, {
15154  demux: AACDemuxer,
15155  remux: MP4Remuxer
15156}, {
15157  demux: MP3Demuxer,
15158  remux: MP4Remuxer
15159}];
15160{
15161  muxConfig.splice(2, 0, {
15162    demux: AC3Demuxer,
15163    remux: MP4Remuxer
15164  });
15165}
15166class Transmuxer {
15167  constructor(observer, typeSupported, config, vendor, id) {
15168    this.async = false;
15169    this.observer = void 0;
15170    this.typeSupported = void 0;
15171    this.config = void 0;
15172    this.vendor = void 0;
15173    this.id = void 0;
15174    this.demuxer = void 0;
15175    this.remuxer = void 0;
15176    this.decrypter = void 0;
15177    this.probe = void 0;
15178    this.decryptionPromise = null;
15179    this.transmuxConfig = void 0;
15180    this.currentTransmuxState = void 0;
15181    this.observer = observer;
15182    this.typeSupported = typeSupported;
15183    this.config = config;
15184    this.vendor = vendor;
15185    this.id = id;
15186  }
15187  configure(transmuxConfig) {
15188    this.transmuxConfig = transmuxConfig;
15189    if (this.decrypter) {
15190      this.decrypter.reset();
15191    }
15192  }
15193  push(data, decryptdata, chunkMeta, state) {
15194    const stats = chunkMeta.transmuxing;
15195    stats.executeStart = now();
15196    let uintData = new Uint8Array(data);
15197    const {
15198      currentTransmuxState,
15199      transmuxConfig
15200    } = this;
15201    if (state) {
15202      this.currentTransmuxState = state;
15203    }
15204    const {
15205      contiguous,
15206      discontinuity,
15207      trackSwitch,
15208      accurateTimeOffset,
15209      timeOffset,
15210      initSegmentChange
15211    } = state || currentTransmuxState;
15212    const {
15213      audioCodec,
15214      videoCodec,
15215      defaultInitPts,
15216      duration,
15217      initSegmentData
15218    } = transmuxConfig;
15219    const keyData = getEncryptionType(uintData, decryptdata);
15220    if (keyData && keyData.method === 'AES-128') {
15221      const decrypter = this.getDecrypter();
15222      // Software decryption is synchronous; webCrypto is not
15223      if (decrypter.isSync()) {
15224        // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
15225        // data is handled in the flush() call
15226        let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
15227        // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
15228        const loadingParts = chunkMeta.part > -1;
15229        if (loadingParts) {
15230          decryptedData = decrypter.flush();
15231        }
15232        if (!decryptedData) {
15233          stats.executeEnd = now();
15234          return emptyResult(chunkMeta);
15235        }
15236        uintData = new Uint8Array(decryptedData);
15237      } else {
15238        this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
15239          // Calling push here is important; if flush() is called while this is still resolving, this ensures that
15240          // the decrypted data has been transmuxed
15241          const result = this.push(decryptedData, null, chunkMeta);
15242          this.decryptionPromise = null;
15243          return result;
15244        });
15245        return this.decryptionPromise;
15246      }
15247    }
15248    const resetMuxers = this.needsProbing(discontinuity, trackSwitch);
15249    if (resetMuxers) {
15250      const error = this.configureTransmuxer(uintData);
15251      if (error) {
15252        logger.warn(`[transmuxer] ${error.message}`);
15253        this.observer.emit(Events.ERROR, Events.ERROR, {
15254          type: ErrorTypes.MEDIA_ERROR,
15255          details: ErrorDetails.FRAG_PARSING_ERROR,
15256          fatal: false,
15257          error,
15258          reason: error.message
15259        });
15260        stats.executeEnd = now();
15261        return emptyResult(chunkMeta);
15262      }
15263    }
15264    if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) {
15265      this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata);
15266    }
15267    if (discontinuity || initSegmentChange || resetMuxers) {
15268      this.resetInitialTimestamp(defaultInitPts);
15269    }
15270    if (!contiguous) {
15271      this.resetContiguity();
15272    }
15273    const result = this.transmux(uintData, keyData, timeOffset, accurateTimeOff
15273set, chunkMeta);
15274    const currentState = this.currentTransmuxState;
15275    currentState.contiguous = true;
15276    currentState.discontinuity = false;
15277    currentState.trackSwitch = false;
15278    stats.executeEnd = now();
15279    return result;
15280  }
15281
15282  // Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type)
15283  flush(chunkMeta) {
15284    const stats = chunkMeta.transmuxing;
15285    stats.executeStart = now();
15286    const {
15287      decrypter,
15288      currentTransmuxState,
15289      decryptionPromise
15290    } = this;
15291    if (decryptionPromise) {
15292      // Upon resolution, the decryption promise calls push() and returns its TransmuxerResult up the stack. Therefore
15293      // only flushing is required for async decryption
15294      return decryptionPromise.then(() => {
15295        return this.flush(chunkMeta);
15296      });
15297    }
15298    const transmuxResults = [];
15299    const {
15300      timeOffset
15301    } = currentTransmuxState;
15302    if (decrypter) {
15303      // The decrypter may have data cached, which needs to be demuxed. In this case we'll have two TransmuxResults
15304      // This happens in the case that we receive only 1 push call for a segment (either for non-progressive downloads,
15305      // or for progressive downloads with small segments)
15306      const decryptedData = decrypter.flush();
15307      if (decryptedData) {
15308        // Push always returns a TransmuxerResult if decryptdata is null
15309        transmuxResults.push(this.push(decryptedData, null, chunkMeta));
15310      }
15311    }
15312    const {
15313      demuxer,
15314      remuxer
15315    } = this;
15316    if (!demuxer || !remuxer) {
15317      // If probing failed, then Hls.js has been given content its not able to handle
15318      stats.executeEnd = now();
15319      return [emptyResult(chunkMeta)];
15320    }
15321    const demuxResultOrPromise = demuxer.flush(timeOffset);
15322    if (isPromise(demuxResultOrPromise)) {
15323      // Decrypt final SAMPLE-AES samples
15324      return demuxResultOrPromise.then(demuxResult => {
15325        this.flushRemux(transmuxResults, demuxResult, chunkMeta);
15326        return transmuxResults;
15327      });
15328    }
15329    this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta);
15330    return transmuxResults;
15331  }
15332  flushRemux(transmuxResults, demuxResult, chunkMeta) {
15333    const {
15334      audioTrack,
15335      videoTrack,
15336      id3Track,
15337      textTrack
15338    } = demuxResult;
15339    const {
15340      accurateTimeOffset,
15341      timeOffset
15342    } = this.currentTransmuxState;
15343    logger.log(`[transmuxer.ts]: Flushed fragment ${chunkMeta.sn}${chunkMeta.part > -1 ? ' p: ' + chunkMeta.part : ''} of level ${chunkMeta.level}`);
15344    const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id);
15345    transmuxResults.push({
15346      remuxResult,
15347      chunkMeta
15348    });
15349    chunkMeta.transmuxing.executeEnd = now();
15350  }
15351  resetInitialTimestamp(defaultInitPts) {
15352    const {
15353      demuxer,
15354      remuxer
15355    } = this;
15356    if (!demuxer || !remuxer) {
15357      return;
15358    }
15359    demuxer.resetTimeStamp(defaultInitPts);
15360    remuxer.resetTimeStamp(defaultInitPts);
15361  }
15362  resetContiguity() {
15363    const {
15364      demuxer,
15365      remuxer
15366    } = this;
15367    if (!demuxer || !remuxer) {
15368      return;
15369    }
15370    demuxer.resetContiguity();
15371    remuxer.resetNextTimestamp();
15372  }
15373  resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata) {
15374    const {
15375      demuxer,
15376      remuxer
15377    } = this;
15378    if (!demuxer || !remuxer) {
15379      return;
15380    }
15381    demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration);
15382    remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata);
15383  }
15384  destroy() {
15385    if (this.demuxer) {
15386      this.demuxer.destroy();
15387      this.demuxer = undefined;
15388    }
15389    if (this.remuxer) {
15390      this.remuxer.destroy();
15391      this.remuxer = undefined;
15392    }
15393  }
15394  transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta) {
15395    let result;
15396    if (keyData && keyData.method === 'SAMPLE-AES') {
15397      result = this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta);
15398    } else {
15399      result = this.transmuxUnencrypted(data, timeOffset, accurateTimeOff
vendor: 3,877 bytes, lines 15399-15516
15399set, chunkMeta);
15400    }
15401    return result;
15402  }
15403  transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta) {
15404    const {
15405      audioTrack,
15406      videoTrack,
15407      id3Track,
15408      textTrack
15409    } = this.demuxer.demux(data, timeOffset, false, !this.config.progressive);
15410    const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id);
15411    return {
15412      remuxResult,
15413      chunkMeta
15414    };
15415  }
15416  transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta) {
15417    return this.demuxer.demuxSampleAes(data, decryptData, timeOffset).then(demuxResult => {
15418      const remuxResult = this.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, this.id);
15419      return {
15420        remuxResult,
15421        chunkMeta
15422      };
15423    });
15424  }
15425  configureTransmuxer(data) {
15426    const {
15427      config,
15428      observer,
15429      typeSupported,
15430      vendor
15431    } = this;
15432    // probe for content type
15433    let mux;
15434    for (let i = 0, len = muxConfig.length; i < len; i++) {
15435      var _muxConfig$i$demux;
15436      if ((_muxConfig$i$demux = muxConfig[i].demux) != null && _muxConfig$i$demux.probe(data)) {
15437        mux = muxConfig[i];
15438        break;
15439      }
15440    }
15441    if (!mux) {
15442      return new Error('Failed to find demuxer by probing fragment data');
15443    }
15444    // so let's check that current remuxer and demuxer are still valid
15445    const demuxer = this.demuxer;
15446    const remuxer = this.remuxer;
15447    const Remuxer = mux.remux;
15448    const Demuxer = mux.demux;
15449    if (!remuxer || !(remuxer instanceof Remuxer)) {
15450      this.remuxer = new Remuxer(observer, config, typeSupported, vendor);
15451    }
15452    if (!demuxer || !(demuxer instanceof Demuxer)) {
15453      this.demuxer = new Demuxer(observer, config, typeSupported);
15454      this.probe = Demuxer.probe;
15455    }
15456  }
15457  needsProbing(discontinuity, trackSwitch) {
15458    // in case of continuity change, or track switch
15459    // we might switch from content type (AAC container to TS container, or TS to fmp4 for example)
15460    return !this.demuxer || !this.remuxer || discontinuity || trackSwitch;
15461  }
15462  getDecrypter() {
15463    let decrypter = this.decrypter;
15464    if (!decrypter) {
15465      decrypter = this.decrypter = new Decrypter(this.config);
15466    }
15467    return decrypter;
15468  }
15469}
15470function getEncryptionType(data, decryptData) {
15471  let encryptionType = null;
15472  if (data.byteLength > 0 && (decryptData == null ? void 0 : decryptData.key) != null && decryptData.iv !== null && decryptData.method != null) {
15473    encryptionType = decryptData;
15474  }
15475  return encryptionType;
15476}
15477const emptyResult = chunkMeta => ({
15478  remuxResult: {},
15479  chunkMeta
15480});
15481function isPromise(p) {
15482  return 'then' in p && p.then instanceof Function;
15483}
15484class TransmuxConfig {
15485  constructor(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts) {
15486    this.audioCodec = void 0;
15487    this.videoCodec = void 0;
15488    this.initSegmentData = void 0;
15489    this.duration = void 0;
15490    this.defaultInitPts = void 0;
15491    this.audioCodec = audioCodec;
15492    this.videoCodec = videoCodec;
15493    this.initSegmentData = initSegmentData;
15494    this.duration = duration;
15495    this.defaultInitPts = defaultInitPts || null;
15496  }
15497}
15498class TransmuxState {
15499  constructor(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange) {
15500    this.discontinuity = void 0;
15501    this.contiguous = void 0;
15502    this.accurateTimeOffset = void 0;
15503    this.trackSwitch = void 0;
15504    this.timeOffset = void 0;
15505    this.initSegmentChange = void 0;
15506    this.discontinuity = discontinuity;
15507    this.contiguous = contiguous;
15508    this.accurateTimeOffset = accurateTimeOffset;
15509    this.trackSwitch = trackSwitch;
15510    this.timeOffset = timeOffset;
15511    this.initSegmentChange = initSegmentChange;
15512  }
15513}
15514
15515var eventemitter3 = {exports: {}};
15516
vendor: 9,399 bytes, lines 15517-15852
15517(function (module) {
15518
15519	var has = Object.prototype.hasOwnProperty
15520	  , prefix = '~';
15521
15522	/**
15523	 * Constructor to create a storage for our `EE` objects.
15524	 * An `Events` instance is a plain object whose properties are event names.
15525	 *
15526	 * @constructor
15527	 * @private
15528	 */
15529	function Events() {}
15530
15531	//
15532	// We try to not inherit from `Object.prototype`. In some engines creating an
15533	// instance in this way is faster than calling `Object.create(null)` directly.
15534	// If `Object.create(null)` is not supported we prefix the event names with a
15535	// character to make sure that the built-in object properties are not
15536	// overridden or used as an attack vector.
15537	//
15538	if (Object.create) {
15539	  Events.prototype = Object.create(null);
15540
15541	  //
15542	  // This hack is needed because the `__proto__` property is still inherited in
15543	  // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5.
15544	  //
15545	  if (!new Events().__proto__) prefix = false;
15546	}
15547
15548	/**
15549	 * Representation of a single event listener.
15550	 *
15551	 * @param {Function} fn The listener function.
15552	 * @param {*} context The context to invoke the listener with.
15553	 * @param {Boolean} [once=false] Specify if the listener is a one-time listener.
15554	 * @constructor
15555	 * @private
15556	 */
15557	function EE(fn, context, once) {
15558	  this.fn = fn;
15559	  this.context = context;
15560	  this.once = once || false;
15561	}
15562
15563	/**
15564	 * Add a listener for a given event.
15565	 *
15566	 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
15567	 * @param {(String|Symbol)} event The event name.
15568	 * @param {Function} fn The listener function.
15569	 * @param {*} context The context to invoke the listener with.
15570	 * @param {Boolean} once Specify if the listener is a one-time listener.
15571	 * @returns {EventEmitter}
15572	 * @private
15573	 */
15574	function addListener(emitter, event, fn, context, once) {
15575	  if (typeof fn !== 'function') {
15576	    throw new TypeError('The listener must be a function');
15577	  }
15578
15579	  var listener = new EE(fn, context || emitter, once)
15580	    , evt = prefix ? prefix + event : event;
15581
15582	  if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
15583	  else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
15584	  else emitter._events[evt] = [emitter._events[evt], listener];
15585
15586	  return emitter;
15587	}
15588
15589	/**
15590	 * Clear event by name.
15591	 *
15592	 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
15593	 * @param {(String|Symbol)} evt The Event name.
15594	 * @private
15595	 */
15596	function clearEvent(emitter, evt) {
15597	  if (--emitter._eventsCount === 0) emitter._events = new Events();
15598	  else delete emitter._events[evt];
15599	}
15600
15601	/**
15602	 * Minimal `EventEmitter` interface that is molded against the Node.js
15603	 * `EventEmitter` interface.
15604	 *
15605	 * @constructor
15606	 * @public
15607	 */
15608	function EventEmitter() {
15609	  this._events = new Events();
15610	  this._eventsCount = 0;
15611	}
15612
15613	/**
15614	 * Return an array listing the events for which the emitter has registered
15615	 * listeners.
15616	 *
15617	 * @returns {Array}
15618	 * @public
15619	 */
15620	EventEmitter.prototype.eventNames = function eventNames() {
15621	  var names = []
15622	    , events
15623	    , name;
15624
15625	  if (this._eventsCount === 0) return names;
15626
15627	  for (name in (events = this._events)) {
15628	    if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
15629	  }
15630
15631	  if (Object.getOwnPropertySymbols) {
15632	    return names.concat(Object.getOwnPropertySymbols(events));
15633	  }
15634
15635	  return names;
15636	};
15637
15638	/**
15639	 * Return the listeners registered for a given event.
15640	 *
15641	 * @param {(String|Symbol)} event The event name.
15642	 * @returns {Array} The registered listeners.
15643	 * @public
15644	 */
15645	EventEmitter.prototype.listeners = function listeners(event) {
15646	  var evt = prefix ? prefix + event : event
15647	    , handlers = this._events[evt];
15648
15649	  if (!handlers) return [];
15650	  if (handlers.fn) return [handlers.fn];
15651
15652	  for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
15653	    ee[i] = handlers[i].fn;
15654	  }
15655
15656	  return ee;
15657	};
15658
15659	/**
15660	 * Return the number of listeners listening to a given event.
15661	 *
15662	 * @param {(String|Symbol)} event The event name.
15663	 * @returns {Number} The number of listeners.
15664	 * @public
15665	 */
15666	EventEmitter.prototype.listenerCount = function listenerCount(event) {
15667	  var evt = prefix ? prefix + event : event
15668	    , listeners = this._events[evt];
15669
15670	  if (!listeners) return 0;
15671	  if (listeners.fn) return 1;
15672	  return listeners.length;
15673	};
15674
15675	/**
15676	 * Calls each of the listeners registered for a given event.
15677	 *
15678	 * @param {(String|Symbol)} event The event name.
15679	 * @returns {Boolean} `true` if the event had listeners, else `false`.
15680	 * @public
15681	 */
15682	EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
15683	  var evt = prefix ? prefix + event : event;
15684
15685	  if (!this._events[evt]) return false;
15686
15687	  var listeners = this._events[evt]
15688	    , len = arguments.length
15689	    , args
15690	    , i;
15691
15692	  if (listeners.fn) {
15693	    if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
15694
15695	    switch (len) {
15696	      case 1: return listeners.fn.call(listeners.context), true;
15697	      case 2: return listeners.fn.call(listeners.context, a1), true;
15698	      case 3: return listeners.fn.call(listeners.context, a1, a2), true;
15699	      case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
15700	      case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
15701	      case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
15702	    }
15703
15704	    for (i = 1, args = new Array(len -1); i < len; i++) {
15705	      args[i - 1] = arguments[i];
15706	    }
15707
15708	    listeners.fn.apply(listeners.context, args);
15709	  } else {
15710	    var length = listeners.length
15711	      , j;
15712
15713	    for (i = 0; i < length; i++) {
15714	      if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
15715
15716	      switch (len) {
15717	        case 1: listeners[i].fn.call(listeners[i].context); break;
15718	        case 2: listeners[i].fn.call(listeners[i].context, a1); break;
15719	        case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
15720	        case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
15721	        default:
15722	          if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
15723	            args[j - 1] = arguments[j];
15724	          }
15725
15726	          listeners[i].fn.apply(listeners[i].context, args);
15727	      }
15728	    }
15729	  }
15730
15731	  return true;
15732	};
15733
15734	/**
15735	 * Add a listener for a given event.
15736	 *
15737	 * @param {(String|Symbol)} event The event name.
15738	 * @param {Function} fn The listener function.
15739	 * @param {*} [context=this] The context to invoke the listener with.
15740	 * @returns {EventEmitter} `this`.
15741	 * @public
15742	 */
15743	EventEmitter.prototype.on = function on(event, fn, context) {
15744	  return addListener(this, event, fn, context, false);
15745	};
15746
15747	/**
15748	 * Add a one-time listener for a given event.
15749	 *
15750	 * @param {(String|Symbol)} event The event name.
15751	 * @param {Function} fn The listener function.
15752	 * @param {*} [context=this] The context to invoke the listener with.
15753	 * @returns {EventEmitter} `this`.
15754	 * @public
15755	 */
15756	EventEmitter.prototype.once = function once(event, fn, context) {
15757	  return addListener(this, event, fn, context, true);
15758	};
15759
15760	/**
15761	 * Remove the listeners of a given event.
15762	 *
15763	 * @param {(String|Symbol)} event The event name.
15764	 * @param {Function} fn Only remove the listeners that match this function.
15765	 * @param {*} context Only remove the listeners that have this context.
15766	 * @param {Boolean} once Only remove one-time listeners.
15767	 * @returns {EventEmitter} `this`.
15768	 * @public
15769	 */
15770	EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
15771	  var evt = prefix ? prefix + event : event;
15772
15773	  if (!this._events[evt]) return this;
15774	  if (!fn) {
15775	    clearEvent(this, evt);
15776	    return this;
15777	  }
15778
15779	  var listeners = this._events[evt];
15780
15781	  if (listeners.fn) {
15782	    if (
15783	      listeners.fn === fn &&
15784	      (!once || listeners.once) &&
15785	      (!context || listeners.context === context)
15786	    ) {
15787	      clearEvent(this, evt);
15788	    }
15789	  } else {
15790	    for (var i = 0, events = [], length = listeners.length; i < length; i++) {
15791	      if (
15792	        listeners[i].fn !== fn ||
15793	        (once && !listeners[i].once) ||
15794	        (context && listeners[i].context !== context)
15795	      ) {
15796	        events.push(listeners[i]);
15797	      }
15798	    }
15799
15800	    //
15801	    // Reset the array, or remove it completely if we have no more listeners.
15802	    //
15803	    if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
15804	    else clearEvent(this, evt);
15805	  }
15806
15807	  return this;
15808	};
15809
15810	/**
15811	 * Remove all listeners, or those of the specified event.
15812	 *
15813	 * @param {(String|Symbol)} [event] The event name.
15814	 * @returns {EventEmitter} `this`.
15815	 * @public
15816	 */
15817	EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
15818	  var evt;
15819
15820	  if (event) {
15821	    evt = prefix ? prefix + event : event;
15822	    if (this._events[evt]) clearEvent(this, evt);
15823	  } else {
15824	    this._events = new Events();
15825	    this._eventsCount = 0;
15826	  }
15827
15828	  return this;
15829	};
15830
15831	//
15832	// Alias methods names because people roll like that.
15833	//
15834	EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
15835	EventEmitter.prototype.addListener = EventEmitter.prototype.on;
15836
15837	//
15838	// Expose the prefix.
15839	//
15840	EventEmitter.prefixed = prefix;
15841
15842	//
15843	// Allow `EventEmitter` to be imported as module namespace.
15844	//
15845	EventEmitter.EventEmitter = EventEmitter;
15846
15847	//
15848	// Expose the module.
15849	//
15850	{
15851	  module.exports = EventEmitter;
15852	}
15852 
15853} (eventemitter3));
15854
15855var eventemitter3Exports = eventemitter3.exports;
15856var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
15857
15858class TransmuxerInterface {
15859  constructor(hls, id, onTransmuxComplete, onFlush) {
15860    this.error = null;
15861    this.hls = void 0;
15862    this.id = void 0;
15863    this.observer = void 0;
15864    this.frag = null;
15865    this.part = null;
15866    this.useWorker = void 0;
15867    this.workerContext = null;
15868    this.onwmsg = void 0;
15869    this.transmuxer = null;
15870    this.onTransmuxComplete = void 0;
15871    this.onFlush = void 0;
15872    const config = hls.config;
15873    this.hls = hls;
15874    this.id = id;
15875    this.useWorker = !!config.enableWorker;
15876    this.onTransmuxComplete = onTransmuxComplete;
15877    this.onFlush = onFlush;
15878    const forwardMessage = (ev, data) => {
15879      data = data || {};
15880      data.frag = this.frag;
15881      data.id = this.id;
15882      if (ev === Events.ERROR) {
15883        this.error = data.error;
15884      }
15885      this.hls.trigger(ev, data);
15886    };
15887
15888    // forward events to main thread
15889    this.observer = new EventEmitter();
15890    this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
15891    this.observer.on(Events.ERROR, forwardMessage);
15892    const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
15893      isTypeSupported: () => false
15894    };
15895    const m2tsTypeSupported = {
15896      mpeg: MediaSource.isTypeSupported('audio/mpeg'),
15897      mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
15898      ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"') 
15899    };
15900    if (this.useWorker && typeof Worker !== 'undefined') {
15901      const canCreateWorker = config.workerPath || hasUMDWorker();
15902      if (canCreateWorker) {
15903        try {
15904          if (config.workerPath) {
15905            logger.log(`loading Web Worker ${config.workerPath} for "${id}"`);
15906            this.workerContext = loadWorker(config.workerPath);
15907          } else {
15908            logger.log(`injecting Web Worker for "${id}"`);
15909            this.workerContext = injectWorker();
15910          }
15911          this.onwmsg = event => this.onWorkerMessage(event);
15912          const {
15913            worker
15914          } = this.workerContext;
15915          worker.addEventListener('message', this.onwmsg);
15916          worker.onerror = event => {
15917            const error = new Error(`${event.message}  (${event.filename}:${event.lineno})`);
15918            config.enableWorker = false;
15919            logger.warn(`Error in "${id}" Web Worker, fallback to inline`);
15920            this.hls.trigger(Events.ERROR, {
15921              type: ErrorTypes.OTHER_ERROR,
15922              details: ErrorDetails.INTERNAL_EXCEPTION,
15923              fatal: false,
15924              event: 'demuxerWorker',
15925              error
15926            });
15927          };
15928          worker.postMessage({
15929            cmd: 'init',
15930            typeSupported: m2tsTypeSupported,
15931            vendor: '',
15932            id: id,
15933            config: JSON.stringify(config)
15934          });
15935        } catch (err) {
15936          logger.warn(`Error setting up "${id}" Web Worker, fallback to inline`, err);
15937          this.resetWorker();
15938          this.error = null;
15939          this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id);
15940        }
15941        return;
15942      }
15943    }
15944    this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id);
15945  }
15946  resetWorker() {
15947    if (this.workerContext) {
15948      const {
15949        worker,
15950        objectURL
15951      } = this.workerContext;
15952      if (objectURL) {
15953        // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
15954        self.URL.revokeObjectURL(objectURL);
15955      }
15956      worker.removeEventListener('message', this.onwmsg);
15957      worker.onerror = null;
15958      worker.terminate();
15959      this.workerContext = null;
15960    }
15961  }
15962  destroy() {
15963    if (this.workerContext) {
15964      this.resetWorker();
15965      this.onwmsg = undefined;
15966    } else {
15967      const transmuxer = this.transmuxer;
15968      if (transmuxer) {
15969        transmuxer.destroy();
15970        this.transmuxer = null;
15971      }
15972    }
15973    const observer = this.observer;
15974    if (observer) {
15975      observer.removeAllListeners();
15976    }
15977    this.frag = null;
15978    // @ts-ignore
15979    this.observer = null;
15980    // @ts-ignore
15981    this.hls = null;
15982  }
15983  push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS) {
15984    var _frag$initSegment, _lastFrag$initSegment;
15985    chunkMeta.transmuxing.start = self.performance.now();
15986    const {
15987      transmuxer
15988    } = this;
15989    const timeOffset = part ? part.start : frag.start;
15990    // TODO: push "clear-lead" decrypt data for unencrypted fragments in streams with encrypted ones
15991    const decryptdata = frag.decryptdata;
15992    const lastFrag = this.frag;
15993    const discontinuity = !(lastFrag && frag.cc === lastFrag.cc);
15994    const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level);
15995    const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1;
15996    const partDiff = this.part ? chunkMeta.part - this.part.index : -1;
15997    const progressive = snDiff === 0 && chunkMeta.id > 1 && chunkMeta.id === (lastFrag == null ? void 0 : lastFrag.stats.chunkCount);
15998    const contiguous = !trackSwitch && (snDiff === 1 || snDiff === 0 && (partDiff === 1 || progressive && partDiff <= 0));
15999    const now = self.performance.now();
16000    if (trackSwitch || snDiff || frag.stats.parsing.start === 0) {
16001      frag.stats.parsing.start = now;
16002    }
16003    if (part && (partDiff || !contiguous)) {
16004      part.stats.parsing.start = now;
16005    }
16006    const initSegmentChange = !(lastFrag && ((_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.url) === ((_lastFrag$initSegment = lastFrag.initSegment) == null ? void 0 : _lastFrag$initSegment.url));
16007    const state = new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange);
16008    if (!contiguous || discontinuity || initSegmentChange) {
16009      logger.log(`[transmuxer-interface, ${frag.type}]: Starting new transmux session for sn: ${chunkMeta.sn} p: ${chunkMeta.part} level: ${chunkMeta.level} id: ${chunkMeta.id}
16010        discontinuity: ${discontinuity}
16011        trackSwitch: ${trackSwitch}
16012        contiguous: ${contiguous}
16013        accurateTimeOffset: ${accurateTimeOffset}
16014        timeOffset: ${timeOffset}
16015        initSegmentChange: ${initSegmentChange}`);
16016      const config = new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS);
16017      this.configureTransmuxer(config);
16018    }
16019    this.frag = frag;
16020    this.part = part;
16021
16022    // Frags with sn of 'initSegment' are not transmuxed
16023    if (this.workerContext) {
16024      // post fragment payload as transferable objects for ArrayBuffer (no copy)
16025      this.workerContext.worker.postMessage({
16026        cmd: 'demux',
16027        data,
16028        decryptdata,
16029        chunkMeta,
16030        state
16031      }, data instanceof ArrayBuffer ? [data] : []);
16032    } else if (transmuxer) {
16033      const transmuxResult = transmuxer.push(data, decryptdata, chunkMeta, state);
16034      if (isPromise(transmuxResult)) {
16035        transmuxer.async = true;
16036        transmuxResult.then(data => {
16037          this.handleTransmuxComplete(data);
16038        }).catch(error => {
16039          this.transmuxerError(error, chunkMeta, 'transmuxer-interface push error');
16040        });
16041      } else {
16042        transmuxer.async = false;
16043        this.handleTransmuxComplete(transmuxResult);
16044      }
16045    }
16046  }
16047  flush(chunkMeta) {
16048    chunkMeta.transmuxing.start = self.performance.now();
16049    const {
16050      transmuxer
16051    } = this;
16052    if (this.workerContext) {
16053      this.workerContext.worker.postMessage({
16054        cmd: 'flush',
16055        chunkMeta
16056      });
16057    } else if (transmuxer) {
16058      let transmuxResult = transmuxer.flush(chunkMeta);
16059      const asyncFlush = isPromise(transmuxResult);
16060      if (asyncFlush || transmuxer.async) {
16061        if (!isPromise(transmuxResult)) {
16062          transmuxResult = Promise.resolve(transmuxResult);
16063        }
16064        transmuxResult.then(data => {
16065          this.handleFlushResult(data, chunkMeta);
16066        }).catch(error => {
16067          this.transmuxerError(error, chunkMeta, 'transmuxer-interface flush error');
16068        });
16069      } else {
16070        this.handleFlushResult(transmuxResult, chunkMeta);
16071      }
16072    }
16073  }
16074  transmuxerError(error, chunkMeta, reason) {
16075    if (!this.hls) {
16076      return;
16077    }
16078    this.error = error;
16079    this.hls.trigger(Events.ERROR, {
16080      type: ErrorTypes.MEDIA_ERROR,
16081      details: ErrorDetails.FRAG_PARSING_ERROR,
16082      chunkMeta,
16083      frag: this.frag || undefined,
16084      fatal: false,
16085      error,
16086      err: error,
16087      reason
16088    });
16089  }
16090  handleFlushResult(results, chunkMeta) {
16091    results.forEach(result => {
16092      this.handleTransmuxComplete(result);
16093    });
16094    this.onFlush(chunkMeta);
16095  }
16096  onWorkerMessage(event) {
16097    const data = event.data;
16098    if (!(data != null && data.event)) {
16099      logger.warn(`worker message received with no ${data ? 'event name' : 'data'}`);
16100      return;
16101    }
16102    const hls = this.hls;
16103    if (!this.hls) {
16104      return;
16105    }
16106    switch (data.event) {
16107      case 'init':
16108        {
16109          var _this$workerContext;
16110          const objectURL = (_this$workerContext = this.workerContext) == null ? void 0 : _this$workerContext.objectURL;
16111          if (objectURL) {
16112            // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
16113            self.URL.revokeObjectURL(objectURL);
16114          }
16115          break;
16116        }
16117      case 'transmuxComplete':
16118        {
16119          this.handleTransmuxComplete(data.data);
16120          break;
16121        }
16122      case 'flush':
16123        {
16124          this.onFlush(data.data);
16125          break;
16126        }
16127
16128      // pass logs from the worker thread to the main logger
16129      case 'workerLog':
16130        if (logger[data.data.logType]) {
16131          logger[data.data.logType](data.data.message);
16132        }
16133        break;
16134      default:
16135        {
16136          data.data = data.data || {};
16137          data.data.frag = this.frag;
16138          data.data.id = this.id;
16139          hls.trigger(data.event, data.data);
16140          break;
16141        }
16142    }
16143  }
16144  configureTransmuxer(config) {
16145    const {
16146      transmuxer
16147    } = this;
16148    if (this.workerContext) {
16149      this.workerContext.worker.postMessage({
16150        cmd: 'configure',
16151        config
16152      });
16153    } else if (transmuxer) {
16154      transmuxer.configure(config);
16155    }
16156  }
16157  handleTransmuxComplete(result) {
16158    result.chunkMeta.transmuxing.end = self.performance.now();
16159    this.onTransmuxComplete(result);
16160  }
16161}
16162
16163function subtitleOptionsIdentical(trackList1, trackList2) {
16164  if (trackList1.length !== trackList2.length) {
16165    return false;
16166  }
16167  for (let i = 0; i < trackList1.length; i++) {
16168    if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) {
16169      return false;
16170    }
16171  }
16172  return true;
16173}
16174function mediaAttributesIdentical(attrs1, attrs2, customAttributes) {
16175  // Media options with the same rendition ID must be bit identical
16176  const stableRenditionId = attrs1['STABLE-RENDITION-ID'];
16177  if (stableRenditionId && !customAttributes) {
16178    return stableRenditionId === attrs2['STABLE-RENDITION-ID'];
16179  }
16180  // When rendition ID is not present, compare attributes
16181  return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute =>
16181 attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]);
16182}
16183function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) {
16184  return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase());
16185}
16186
16187const TICK_INTERVAL$2 = 100; // how often to tick in ms
16188
16189class AudioStreamController extends BaseStreamController {
16190  constructor(hls, fragmentTracker, keyLoader) {
16191    super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
16192    this.videoBuffer = null;
16193    this.videoTrackCC = -1;
16194    this.waitingVideoCC = -1;
16195    this.bufferedTrack = null;
16196    this.switchingTrack = null;
16197    this.trackId = -1;
16198    this.waitingData = null;
16199    this.mainDetails = null;
16200    this.flushing = false;
16201    this.bufferFlushed = false;
16202    this.cachedTrackLoadedData = null;
16203    this._registerListeners();
16204  }
16205  onHandlerDestroying() {
16206    this._unregisterListeners();
16207    super.onHandlerDestroying();
16208    this.mainDetails = null;
16209    this.bufferedTrack = null;
16210    this.switchingTrack = null;
16211  }
16212  _registerListeners() {
16213    const {
16214      hls
16215    } = this;
16216    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16217    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16218    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16219    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16220    hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16221    hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16222    hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
16223    hls.on(Events.ERROR, this.onError, this);
16224    hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
16225    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
16226    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16227    hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
16228    hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
16229    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16230  }
16231  _unregisterListeners() {
16232    const {
16233      hls
16234    } = this;
16235    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
16236    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
16237    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16238    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16239    hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16240    hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16241    hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
16242    hls.off(Events.ERROR, this.onError, this);
16243    hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
16244    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
16245    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16246    hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
16247    hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
16248    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16249  }
16250
16251  // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value
16252  onInitPtsFound(event, {
16253    frag,
16254    id,
16255    initPTS,
16256    timescale
16257  }) {
16258    // Always update the new INIT PTS
16259    // Can change due level switch
16260    if (id === 'main') {
16261      const cc = frag.cc;
16262      this.initPTS[frag.cc] = {
16263        baseTime: initPTS,
16264        timescale
16265      };
16266      this.log(`InitPTS for cc: ${cc} found from main: ${initPTS}`);
16267      this.videoTrackCC = cc;
16268      // If we are waiting, tick immediately to unblock audio fragment transmuxing
16269      if (this.state === State.WAITING_INIT_PTS) {
16270        this.tick();
16271      }
16272    }
16273  }
16274  startLoad(startPosition) {
16275    if (!this.levels) {
16276      this.startPosition = startPosition;
16277      this.state = State.STOPPED;
16278      return;
16279    }
16280    const lastCurrentTime = this.lastCurrentTime;
16281    this.stopLoad();
16282    this.setInterval(TICK_INTERVAL$2);
16283    if (lastCurrentTime > 0 && startPosition === -1) {
16284      this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);
16285      startPosition = lastCurrentTime;
16286      this.state = State.IDLE;
16287    } else {
16288      this.loadedmetadata = false;
16289      this.state = State.WAITING_TRACK;
16290    }
16291    this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition;
16292    this.tick();
16293  }
16294  doTick() {
16295    switch (this.state) {
16296      case State.IDLE:
16297        this.doTickIdle();
16298        break;
16299      case State.WAITING_TRACK:
16300        {
16301          var _levels$trackId;
16302          const {
16303            levels,
16304            trackId
16305          } = this;
16306          const details = levels == null ? void 0 : (_levels$trackId = levels[trackId]) == null ? void 0 : _levels$trackId.details;
16307          if (details) {
16308            if (this.waitForCdnTuneIn(details)) {
16309              break;
16310            }
16311            this.state = State.WAITING_INIT_PTS;
16312          }
16313          break;
16314        }
16315      case State.FRAG_LOADING_WAITING_RETRY:
16316        {
16317          var _this$media;
16318          const now = performance.now();
16319          const retryDate = this.retryDate;
16320          // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading
16321          if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) {
16322            const {
16323              levels,
16324              trackId
16325            } = this;
16326            this.log('RetryDate reached, switch back to IDLE state');
16327            this.resetStartWhenNotLoaded((levels == null ? void 0 : levels[trackId]) || null);
16328            this.state = State.IDLE;
16329          }
16330          break;
16331        }
16332      case State.WAITING_INIT_PTS:
16333        {
16334          // Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS
16335          const waitingData = this.waitingData;
16336          if (waitingData) {
16337            const {
16338              frag,
16339              part,
16340              cache,
16341              complete
16342            } = waitingData;
16343            if (this.initPTS[frag.cc] !== undefined) {
16344              this.waitingData = null;
16345              this.waitingVideoCC = -1;
16346              this.state = State.FRAG_LOADING;
16347              const payload = cache.flush();
16348              const data = {
16349                frag,
16350                part,
16351                payload,
16352                networkDetails: null
16353              };
16354              this._handleFragmentLoadProgress(data);
16355              if (complete) {
16356                super._handleFragmentLoadComplete(data);
16357              }
16358            } else if (this.videoTrackCC !== this.waitingVideoCC) {
16359              // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
16360              this.log(`Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${this.videoTrackCC}`);
16361              this.clearWaitingFragment();
16362            } else {
16363              // Drop waiting fragment if an earlier fragment is needed
16364              const pos = this.getLoadPosition();
16365              const bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, pos, this.config.maxBufferHole);
16366              const waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, this.config.maxFragLookUpTolerance, frag);
16367              if (waitingFragmentAtPosition < 0) {
16368                this.log(`Waiting fragment cc (${frag.cc}) @ ${frag.start} cancelled because another fragment at ${bufferInfo.end} is needed`);
16369                this.clearWaitingFragment();
16370              }
16371            }
16372          } else {
16373            this.state = State.IDLE;
16374          }
16375        }
16376    }
16377    this.onTickEnd();
16378  }
16379  clearWaitingFragment() {
16380    const waitingData = this.waitingData;
16381    if (waitingData) {
16382      this.fragmentTracker.removeFragment(waitingData.frag);
16383      this.waitingData = null;
16384      this.waitingVideoCC = -1;
16385      this.state = State.IDLE;
16386    }
16387  }
16388  resetLoadingState() {
16389    this.clearWaitingFragment();
16390    super.resetLoadingState();
16391  }
16392  onTickEnd() {
16393    const {
16394      media
16395    } = this;
16396    if (!(media != null && media.readyState)) {
16397      // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0)
16398      return;
16399    }
16400    this.lastCurrentTime = media.currentTime;
16401  }
16402  doTickIdle() {
16403    const {
16404      hls,
16405      levels,
16406      media,
16407      trackId
16408    } = this;
16409    const config = hls.config;
16410
16411    // 1. if video not attached AND
16412    //    start fragment already requested OR start frag prefetch not enabled
16413    // 2. if tracks or track not loaded and selected
16414    // then exit loop
16415    // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
16416    if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
16417      return;
16418    }
16419    const levelInfo = levels[trackId];
16420    const trackDetails = levelInfo.details;
16421    if (!trackDetails || trackDetails.live && this.levelLastLoaded !== levelInfo || this.waitForCdnTuneIn(trackDetails)) {
16422      this.state = State.WAITING_TRACK;
16423      return;
16424    }
16425    const bufferable = this.mediaBuffer ? this.mediaBuffer : this.media;
16426    if (this.bufferFlushed && bufferable) {
16427      this.bufferFlushed = false;
16428      this.afterBufferFlushed(bufferable, ElementaryStreamTypes.AUDIO, PlaylistLevelType.AUDIO);
16429    }
16430    const bufferInfo = this.getFwdBufferInfo(bufferable, PlaylistLevelType.AUDIO);
16431    if (bufferInfo === null) {
16432      return;
16433    }
16434    const {
16435      bufferedTrack,
16436      switchingTrack
16437    } = this;
16438    if (!switchingTrack && this._streamEnded(bufferInfo, trackDetails)) {
16439      hls.trigger(Events.BUFFER_EOS, {
16440        type: 'audio'
16441      });
16442      this.state = State.ENDED;
16443      return;
16444    }
16445    const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
16446    const bufferLen = bufferInfo.len;
16447    const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len);
16448    const fragments = trackDetails.fragments;
16449    const start = fragments[0].start;
16450    let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end;
16451    if (switchingTrack && media) {
16452      const pos = this.getLoadPosition();
16453      // STABLE
16454      if (bufferedTrack && !mediaAttributesIdentical(switchingTrack.attrs, bufferedTrack.attrs)) {
16455        targetBufferTime = pos;
16456      }
16457      // if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime
16458      if (trackDetails.PTSKnown && pos < start) {
16459        // if everything is buffered from pos to start or if audio buffer upfront, let's seek to start
16460        if (bufferInfo.end > start || bufferInfo.nextStart) {
16461          this.log('Alt audio track ahead of main track, seek to start of alt audio track');
16462          media.currentTime = start + 0.05;
16463        }
16464      }
16465    }
16466
16467    // if buffer length is less than maxBufLen, or near the end, find a fragment to load
16468    if (bufferLen >= maxBufLen && !switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) {
16469      return;
16470    }
16471    let frag = this.getNextFragment(targetBufferTime, trackDetails);
16472    let atGap = false;
16473    // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
16474    if (frag && this.isLoopLoading(frag, targetBufferTime)) {
16475      atGap = !!frag.gap;
16476      frag = this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
16477    }
16478    if (!frag) {
16479      this.bufferFlushed = true;
16480      return;
16481    }
16482
16483    // Buffer audio up to one target duration ahead of main buffer
16484    const atBufferSyncLimit = mainBufferInfo && frag.start > mainBufferInfo.end + trackDetails.targetduration;
16485    if (atBufferSyncLimit ||
16486    // Or wait for main buffer after buffing some audio
16487    !(mainBufferInfo != null && mainBufferInfo.len) && bufferInfo.len) {
16488      // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
16489      const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN);
16490      if (mainFrag === null) {
16491        return;
16492      }
16493      // Bridge gaps in main buffer
16494      atGap || (atGap = !!mainFrag.gap || !!atBufferSyncLimit && mainBufferInfo.len === 0);
16495      if (atBufferSyncLimit && !atGap || atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
16496        return;
16497      }
16498    }
16499    this.loadFragment(frag, levelInfo, targetBufferTime);
16500  }
16501  getMaxBufferLength(mainBufferLength) {
16502    const maxConfigBuffer = super.getMaxBufferLength();
16503    if (!mainBufferLength) {
16504      return maxConfigBuffer;
16505    }
16506    return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength);
16507  }
16508  onMediaDetaching() {
16509    this.videoBuffer = null;
16510    this.bufferFlushed = this.flushing = false;
16511    super.onMediaDetaching();
16512  }
16513  onAudioTracksUpdated(event, {
16514    audioTracks
16515  }) {
16516    // Reset tranxmuxer is essential for large context switches (Content Steering)
16517    this.resetTransmuxer();
16518    this.levels = audioTracks.map(mediaPlaylist => new Level(mediaPlaylist));
16519  }
16520  onAudioTrackSwitching(event, data) {
16521    // if any URL found on new audio track, it is an alternate audio track
16522    const altAudio = !!data.url;
16523    this.trackId = data.id;
16524    const {
16525      fragCurrent
16526    } = this;
16527    if (fragCurrent) {
16528      fragCurrent.abortRequests();
16529      this.removeUnbufferedFrags(fragCurrent.start);
16530    }
16531    this.resetLoadingState();
16532    // destroy useless transmuxer when switching audio to main
16533    if (!altAudio) {
16534      this.resetTransmuxer();
16535    } else {
16536      // switching to audio track, start timer if not already started
16537      this.setInterval(TICK_INTERVAL$2);
16538    }
16539
16540    // should we switch tracks ?
16541    if (altAudio) {
16542      this.switchingTrack = data;
16543      // main audio track are handled by stream-controller, just do something if switching to alt audio track
16544      this.state = State.IDLE;
16545      this.flushAudioIfNeeded(data);
16546    } else {
16547      this.switchingTrack = null;
16548      this.bufferedTrack = data;
16549      this.state = State.STOPPED;
16550    }
16551    this.tick();
16552  }
16553  onManifestLoading() {
16554    this.fragmentTracker.removeAllFragments();
16555    this.startPosition = this.lastCurrentTime = 0;
16556    this.bufferFlushed = this.flushing = false;
16557    this.levels = this.mainDetails = this.waitingData = this.bufferedTrack = this.cachedTrackLoadedData = this.switchingTrack = null;
16558    this.startFragRequested = false;
16559    this.trackId = this.videoTrackCC = this.waitingVideoCC = -1;
16560  }
16561  onLevelLoaded(event, data) {
16562    this.mainDetails = data.details;
16563    if (this.cachedTrackLoadedData !== null) {
16564      this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData);
16565      this.cachedTrackLoadedData = null;
16566    }
16567  }
16568  onAudioTrackLoaded(event, data) {
16569    var _track$details;
16570    if (this.mainDetails == null) {
16571      this.cachedTrackLoadedData = data;
16572      return;
16573    }
16574    const {
16575      levels
16576    } = this;
16577    const {
16578      details: newDetails,
16579      id: trackId
16580    } = data;
16581    if (!levels) {
16582      this.warn(`Audio tracks were reset while loading level ${trackId}`);
16583      return;
16584    }
16585    this.log(`Audio track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''},duration:${newDetails.totalduration}`);
16586    const track = levels[trackId];
16587    let sliding = 0;
16588    if (newDetails.live || (_track$details = track.details) != null && _track$details.live) {
16589      this.checkLiveUpdate(newDetails);
16590      const mainDetails = this.mainDetails;
16591      if (newDetails.deltaUpdateFailed || !mainDetails) {
16592        return;
16593      }
16594      if (!track.details && newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) {
16595        // Make sure our audio rendition is aligned with the "main" rendition, using
16596        // pdt as our reference times.
16597        alignMediaPlaylistByPDT(newDetails, mainDetails);
16598        sliding = newDetails.fragments[0].start;
16599      } else {
16600        var _this$levelLastLoaded;
16601        sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
16602      }
16603    }
16604    track.details = newDetails;
16605    this.levelLastLoaded = track;
16606
16607    // compute start position if we are aligned with the main playlist
16608    if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) {
16609      this.setStartPosition(this.mainDetails || newDetails, sliding);
16610    }
16611    // only switch back to IDLE state if we were waiting for track to start downloading a new fragment
16612    if (this.state === State.WAITING_TRACK && !this.waitForCdnTuneIn(newDetails)) {
16613      this.state = State.IDLE;
16614    }
16615
16616    // trigger handler right now
16617    this.tick();
16618  }
16619  _handleFragmentLoadProgress(data) {
16620    var _frag$initSegment;
16621    const {
16622      frag,
16623      part,
16624      payload
16625    } = data;
16626    const {
16627      config,
16628      trackId,
16629      levels
16630    } = this;
16631    if (!levels) {
16632      this.warn(`Audio tracks were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);
16633      return;
16634    }
16635    const track = levels[trackId];
16636    if (!track) {
16637      this.warn('Audio track is undefined on fragment load progress');
16638      return;
16639    }
16640    const details = track.details;
16641    if (!details) {
16642      this.warn('Audio track details undefined on fragment load progress');
16643      this.removeUnbufferedFrags(frag.start);
16644      return;
16645    }
16646    const audioCodec = config.defaultAudioCodec || track.audioCodec || 'mp4a.40.2';
16647    let transmuxer = this.transmuxer;
16648    if (!transmuxer) {
16649      transmuxer = this.transmuxer = new TransmuxerInterface(this.hls, PlaylistLevelType.AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
16650    }
16651
16652    // Check if we have video initPTS
16653    // If not we need to wait for it
16654    const initPTS = this.initPTS[frag.cc];
16655    const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data;
16656    if (initPTS !== undefined) {
16657      // this.log(`Transmuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`);
16658      // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)
16659      const accurateTimeOffset = false; // details.PTSKnown || !details.live;
16660      const partIndex = part ? part.index : -1;
16661      const partial = partIndex !== -1;
16662      const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
16663      transmuxer.push(payload, initSegmentData, audioCodec, '', frag, part, details.totalduration, accurateTimeOff
16663set, chunkMeta, initPTS);
16664    } else {
16665      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}`);
16666      const {
16667        cache
16668      } = this.waitingData = this.waitingData || {
16669        frag,
16670        part,
16671        cache: new ChunkCache(),
16672        complete: false
16673      };
16674      cache.push(new Uint8Array(payload));
16675      this.waitingVideoCC = this.videoTrackCC;
16676      this.state = State.WAITING_INIT_PTS;
16677    }
16678  }
16679  _handleFragmentLoadComplete(fragLoadedData) {
16680    if (this.waitingData) {
16681      this.waitingData.complete = true;
16682      return;
16683    }
16684    super._handleFragmentLoadComplete(fragLoadedData);
16685  }
16686  onBufferReset( /* event: Events.BUFFER_RESET */
16687  ) {
16688    // reset reference to sourcebuffers
16689    this.mediaBuffer = this.videoBuffer = null;
16690    this.loadedmetadata = false;
16691  }
16692  onBufferCreated(event, data) {
16693    const audioTrack = data.tracks.audio;
16694    if (audioTrack) {
16695      this.mediaBuffer = audioTrack.buffer || null;
16696    }
16697    if (data.tracks.video) {
16698      this.videoBuffer = data.tracks.video.buffer || null;
16699    }
16700  }
16701  onFragBuffered(event, data) {
16702    const {
16703      frag,
16704      part
16705    } = data;
16706    if (frag.type !== PlaylistLevelType.AUDIO) {
16707      if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) {
16708        const bufferable = this.videoBuffer || this.media;
16709        if (bufferable) {
16710          const bufferedTimeRanges = BufferHelper.getBuffered(bufferable);
16711          if (bufferedTimeRanges.length) {
16712            this.loadedmetadata = true;
16713          }
16714        }
16715      }
16716      return;
16717    }
16718    if (this.fragContextChanged(frag)) {
16719      // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion
16720      // Avoid setting state back to IDLE or concluding the audio switch; otherwise, the switched-to track will not buffer
16721      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'}`);
16722      return;
16723    }
16724    if (frag.sn !== 'initSegment') {
16725      this.fragPrevious = frag;
16726      const track = this.switchingTrack;
16727      if (track) {
16728        this.bufferedTrack = track;
16729        this.switchingTrack = null;
16730        this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, track));
16731      }
16732    }
16733    this.fragBufferedComplete(frag, part);
16734  }
16735  onError(event, data) {
16736    var _data$context;
16737    if (data.fatal) {
16738      this.state = State.ERROR;
16739      return;
16740    }
16741    switch (data.details) {
16742      case ErrorDetails.FRAG_GAP:
16743      case ErrorDetails.FRAG_PARSING_ERROR:
16744      case ErrorDetails.FRAG_DECRYPT_ERROR:
16745      case ErrorDetails.FRAG_LOAD_ERROR:
16746      case ErrorDetails.FRAG_LOAD_TIMEOUT:
16747      case ErrorDetails.KEY_LOAD_ERROR:
16748      case ErrorDetails.KEY_LOAD_TIMEOUT:
16749        this.onFragmentOrKeyLoadError(PlaylistLevelType.AUDIO, data);
16750        break;
16751      case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
16752      case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
16753      case ErrorDetails.LEVEL_PARSING_ERROR:
16754        // in case of non fatal error while loading track, if not retrying to load track, switch back to IDLE
16755        if (!data.levelRetry && this.state === State.WAITING_TRACK && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.AUDIO_TRACK) {
16756          this.state = State.IDLE;
16757        }
16758        break;
16759      case ErrorDetails.BUFFER_APPEND_ERROR:
16760      case ErrorDetails.BUFFER_FULL_ERROR:
16761        if (!data.parent || data.parent !== 'audio') {
16762          return;
16763        }
16764        if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) {
16765          this.resetLoadingState();
16766          return;
16767        }
16768        if (this.reduceLengthAndFlushBuffer(data)) {
16769          this.bufferedTrack = null;
16770          super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
16771        }
16772        break;
16773      case ErrorDetails.INTERNAL_EXCEPTION:
16774        this.recoverWorkerError(data);
16775        break;
16776    }
16777  }
16778  onBufferFlushing(event, {
16779    type
16780  }) {
16781    if (type !== ElementaryStreamTypes.VIDEO) {
16782      this.flushing = true;
16783    }
16784  }
16785  onBufferFlushed(event, {
16786    type
16787  }) {
16788    if (type !== ElementaryStreamTypes.VIDEO) {
16789      this.flushing = false;
vendor: 4,293 bytes, lines 16790-16930
16790      this.bufferFlushed = true;
16791      if (this.state === State.ENDED) {
16792        this.state = State.IDLE;
16793      }
16794      const mediaBuffer = this.mediaBuffer || this.media;
16795      if (mediaBuffer) {
16796        this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.AUDIO);
16797        this.tick();
16798      }
16799    }
16800  }
16801  _handleTransmuxComplete(transmuxResult) {
16802    var _id3$samples;
16803    const id = 'audio';
16804    const {
16805      hls
16806    } = this;
16807    const {
16808      remuxResult,
16809      chunkMeta
16810    } = transmuxResult;
16811    const context = this.getCurrentContext(chunkMeta);
16812    if (!context) {
16813      this.resetWhenMissingContext(chunkMeta);
16814      return;
16815    }
16816    const {
16817      frag,
16818      part,
16819      level
16820    } = context;
16821    const {
16822      details
16823    } = level;
16824    const {
16825      audio,
16826      text,
16827      id3,
16828      initSegment
16829    } = remuxResult;
16830
16831    // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level.
16832    // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed.
16833    if (this.fragContextChanged(frag) || !details) {
16834      this.fragmentTracker.removeFragment(frag);
16835      return;
16836    }
16837    this.state = State.PARSING;
16838    if (this.switchingTrack && audio) {
16839      this.completeAudioSwitch(this.switchingTrack);
16840    }
16841    if (initSegment != null && initSegment.tracks) {
16842      const mapFragment = frag.initSegment || frag;
16843      this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta);
16844      hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
16845        frag: mapFragment,
16846        id,
16847        tracks: initSegment.tracks
16848      });
16849      // Only flush audio from old audio tracks when PTS is known on new audio track
16850    }
16851    if (audio) {
16852      const {
16853        startPTS,
16854        endPTS,
16855        startDTS,
16856        endDTS
16857      } = audio;
16858      if (part) {
16859        part.elementaryStreams[ElementaryStreamTypes.AUDIO] = {
16860          startPTS,
16861          endPTS,
16862          startDTS,
16863          endDTS
16864        };
16865      }
16866      frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);
16867      this.bufferFragmentData(audio, frag, part, chunkMeta);
16868    }
16869    if (id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) {
16870      const emittedID3 = _extends({
16871        id,
16872        frag,
16873        details
16874      }, id3);
16875      hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);
16876    }
16877    if (text) {
16878      const emittedText = _extends({
16879        id,
16880        frag,
16881        details
16882      }, text);
16883      hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);
16884    }
16885  }
16886  _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) {
16887    if (this.state !== State.PARSING) {
16888      return;
16889    }
16890    // delete any video track found on audio transmuxer
16891    if (tracks.video) {
16892      delete tracks.video;
16893    }
16894
16895    // include levelCodec in audio and video tracks
16896    const track = tracks.audio;
16897    if (!track) {
16898      return;
16899    }
16900    track.id = 'audio';
16901    const variantAudioCodecs = currentLevel.audioCodec;
16902    this.log(`Init audio buffer, container:${track.container}, codecs[level/parsed]=[${variantAudioCodecs}/${track.codec}]`);
16903    // SourceBuffer will use track.levelCodec if defined
16904    if (variantAudioCodecs && variantAudioCodecs.split(',').length === 1) {
16905      track.levelCodec = variantAudioCodecs;
16906    }
16907    this.hls.trigger(Events.BUFFER_CODECS, tracks);
16908    const initSegment = track.initSegment;
16909    if (initSegment != null && initSegment.byteLength) {
16910      const segment = {
16911        type: 'audio',
16912        frag,
16913        part: null,
16914        chunkMeta,
16915        parent: frag.type,
16916        data: initSegment
16917      };
16918      this.hls.trigger(Events.BUFFER_APPENDING, segment);
16919    }
16920    // trigger handler right now
16921    this.tickImmediate();
16922  }
16923  loadFragment(frag, track, targetBufferTime) {
16924    // only load if fragment is not loaded or if in audio switch
16925    const fragState = this.fragmentTracker.getState(frag);
16926    this.fragCurrent = frag;
16927
16928    // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch
16929    if (this.switchingTrack || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
16930      var _track$details2;
16931      if (frag.sn === 'initSegment') {
16932        this._loadInitSegment(frag, track);
16933      } else if ((_track$details2 = track.details) != null && _track$details2.live && !this.initPTS[frag.cc]) {
16934        this.log(`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`);
16935        this.state = State.WAITING_INIT_PTS;
16936        const mainDetails = this.mainDetails;
16937        if (mainDetails && mainDetails.fragments[0].start !== track.details.fragments[0].start) {
16938          alignMediaPlaylistByPDT(track.details, mainDetails);
16939        }
16940      } else {
16941        this.startFragRequested = true;
16942        super.loadFragment(frag, track, targetBufferTime);
16943      }
16944    } else {
16945      this.clearTrackerIfNeeded(frag);
16946    }
16947  }
16948  flushAudioIfNeeded(switchingTrack) {
16949    const {
16950      media,
16951      bufferedTrack
16952    } = this;
16953    const bufferedAttributes = bufferedTrack == null ? void 0 : bufferedTrack.attrs;
16954    const switchAttributes = switchingTrack.attrs;
16955    if (media && bufferedAttributes && (bufferedAttributes.CHANNELS !== switchAttributes.CHANNELS || bufferedTrack.name !== switchingTrack.name || bufferedTrack.lang !== switchingTrack.lang)) {
16956      this.log('Switching audio track : flushing all audio');
16957      super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
16958      this.bufferedTrack = null;
16959    }
16960  }
16961  completeAudioSwitch(switchingTrack) {
16962    const {
16963      hls
16964    } = this;
16965    this.flushAudioIfNeeded(switchingTrack);
16966    this.bufferedTrack = switchingTrack;
16967    this.switchingTrack = null;
16968    hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, switchingTrack));
16969  }
16970}
16971
16972class AudioTrackController extends BasePlaylistController {
16973  constructor(hls) {
16974    super(hls, '[audio-track-controller]');
16975    this.tracks = [];
16976    this.groupIds = null;
16977    this.tracksInGroup = [];
16978    this.trackId = -1;
16979    this.currentTrack = null;
16980    this.selectDefaultTrack = true;
16981    this.registerListeners();
16982  }
16983  registerListeners() {
16984    const {
16985      hls
16986    } = this;
16987    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16988    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
16989    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
16990    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
16991    hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
16992    hls.on(Events.ERROR, this.onError, this);
16993  }
16994  unregisterListeners() {
16995    const {
16996      hls
16997    } = this;
16998    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
16999    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
17000    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
17001    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
17002    hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
17003    hls.off(Events.ERROR, this.onError, this);
17004  }
17005  destroy() {
17006    this.unregisterListeners();
17007    this.tracks.length = 0;
17008    this.tracksInGroup.length = 0;
17009    this.currentTrack = null;
17010    super.destroy();
17011  }
17012  onManifestLoading() {
17013    this.tracks = [];
17014    this.tracksInGroup = [];
17015    this.groupIds = null;
17016    this.currentTrack = null;
17017    this.trackId = -1;
17018    this.selectDefaultTrack = true;
17019  }
17020  onManifestParsed(event, data) {
17021    this.tracks = data.audioTracks || [];
17022  }
17023  onAudioTrackLoaded(event, data) {
17024    const {
17025      id,
17026      groupId,
17027      details
17028    } = data;
17029    const trackInActiveGroup = this.tracksInGroup[id];
17030    if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) {
17031      this.warn(`Audio track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`);
17032      return;
17033    }
17034    const curDetails = trackInActiveGroup.details;
17035    trackInActiveGroup.details = data.details;
17036    this.log(`Audio track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);
17037    if (id === this.trackId) {
17038      this.playlistLoaded(id, data, curDetails);
17039    }
17040  }
17041  onLevelLoading(event, data) {
17042    this.switchLevel(data.level);
17043  }
17044  onLevelSwitching(event, data) {
17045    this.switchLevel(data.level);
17046  }
17047  switchLevel(levelIndex) {
17048    const levelInfo = this.hls.levels[levelIndex];
17049    if (!levelInfo) {
17050      return;
17051    }
17052    const audioGroups = levelInfo.audioGroups || null;
17053    const currentGroups = this.groupIds;
vendor: 4,728 bytes, lines 17054-17165
17054    let currentTrack = this.currentTrack;
17055    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)) {
17056      this.groupIds = audioGroups;
17057      this.trackId = -1;
17058      this.currentTrack = null;
17059      const audioTracks = this.tracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
17060      if (audioTracks.length) {
17061        // Disable selectDefaultTrack if there are no default tracks
17062        if (this.selectDefaultTrack && !audioTracks.some(track => track.default)) {
17063          this.selectDefaultTrack = false;
17064        }
17065        // track.id should match hls.audioTracks index
17066        audioTracks.forEach((track, i) => {
17067          track.id = i;
17068        });
17069      } else if (!currentTrack && !this.tracksInGroup.length) {
17070        // Do not dispatch AUDIO_TRACKS_UPDATED when there were and are no tracks
17071        return;
17072      }
17073      this.tracksInGroup = audioTracks;
17074
17075      // Find preferred track
17076      const audioPreference = this.hls.config.audioPreference;
17077      if (!currentTrack && audioPreference) {
17078        const groupIndex = findMatchingOption(audioPreference, audioTracks, audioMatchPredicate);
17079        if (groupIndex > -1) {
17080          currentTrack = audioTracks[groupIndex];
17081        } else {
17082          const allIndex = findMatchingOption(audioPreference, this.tracks);
17083          currentTrack = this.tracks[allIndex];
17084        }
17085      }
17086
17087      // Select initial track
17088      let trackId = this.findTrackId(currentTrack);
17089      if (trackId === -1 && currentTrack) {
17090        trackId = this.findTrackId(null);
17091      }
17092
17093      // Dispatch events and load track if needed
17094      const audioTracksUpdated = {
17095        audioTracks
17096      };
17097      this.log(`Updating audio tracks, ${audioTracks.length} track(s) found in group(s): ${audioGroups == null ? void 0 : audioGroups.join(',')}`);
17098      this.hls.trigger(Events.AUDIO_TRACKS_UPDATED, audioTracksUpdated);
17099      const selectedTrackId = this.trackId;
17100      if (trackId !== -1 && selectedTrackId === -1) {
17101        this.setAudioTrack(trackId);
17102      } else if (audioTracks.length && selectedTrackId === -1) {
17103        var _this$groupIds;
17104        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}`);
17105        this.warn(error.message);
17106        this.hls.trigger(Events.ERROR, {
17107          type: ErrorTypes.MEDIA_ERROR,
17108          details: ErrorDetails.AUDIO_TRACK_LOAD_ERROR,
17109          fatal: true,
17110          error
17111        });
17112      }
17113    } else if (this.shouldReloadPlaylist(currentTrack)) {
17114      // Retry playlist loading if no playlist is or has been loaded yet
17115      this.setAudioTrack(this.trackId);
17116    }
17117  }
17118  onError(event, data) {
17119    if (data.fatal || !data.context) {
17120      return;
17121    }
17122    if (data.context.type === PlaylistContextType.AUDIO_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) {
17123      this.requestScheduled = -1;
17124      this.checkRetry(data);
17125    }
17126  }
17127  get allAudioTracks() {
17128    return this.tracks;
17129  }
17130  get audioTracks() {
17131    return this.tracksInGroup;
17132  }
17133  get audioTrack() {
17134    return this.trackId;
17135  }
17136  set audioTrack(newId) {
17137    // If audio track is selected from API then don't choose from the manifest default track
17138    this.selectDefaultTrack = false;
17139    this.setAudioTrack(newId);
17140  }
17141  setAudioOption(audioOption) {
17142    const hls = this.hls;
17143    hls.config.audioPreference = audioOption;
17144    if (audioOption) {
17145      const allAudioTracks = this.allAudioTracks;
17146      this.selectDefaultTrack = false;
17147      if (allAudioTracks.length) {
17148        // First see if current option matches (no switch op)
17149        const currentTrack = this.currentTrack;
17150        if (currentTrack && matchesOption(audioOption, currentTrack, audioMatchPredicate)) {
17151          return currentTrack;
17152        }
17153        // Find option in available tracks (tracksInGroup)
17154        const groupIndex = findMatchingOption(audioOption, this.tracksInGroup, audioMatchPredicate);
17155        if (groupIndex > -1) {
17156          const track = this.tracksInGroup[groupIndex];
17157          this.setAudioTrack(groupIndex);
17158          return track;
17159        } else if (currentTrack) {
17160          // Find option in nearest level audio group
17161          let searchIndex = hls.loadLevel;
17162          if (searchIndex === -1) {
17163            searchIndex = hls.firstAutoLevel;
17164          }
17165          const switchIndex = findClosestLevelWithAudioGroup(audioOption, hl
vendor: 4,640 bytes, lines 17165-17307
17165s.levels, allAudioTracks, searchIndex, audioMatchPredicate);
17166          if (switchIndex === -1) {
17167            // could not find matching variant
17168            return null;
17169          }
17170          // and switch level to acheive the audio group switch
17171          hls.nextLoadLevel = switchIndex;
17172        }
17173        if (audioOption.channels || audioOption.audioCodec) {
17174          // Could not find a match with codec / channels predicate
17175          // Find a match without channels or codec
17176          const withoutCodecAndChannelsMatch = findMatchingOption(audioOption, allAudioTracks);
17177          if (withoutCodecAndChannelsMatch > -1) {
17178            return allAudioTracks[withoutCodecAndChannelsMatch];
17179          }
17180        }
17181      }
17182    }
17183    return null;
17184  }
17185  setAudioTrack(newId) {
17186    const tracks = this.tracksInGroup;
17187
17188    // check if level idx is valid
17189    if (newId < 0 || newId >= tracks.length) {
17190      this.warn(`Invalid audio track id: ${newId}`);
17191      return;
17192    }
17193
17194    // stopping live reloading timer if any
17195    this.clearTimer();
17196    this.selectDefaultTrack = false;
17197    const lastTrack = this.currentTrack;
17198    const track = tracks[newId];
17199    const trackLoaded = track.details && !track.details.live;
17200    if (newId === this.trackId && track === lastTrack && trackLoaded) {
17201      return;
17202    }
17203    this.log(`Switching to audio-track ${newId} "${track.name}" lang:${track.lang} group:${track.groupId} channels:${track.channels}`);
17204    this.trackId = newId;
17205    this.currentTrack = track;
17206    this.hls.trigger(Events.AUDIO_TRACK_SWITCHING, _objectSpread2({}, track));
17207    // Do not reload track unless live
17208    if (trackLoaded) {
17209      return;
17210    }
17211    const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details);
17212    this.loadPlaylist(hlsUrlParameters);
17213  }
17214  findTrackId(currentTrack) {
17215    const audioTracks = this.tracksInGroup;
17216    for (let i = 0; i < audioTracks.length; i++) {
17217      const track = audioTracks[i];
17218      if (this.selectDefaultTrack && !track.default) {
17219        continue;
17220      }
17221      if (!currentTrack || matchesOption(currentTrack, track, audioMatchPredicate)) {
17222        return i;
17223      }
17224    }
17225    if (currentTrack) {
17226      const {
17227        name,
17228        lang,
17229        assocLang,
17230        characteristics,
17231        audioCodec,
17232        channels
17233      } = currentTrack;
17234      for (let i = 0; i < audioTracks.length; i++) {
17235        const track = audioTracks[i];
17236        if (matchesOption({
17237          name,
17238          lang,
17239          assocLang,
17240          characteristics,
17241          audioCodec,
17242          channels
17243        }, track, audioMatchPredicate)) {
17244          return i;
17245        }
17246      }
17247      for (let i = 0; i < audioTracks.length; i++) {
17248        const track = audioTracks[i];
17249        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) {
17250          return i;
17251        }
17252      }
17253      for (let i = 0; i < audioTracks.length; i++) {
17254        const track = audioTracks[i];
17255        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])) {
17256          return i;
17257        }
17258      }
17259    }
17260    return -1;
17261  }
17262  loadPlaylist(hlsUrlParameters) {
17263    const audioTrack = this.currentTrack;
17264    if (this.shouldLoadPlaylist(audioTrack) && audioTrack) {
17265      super.loadPlaylist();
17266      const id = audioTrack.id;
17267      const groupId = audioTrack.groupId;
17268      let url = audioTrack.url;
17269      if (hlsUrlParameters) {
17270        try {
17271          url = hlsUrlParameters.addDirectives(url);
17272        } catch (error) {
17273          this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);
17274        }
17275      }
17276      // track not retrieved yet, or live playlist we need to (re)load it
17277      this.log(`loading audio-track playlist ${id} "${audioTrack.name}" lang:${audioTrack.lang} group:${groupId}`);
17278      this.clearTimer();
17279      this.hls.trigger(Events.AUDIO_TRACK_LOADING, {
17280        url,
17281        id,
17282        groupId,
17283        deliveryDirectives: hlsUrlParameters || null
17284      });
17285    }
17286  }
17287}
17288
17289const TICK_INTERVAL$1 = 500; // how often to tick in ms
17290
17291class SubtitleStreamController extends BaseStreamController {
17292  constructor(hls, fragmentTracker, keyLoader) {
17293    super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
17294    this.currentTrackId = -1;
17295    this.tracksBuffered = [];
17296    this.mainDetails = null;
17297    this._registerListeners();
17298  }
17299  onHandlerDestroying() {
17300    this._unregisterListeners();
17301    super.onHandlerDestroying();
17302    this.mainDetails = null;
17303  }
17304  _registerListeners() {
17305    const {
17306      hls
17307    } = this;
vendor: 4,737 bytes, lines 17308-17444
17308    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
17309    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
17310    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
17311    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
17312    hls.on(Events.ERROR, this.onError, this);
17313    hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
17314    hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
17315    hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
17316    hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
17317    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
17318    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
17319  }
17320  _unregisterListeners() {
17321    const {
17322      hls
17323    } = this;
17324    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
17325    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
17326    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
17327    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
17328    hls.off(Events.ERROR, this.onError, this);
17329    hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
17330    hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
17331    hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
17332    hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
17333    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
17334    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
17335  }
17336  startLoad(startPosition) {
17337    this.stopLoad();
17338    this.state = State.IDLE;
17339    this.setInterval(TICK_INTERVAL$1);
17340    this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition;
17341    this.tick();
17342  }
17343  onManifestLoading() {
17344    this.mainDetails = null;
17345    this.fragmentTracker.removeAllFragments();
17346  }
17347  onMediaDetaching() {
17348    this.tracksBuffered = [];
17349    super.onMediaDetaching();
17350  }
17351  onLevelLoaded(event, data) {
17352    this.mainDetails = data.details;
17353  }
17354  onSubtitleFragProcessed(event, data) {
17355    const {
17356      frag,
17357      success
17358    } = data;
17359    this.fragPrevious = frag;
17360    this.state = State.IDLE;
17361    if (!success) {
17362      return;
17363    }
17364    const buffered = this.tracksBuffered[this.currentTrackId];
17365    if (!buffered) {
17366      return;
17367    }
17368
17369    // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo
17370    // so we can re-use the logic used to detect how much has been buffered
17371    let timeRange;
17372    const fragStart = frag.start;
17373    for (let i = 0; i < buffered.length; i++) {
17374      if (fragStart >= buffered[i].start && fragStart <= buffered[i].end) {
17375        timeRange = buffered[i];
17376        break;
17377      }
17378    }
17379    const fragEnd = frag.start + frag.duration;
17380    if (timeRange) {
17381      timeRange.end = fragEnd;
17382    } else {
17383      timeRange = {
17384        start: fragStart,
17385        end: fragEnd
17386      };
17387      buffered.push(timeRange);
17388    }
17389    this.fragmentTracker.fragBuffered(frag);
17390    this.fragBufferedComplete(frag, null);
17391  }
17392  onBufferFlushing(event, data) {
17393    const {
17394      startOffset,
17395      endOffset
17396    } = data;
17397    if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
17398      const endOffsetSubtitles = endOffset - 1;
17399      if (endOffsetSubtitles <= 0) {
17400        return;
17401      }
17402      data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles);
17403      this.tracksBuffered.forEach(buffered => {
17404        for (let i = 0; i < buffered.length;) {
17405          if (buffered[i].end <= endOffsetSubtitles) {
17406            buffered.shift();
17407            continue;
17408          } else if (buffered[i].start < endOffsetSubtitles) {
17409            buffered[i].start = endOffsetSubtitles;
17410          } else {
17411            break;
17412          }
17413          i++;
17414        }
17415      });
17416      this.fragmentTracker.removeFragmentsInRange(startOffset, endOffsetSubtitles, PlaylistLevelType.SUBTITLE);
17417    }
17418  }
17419  onFragBuffered(event, data) {
17420    if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) {
17421      var _this$media;
17422      if ((_this$media = this.media) != null && _this$media.buffered.length) {
17423        this.loadedmetadata = true;
17424      }
17425    }
17426  }
17427
17428  // If something goes wrong, proceed to next frag, if we were processing one.
17429  onError(event, data) {
17430    const frag = data.frag;
17431    if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.SUBTITLE) {
17432      if (data.details === ErrorDetails.FRAG_GAP) {
17433        this.fragmentTracker.fragBuffered(frag, true);
17434      }
17435      if (this.fragCurrent) {
17436        this.fragCurrent.abortRequests();
17437      }
17438      if (this.state !== State.STOPPED) {
17439        this.state = State.IDLE;
17440      }
17441    }
17442  }
17443
17444  // Got all new subtitle levels.
17445  onSubtitleTracksUpdated(event, {
17446    subtitleTracks
17447  }) {
17448    if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) {
17449      this.levels = subtitleTracks.map(mediaPlaylist => new Level(mediaPlaylist));
17450      return;
17451    }
17452    this.tracksBuffered = [];
17453    this.levels = subtitleTracks.map(mediaPlaylist => {
17454      const level = new Level(mediaPlaylist);
17455      this.tracksBuffered[level.id] = [];
17456      return level;
17457    });
17458    this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE);
17459    this.fragPrevious = null;
17460    this.mediaBuffer = null;
17461  }
17462  onSubtitleTrackSwitch(event, data) {
17463    var _this$levels;
17464    this.currentTrackId = data.id;
17465    if (!((_this$levels = this.levels) != null && _this$levels.length) || this.currentTrackId === -1) {
17466      this.clearInterval();
17467      return;
17468    }
17469
17470    // Check if track has the necessary details to load fragments
17471    const currentTrack = this.levels[this.currentTrackId];
17472    if (currentTrack != null && currentTrack.details) {
17473      this.mediaBuffer = this.mediaBufferTimeRanges;
17474    } else {
17475      this.mediaBuffer = null;
17476    }
17477    if (currentTrack) {
17478      this.setInterval(TICK_INTERVAL$1);
17479    }
17480  }
17481
17482  // Got a new set of subtitle fragments.
17483  onSubtitleTrackLoaded(event, data) {
17484    var _track$details;
17485    const {
17486      currentTrackId,
17487      levels
17488    } = this;
17489    const {
17490      details: newDetails,
17491      id: trackId
17492    } = data;
17493    if (!levels) {
17494      this.warn(`Subtitle tracks were reset while loading level ${trackId}`);
17495      return;
17496    }
17497    const track = levels[trackId];
17498    if (trackId >= levels.length || !track) {
17499      return;
17500    }
17501    this.log(`Subtitle track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''},duration:${newDetails.totalduration}`);
17502    this.mediaBuffer = this.mediaBufferTimeRanges;
17503    let sliding = 0;
17504    if (newDetails.live || (_track$details = track.details) != null && _track$details.live) {
17505      const mainDetails = this.mainDetails;
17506      if (newDetails.deltaUpdateFailed || !mainDetails) {
17507        return;
17508      }
17509      const mainSlidingStartFragment = mainDetails.fragments[0];
17510      if (!track.details) {
17511        if (newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) {
17512          alignMediaPlaylistByPDT(newDetails, mainDetails);
17513          sliding = newDetails.fragments[0].start;
17514        } else if (mainSlidingStartFragment) {
17515          // line up live playlist with main so that fragments in range are loaded
17516          sliding = mainSlidingStartFragment.start;
17517          addSliding(newDetails, sliding);
17518        }
17519      } else {
17520        var _this$levelLastLoaded;
17521        sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
17522        if (sliding === 0 && mainSlidingStartFragment) {
17523          // realign with main when there is no overlap with last refresh
17524          sliding = mainSlidingStartFragment.start;
17525          addSliding(newDetails, sliding);
17526        }
17527      }
17528    }
17529    track.details = newDetails;
17530    this.levelLastLoaded = track;
17531    if (trackId !== currentTrackId) {
17532      return;
17533    }
17534    if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) {
17535      this.setStartPosition(this.mainDetails || newDetails, sliding);
17536    }
17537
17538    // trigger handler right now
17539    this.tick();
17540
17541    // If playlist is misaligned because of bad PDT or drift, delete details to resync with main on reload
17542    if (newDetails.live && !this.fragCurrent && this.media && this.state === State.IDLE) {
17543      const foundFrag = findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0);
17544      if (!foundFrag) {
17545        this.warn('Subtitle playlist not aligned with playback');
17546        track.details = undefined;
17547      }
17548    }
17549  }
17550  _handleFragmentLoadComplete(fragLoadedData) {
17551    const {
17552      frag,
17553      payload
17554    } = fragLoadedData;
17555    const decryptData = frag.decryptdata;
17556    const hls = this.hls;
17557    if (this.fragContextChanged(frag)) {
17558      return;
17559    }
17560    // check to see if the payload needs to be decrypted
17561    if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
17562      const startTime = performance.now();
17563      // decrypt the subtitles
17564      this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
17565        hls.trigger(Events.ERROR, {
17566          type: ErrorTypes.MEDIA_ERROR,
17567          details: ErrorDetails.FRAG_DECRYPT_ERROR,
17568          fatal: false,
17569          error: err,
17570          reason: err.message,
17571          frag
17572        });
17573        throw err;
17574      }).then(decryptedData => {
17575        const endTime = performance.now();
17576        hls.trigger(Events.FRAG_DECRYPTED, {
17577          frag,
17578          payload: decryptedData,
17579          stats: {
17580            tstart: startTime,
17581            tdecrypt: endTime
17582          }
17583        });
17584      }).catch(err => {
17585        this.warn(`${err.name}: ${err.message}`);
17586        this.state = State.IDLE;
17587      });
17588    }
17589  }
17590  doTick() {
17591    if (!this.media) {
17592      this.state = State.IDLE;
17593      return;
17594    }
17595    if (this.state === State.IDLE) {
17596      const {
17597        currentTrackId,
17598        levels
17599      } = this;
17600      const track = levels == null ? void 0 : levels[currentTrackId];
17601      if (!track || !levels.length || !track.details) {
17602        return;
17603      }
17604      const {
17605        config
17606      } = this;
17607      const currentTime = this.getLoadPosition();
17608      const bufferedInfo = BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId] || [], currentTime, config.maxBufferHole);
17609      const {
17610        end: targetBufferTime,
17611        len: bufferLen
17612      } = bufferedInfo;
17613      const mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN);
17614      const trackDetails = track.details;
17615      const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len) + trackDetails.levelTargetDuration;
17616      if (bufferLen > maxBufLen) {
17617        return;
17618      }
17619      const fragments = trackDetails.fragments;
17620      const fragLen = fragments.length;
17621      const end = trackDetails.edge;
17622      let foundFrag = null;
17623      const fragPrevious = this.fragPrevious;
17624      if (targetBufferTime < end) {
17625        const tolerance = config.maxFragLookUpTolerance;
17626        const lookupTolerance = targetBufferTime > end - tolerance ? 0 : tolerance;
17627        foundFrag = findFragmentByPTS(fragPrevious, fragments, Math.max(fragments[0].start, targetBufferTime), lookupTolerance);
17628        if (!foundFrag && fragPrevious && fragPrevious.start < fragments[0].start) {
17629          foundFrag = fragments[0];
17630        }
17631      } else {
17632        foundFrag = fragments[fragLen - 1];
17633      }
17634      if (!foundFrag) {
17635        return;
17636      }
17637      foundFrag = this.mapToInitFragWhenRequired(foundFrag);
17638      if (foundFrag.sn !== 'initSegment') {
17639        // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment
17640        const curSNIdx = foundFrag.sn - trackDetails.startSN;
17641        const prevFrag = fragments[curSNIdx - 1];
17642        if (prevFrag && prevFrag.cc === foundFrag.cc && this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED) {
17643          foundFrag = prevFrag;
17644        }
17645      }
17646      if (this.fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED) {
17647        // only load if fragment is not loaded
17648        this.loadFragment(foundFrag, track, targetBufferTime);
17649      }
17650    }
17651  }
17652  getMaxBufferLength(mainBufferLength) {
17653    const maxConfigBuffer = super.getMaxBufferLength();
17654    if (!mainBufferLength) {
17655      return maxConfigBuffer;
17656    }
17657    return Math.max(maxConfigBuffer, mainBufferLength);
17658  }
17659  loadFragment(frag, level, targetBufferTime) {
17660    this.fragCurrent = frag;
17661    if (frag.sn === 'initSegment') {
17662      this._loadInitSegment(frag, level);
17663    } else {
17664      this.startFragRequested = true;
17665      super.loadFragment(frag, level, targetBufferTime);
17666    }
17667  }
17668  get mediaBufferTimeRanges() {
17669    return new BufferableInstance(this.tracksBuffered[this.currentTrackId] || []);
17670  }
17671}
17672class BufferableInstance {
17673  constructor(timeranges) {
17674    this.buffered = void 0;
17675    const getRange = (name, index, length) => {
17676      index = index >>> 0;
17677      if (index > length - 1) {
17678        throw new DOMException(`Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`);
17679      }
17680      return timeranges[index][name];
17681    };
17682    this.buffered = {
17683      get length() {
17684        return timeranges.length;
17685      },
17686      end(index) {
17687        return getRange('end', index, timeranges.length);
17688      },
17689      start(index) {
17690        return getRange('start', index, timeranges.length);
17691      }
17692    };
17693  }
17694}
17695
17696class SubtitleTrackController extends BasePlaylistController {
17697  constructor(hls) {
17698    super(hls, '[subtitle-track-controller]');
17699    this.media = null;
17700    this.tracks = [];
17701    this.groupIds = null;
17702    this.tracksInGroup = [];
17703    this.trackId = -1;
17704    this.currentTrack = null;
17705    this.selectDefaultTrack = true;
17706    this.queuedDefaultTrack = -1;
17707    this.asyncPollTrackChange = () => this.pollTrackChange(0);
17708    this.useTextTrackPolling = false;
vendor: 5,523 bytes, lines 17709-17867
17709    this.subtitlePollingInterval = -1;
17710    this._subtitleDisplay = true;
17711    this.onTextTracksChanged = () => {
17712      if (!this.useTextTrackPolling) {
17713        self.clearInterval(this.subtitlePollingInterval);
17714      }
17715      // Media is undefined when switching streams via loadSource()
17716      if (!this.media || !this.hls.config.renderTextTracksNatively) {
17717        return;
17718      }
17719      let textTrack = null;
17720      const tracks = filterSubtitleTracks(this.media.textTracks);
17721      for (let i = 0; i < tracks.length; i++) {
17722        if (tracks[i].mode === 'hidden') {
17723          // Do not break in case there is a following track with showing.
17724          textTrack = tracks[i];
17725        } else if (tracks[i].mode === 'showing') {
17726          textTrack = tracks[i];
17727          break;
17728        }
17729      }
17730
17731      // Find internal track index for TextTrack
17732      const trackId = this.findTrackForTextTrack(textTrack);
17733      if (this.subtitleTrack !== trackId) {
17734        this.setSubtitleTrack(trackId);
17735      }
17736    };
17737    this.registerListeners();
17738  }
17739  destroy() {
17740    this.unregisterListeners();
17741    this.tracks.length = 0;
17742    this.tracksInGroup.length = 0;
17743    this.currentTrack = null;
17744    this.onTextTracksChanged = this.asyncPollTrackChange = null;
17745    super.destroy();
17746  }
17747  get subtitleDisplay() {
17748    return this._subtitleDisplay;
17749  }
17750  set subtitleDisplay(value) {
17751    this._subtitleDisplay = value;
17752    if (this.trackId > -1) {
17753      this.toggleTrackModes();
17754    }
17755  }
17756  registerListeners() {
17757    const {
17758      hls
17759    } = this;
17760    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
17761    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
17762    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
17763    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
17764    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
17765    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
17766    hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
17767    hls.on(Events.ERROR, this.onError, this);
17768  }
17769  unregisterListeners() {
17770    const {
17771      hls
17772    } = this;
17773    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
17774    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
17775    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
17776    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
17777    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
17778    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
17779    hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
17780    hls.off(Events.ERROR, this.onError, this);
17781  }
17782
17783  // Listen for subtitle track change, then extract the current track ID.
17784  onMediaAttached(event, data) {
17785    this.media = data.media;
17786    if (!this.media) {
17787      return;
17788    }
17789    if (this.queuedDefaultTrack > -1) {
17790      this.subtitleTrack = this.queuedDefaultTrack;
17791      this.queuedDefaultTrack = -1;
17792    }
17793    this.useTextTrackPolling = !(this.media.textTracks && 'onchange' in this.media.textTracks);
17794    if (this.useTextTrackPolling) {
17795      this.pollTrackChange(500);
17796    } else {
17797      this.media.textTracks.addEventListener('change', this.asyncPollTrackChange);
17798    }
17799  }
17800  pollTrackChange(timeout) {
17801    self.clearInterval(this.subtitlePollingInterval);
17802    this.subtitlePollingInterval = self.setInterval(this.onTextTracksChanged, timeout);
17803  }
17804  onMediaDetaching() {
17805    if (!this.media) {
17806      return;
17807    }
17808    self.clearInterval(this.subtitlePollingInterval);
17809    if (!this.useTextTrackPolling) {
17810      this.media.textTracks.removeEventListener('change', this.asyncPollTrackChange);
17811    }
17812    if (this.trackId > -1) {
17813      this.queuedDefaultTrack = this.trackId;
17814    }
17815    const textTracks = filterSubtitleTracks(this.media.textTracks);
17816    // Clear loaded cues on media detachment from tracks
17817    textTracks.forEach(track => {
17818      clearCurrentCues(track);
17819    });
17820    // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled.
17821    this.subtitleTrack = -1;
17822    this.media = null;
17823  }
17824  onManifestLoading() {
17825    this.tracks = [];
17826    this.groupIds = null;
17827    this.tracksInGroup = [];
17828    this.trackId = -1;
17829    this.currentTrack = null;
17830    this.selectDefaultTrack = true;
17831  }
17832
17833  // Fired whenever a new manifest is loaded.
17834  onManifestParsed(event, data) {
17835    this.tracks = data.subtitleTracks;
17836  }
17837  onSubtitleTrackLoaded(event, data) {
17838    const {
17839      id,
17840      groupId,
17841      details
17842    } = data;
17843    const trackInActiveGroup = this.tracksInGroup[id];
17844    if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) {
17845      this.warn(`Subtitle track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`);
17846      return;
17847    }
17848    const curDetails = trackInActiveGroup.details;
17849    trackInActiveGroup.details = data.details;
17850    this.log(`Subtitle track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);
17851    if (id === this.trackId) {
17852      this.playlistLoaded(id, data, curDetails);
17853    }
17854  }
17855  onLevelLoading(event, data) {
17856    this.switchLevel(data.level);
17857  }
17858  onLevelSwitching(event, data) {
17859    this.switchLevel(data.level);
17860  }
17861  switchLevel(levelIndex) {
17862    const levelInfo = this.hls.levels[levelIndex];
17863    if (!levelInfo) {
17864      return;
17865    }
17866    const subtitleGroups = levelInfo.subtitleGroups || null;
17867    const currentGroups = this.groupIds;
vendor: 4,378 bytes, lines 17868-17984
17868    let currentTrack = this.currentTrack;
17869    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)) {
17870      this.groupIds = subtitleGroups;
17871      this.trackId = -1;
17872      this.currentTrack = null;
17873      const subtitleTracks = this.tracks.filter(track => !subtitleGroups || subtitleGroups.indexOf(track.groupId) !== -1);
17874      if (subtitleTracks.length) {
17875        // Disable selectDefaultTrack if there are no default tracks
17876        if (this.selectDefaultTrack && !subtitleTracks.some(track => track.default)) {
17877          this.selectDefaultTrack = false;
17878        }
17879        // track.id should match hls.audioTracks index
17880        subtitleTracks.forEach((track, i) => {
17881          track.id = i;
17882        });
17883      } else if (!currentTrack && !this.tracksInGroup.length) {
17884        // Do not dispatch SUBTITLE_TRACKS_UPDATED when there were and are no tracks
17885        return;
17886      }
17887      this.tracksInGroup = subtitleTracks;
17888
17889      // Find preferred track
17890      const subtitlePreference = this.hls.config.subtitlePreference;
17891      if (!currentTrack && subtitlePreference) {
17892        this.selectDefaultTrack = false;
17893        const groupIndex = findMatchingOption(subtitlePreference, subtitleTracks);
17894        if (groupIndex > -1) {
17895          currentTrack = subtitleTracks[groupIndex];
17896        } else {
17897          const allIndex = findMatchingOption(subtitlePreference, this.tracks);
17898          currentTrack = this.tracks[allIndex];
17899        }
17900      }
17901
17902      // Select initial track
17903      let trackId = this.findTrackId(currentTrack);
17904      if (trackId === -1 && currentTrack) {
17905        trackId = this.findTrackId(null);
17906      }
17907
17908      // Dispatch events and load track if needed
17909      const subtitleTracksUpdated = {
17910        subtitleTracks
17911      };
17912      this.log(`Updating subtitle tracks, ${subtitleTracks.length} track(s) found in "${subtitleGroups == null ? void 0 : subtitleGroups.join(',')}" group-id`);
17913      this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated);
17914      if (trackId !== -1 && this.trackId === -1) {
17915        this.setSubtitleTrack(trackId);
17916      }
17917    } else if (this.shouldReloadPlaylist(currentTrack)) {
17918      // Retry playlist loading if no playlist is or has been loaded yet
17919      this.setSubtitleTrack(this.trackId);
17920    }
17921  }
17922  findTrackId(currentTrack) {
17923    const tracks = this.tracksInGroup;
17924    const selectDefault = this.selectDefaultTrack;
17925    for (let i = 0; i < tracks.length; i++) {
17926      const track = tracks[i];
17927      if (selectDefault && !track.default || !selectDefault && !currentTrack) {
17928        continue;
17929      }
17930      if (!currentTrack || matchesOption(track, currentTrack)) {
17931        return i;
17932      }
17933    }
17934    if (currentTrack) {
17935      for (let i = 0; i < tracks.length; i++) {
17936        const track = tracks[i];
17937        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) {
17938          return i;
17939        }
17940      }
17941      for (let i = 0; i < tracks.length; i++) {
17942        const track = tracks[i];
17943        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])) {
17944          return i;
17945        }
17946      }
17947    }
17948    return -1;
17949  }
17950  findTrackForTextTrack(textTrack) {
17951    if (textTrack) {
17952      const tracks = this.tracksInGroup;
17953      for (let i = 0; i < tracks.length; i++) {
17954        const track = tracks[i];
17955        if (subtitleTrackMatchesTextTrack(track, textTrack)) {
17956          return i;
17957        }
17958      }
17959    }
17960    return -1;
17961  }
17962  onError(event, data) {
17963    if (data.fatal || !data.context) {
17964      return;
17965    }
17966    if (data.context.type === PlaylistContextType.SUBTITLE_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) {
17967      this.checkRetry(data);
17968    }
17969  }
17970  get allSubtitleTracks() {
17971    return this.tracks;
17972  }
17973
17974  /** get alternate subtitle tracks list from playlist **/
17975  get subtitleTracks() {
17976    return this.tracksInGroup;
17977  }
17978
17979  /** get/set index of the selected subtitle track (based on index in subtitle track lists) **/
17980  get subtitleTrack() {
17981    return this.trackId;
17982  }
17983  set subtitleTrack(newId) {
17984    this.selectDefaultTrack = false;
17985    this.setSubtitleTrack(newId);
17986  }
17987  setSubtitleOption(subtitleOption) {
17988    this.hls.config.subtitlePreference = subtitleOption;
17989    if (subtitleOption) {
17990      const allSubtitleTracks = this.allSubtitleTracks;
17991      this.selectDefaultTrack = false;
17992      if (allSubtitleTracks.length) {
17993        // First see if current option matches (no switch op)
17994        const currentTrack = this.currentTrack;
17995        if (currentTrack && matchesOption(subtitleOption, currentTrack)) {
17996          return currentTrack;
17997        }
17998        // Find option in current group
17999        const groupIndex = findMatchingOption(subtitleOption, this.tracksInGroup);
18000        if (groupIndex > -1) {
18001          const track = this.tracksInGroup[groupIndex];
18002          this.setSubtitleTrack(groupIndex);
18003          return track;
18004        } else if (currentTrack) {
18005          // If this is not the initial selection return null
18006          // option should have matched one in active group
18007          return null;
18008        } else {
18009          // Find the option in all tracks for initial selection
18010          const allIndex = findMatchingOption(subtitleOption, allSubtitleTracks);
18011          if (allIndex > -1) {
18012            return allSubtitleTracks[allIndex];
18013          }
18014        }
18015      }
18016    }
18017    return null;
18018  }
18019  loadPlaylist(hlsUrlParameters) {
18020    super.loadPlaylist();
18021    const currentTrack = this.currentTrack;
18022    if (this.shouldLoadPlaylist(currentTrack) && currentTrack) {
18023      const id = currentTrack.id;
18024      const groupId = currentTrack.groupId;
18025      let url = currentTrack.url;
18026      if (hlsUrlParameters) {
18027        try {
18028          url = hlsUrlParameters.addDirectives(url);
18029        } catch (error) {
18030          this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);
18031        }
18032      }
18033      this.log(`Loading subtitle playlist for id ${id}`);
18034      this.hls.trigger(Events.SUBTITLE_TRACK_LOADING, {
18035        url,
18036        id,
18037        groupId,
18038        deliveryDirectives: hlsUrlParameters || null
18039      });
18040    }
18041  }
18042
18043  /**
18044   * Disables the old subtitleTrack and sets current mode on the next subtitleTrack.
18045   * This operates on the DOM textTracks.
18046   * A value of -1 will disable all subtitle tracks.
18047   */
18048  toggleTrackModes() {
18049    const {
18050      media
18051    } = this;
18052    if (!media) {
18053      return;
18054    }
18055    const textTracks = filterSubtitleTracks(media.textTracks);
18056    const currentTrack = this.currentTrack;
18057    let nextTrack;
18058    if (currentTrack) {
18059      nextTrack = textTracks.filter(textTrack => subtitleTrackMatchesTextTrack(currentTrack, textTrack))[0];
18060      if (!nextTrack) {
18061        this.warn(`Unable to find subtitle TextTrack with name "${currentTrack.name}" and language "${currentTrack.lang}"`);
18062      }
18063    }
18064    [].slice.call(textTracks).forEach(track => {
18065      if (track.mode !== 'disabled' && track !== nextTrack) {
18066        track.mode = 'disabled';
18067      }
18068    });
18069    if (nextTrack) {
18070      const mode = this.subtitleDisplay ? 'showing' : 'hidden';
18071      if (nextTrack.mode !== mode) {
18072        nextTrack.mode = mode;
18073      }
18074    }
18075  }
18076
18077  /**
18078   * This method is responsible for validating the subtitle index and periodically reloading if live.
18079   * Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track.
18080   */
18081  setSubtitleTrack(newId) {
18082    const tracks = this.tracksInGroup;
18083
18084    // setting this.subtitleTrack will trigger internal logic
18085    // if media has not been attached yet, it will fail
18086    // we keep a reference to the default track id
18087    // and we'll set subtitleTrack when onMediaAttached is triggered
18088    if (!this.media) {
18089      this.queuedDefaultTrack = newId;
18090      return;
18091    }
18092
18093    // exit if track id as already set or invalid
18094    if (newId < -1 || newId >= tracks.length || !isFiniteNumber(newId)) {
18095      this.warn(`Invalid subtitle track id: ${newId}`);
18096      return;
18097    }
18098
18099    // stopping live reloading timer if any
18100    this.clearTimer();
18101    this.selectDefaultTrack = false;
18102    const lastTrack = this.currentTrack;
18103    const track = tracks[newId] || null;
18104    this.trackId = newId;
18105    this.currentTrack = track;
18106    this.toggleTrackModes();
18107    if (!track) {
18108      // switch to -1
18109      this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {
18110        id: newId
18111      });
18112      return;
18113    }
18114    const trackLoaded = !!track.details && !track.details.live;
18115    if (newId === this.trackId && track === lastTrack && trackLoaded) {
18116      return;
18117    }
18118    this.log(`Switching to subtitle-track ${newId}` + (track ? ` "${track.name}" lang:${track.lang} group:${track.groupId}` : ''));
18119    const {
18120      id,
18121      groupId = '',
18122      name,
18123      type,
18124      url
18125    } = track;
18126    this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {
18127      id,
18128      groupId,
18129      name,
18130      type,
18131      url
18132    });
18133    const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details);
18134    this.loadPlaylist(hlsUrlParameters);
18135  }
18136}
18137
18138class BufferOperationQueue {
18139  constructor(sourceBufferReference) {
18140    this.buffers = void 0;
18141    this.queues = {
18142      video: [],
18143      audio: [],
18144      audiovideo: []
18145    };
18146    this.buffers = sourceBufferReference;
18147  }
18148  append(operation, type, pending) {
18149    const queue = this.queues[type];
18150    queue.push(operation);
18151    if (queue.length === 1 && !pending) {
18152      this.executeNext(type);
18153    }
18154  }
18155  insertAbort(operation, type) {
18156    const queue = this.queues[type];
18157    queue.unshift(operation);
18158    this.executeNext(type);
18159  }
18160  appendBlocker(type) {
18161    let execute;
18162    const promise = new Promise(resolve => {
18163      execute = resolve;
18164    });
18165    const operation = {
18166      execute,
18167      onStart: () => {},
18168      onComplete: () => {},
18169      onError: () => {}
18170    };
18171    this.append(operation, type);
18172    return promise;
18173  }
18174  executeNext(type) {
18175    const queue = this.queues[type];
18176    if (queue.length) {
18177      const operation = queue[0];
18178      try {
18179        // Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations
18180        // which do not end with this event must call _onSBUpdateEnd manually
18181        operation.execute();
18182      } catch (error) {
18183        logger.warn(`[buffer-operation-queue]: Exception executing "${type}" SourceBuffer operation: ${error}`);
18184        operation.onError(error);
18185
18186        // Only shift the current operation off, otherwise the updateend handler will do this for us
18187        const sb = this.buffers[type];
18188        if (!(sb != null && sb.updating)) {
18189          this.shiftAndExecuteNext(type);
18190        }
18191      }
18192    }
18193  }
18194  shiftAndExecuteNext(type) {
18195    this.queues[type].shift();
18196    this.executeNext(type);
18197  }
18198  current(type) {
18199    return this.queues[type][0];
18200  }
18201}
18202
18203const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
18204class BufferController {
18205  constructor(hls) {
18206    // The level details used to determine duration, target-duration and live
18207    this.details = null;
18208    // cache the self generated object url to detect hijack of video tag
18209    this._objectUrl = null;
18210    // A queue of buffer operations which require the SourceBuffer to not be updating upon execution
18211    this.operationQueue = void 0;
18212    // References to event listeners for each SourceBuffer, so that they can be referenced for event removal
18213    this.listeners = void 0;
18214    this.hls = void 0;
18215    // The number of BUFFER_CODEC events received before any sourceBuffers are created
18216    this.bufferCodecEventsExpected = 0;
18217    // The total number of BUFFER_CODEC events received
18218    this._bufferCodecEventsTotal = 0;
18219    // A reference to the attached media element
18220    this.media = null;
18221    // A reference to the active media source
18222    this.mediaSource = null;
18223    // Last MP3 audio chunk appended
18224    this.lastMpegAudioChunk = null;
18225    this.appendSource = void 0;
18226    // counters
18227    this.appendErrors = {
18228      audio: 0,
18229      video: 0,
18230      audiovideo: 0
18231    };
18232    this.tracks = {};
18233    this.pendingTracks = {};
18234    this.sourceBuffer = void 0;
18235    this.log = void 0;
18236    this.warn = void 0;
18237    this.error = void 0;
18238    this._onEndStreaming = event => {
18239      if (!this.hls) {
18240        return;
18241      }
18242      this.hls.pauseBuffering();
18243    };
18244    this._onStartStreaming = event => {
18245      if (!this.hls) {
18246        return;
18247      }
18248      this.hls.resumeBuffering();
18249    };
18250    // Keep as arrow functions so that we can directly reference these functions directly as event listeners
18251    this._onMediaSourceOpen = () => {
18252      const {
18253        media,
18254        mediaSource
18255      } = this;
18256      this.log('Media source opened');
18257      if (media) {
18258        media.removeEventListener('emptied', this._onMediaEmptied);
18259        this.updateMediaElementDuration();
18260        this.hls.trigger(Events.MEDIA_ATTACHED, {
18261          media,
18262          mediaSource: mediaSource
18263        });
18264      }
18265      if (mediaSource) {
18266        // once received, don't listen anymore to sourceopen event
18267        mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
18268      }
18269      this.checkPendingTracks();
18270    };
18271    this._onMediaSourceClose = () => {
18272      this.log('Media source closed');
18273    };
18274    this._onMediaSourceEnded = () => {
18275      this.log('Media source ended');
18276    };
18277    this._onMediaEmptied = () => {
18278      const {
18279        mediaSrc,
18280        _objectUrl
18281      } = this;
18282      if (mediaSrc !== _objectUrl) {
18283        logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18284      }
18285    };
18286    this.hls = hls;
18287    const logPrefix = '[buffer-controller]';
18288    this.appendSource = isManagedMediaSource(getMediaSource(hls.config.preferManagedMediaSource));
18289    this.log = logger.log.bind(logger, logPrefix);
18290    this.warn = logger.warn.bind(logger, logPrefix);
18291    this.error = logger.error.bind(logger, logPrefix);
18292    this._initSourceBuffer();
18293    this.registerListeners();
18294  }
18295  hasSourceTypes() {
18296    return this.getSourceBufferTypes().length > 0 || Object.keys(this.pendingTracks).length > 0;
18297  }
18298  destroy() {
18299    this.unregisterListeners();
18300    this.details = null;
18301    this.lastMpegAudioChunk = null;
18302    // @ts-ignore
18303    this.hls = null;
18304  }
18305  registerListeners() {
18306    const {
18307      hls
18308    } = this;
18309    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
18310    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18311    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18312    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18313    hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
18314    hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this);
18315    hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
18316    hls.on(Events.BUFFER_EOS, this.onBufferEos, this);
18317    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
18318    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
18319    hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
18320    hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
18321  }
18322  unregisterListeners() {
18323    const {
18324      hls
18325    } = this;
18326    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
18327    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18328    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18329    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18330    hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
18331    hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this);
18332    hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
18333    hls.off(Events.BUFFER_EOS, this.onBufferEos, this);
18334    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
18335    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
18336    hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
18337    hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
18338  }
18339  _initSourceBuffer() {
18340    this.sourceBuffer = {};
18341    this.operationQueue = new BufferOperationQueue(this.sourceBuffer);
18342    this.listeners = {
18343      audio: [],
18344      video: [],
18345      audiovideo: []
18346    };
18347    this.appendErrors = {
18348      audio: 0,
18349      video: 0,
18350      audiovideo: 0
18351    };
18352    this.lastMpegAudioChunk = null;
18353  }
18354  onManifestLoading() {
18355    this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
18356    this.details = null;
18357  }
18358  onManifestParsed(event, data) {
18359    // in case of alt audio 2 BUFFER_CODECS events will be triggered, one per stream controller
18360    // sourcebuffers will be created all at once when the expected nb of tracks will be reached
18361    // in case alt audio is not used, only one BUFFER_CODEC event will be fired from main stream controller
18362    // it will contain the expected nb of source buffers, no need to compute it
18363    let codecEvents = 2;
18364    if (data.audio && !data.video || !data.altAudio || false) {
18365      codecEvents = 1;
18366    }
18367    this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = codecEvents;
18368    this.log(`${this.bufferCodecEventsExpected} bufferCodec event(s) expected`);
18369  }
18370  onMediaAttaching(event, data) {
18371    const media = this.media = data.media;
18372    const MediaSource = getMediaSource(this.appendSource);
18373    if (media && MediaSource) {
18374      var _ms$constructor;
18375      const ms = this.mediaSource = new MediaSource();
18376      this.log(`created media source: ${(_ms$constructor = ms.constructor) == null ? void 0 : _ms$constructor.name}`);
18377      // MediaSource listeners are arrow functions with a lexical scope, and do not need to be bound
18378      ms.addEventListener('sourceopen', this._onMediaSourceOpen);
18379      ms.addEventListener('sourceended', this._onMediaSourceEnded);
18380      ms.addEventListener('sourceclose', this._onMediaSourceClose);
18381      if (this.appendSource) {
18382        ms.addEventListener('startstreaming', this._onStartStreaming);
18383        ms.addEventListener('endstreaming', this._onEndStreaming);
18384      }
18385
18386      // cache the locally generated object url
18387      const objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
18388      // link video and media Source
18389      if (this.appendSource) {
18390        try {
18391          media.removeAttribute('src');
18392          // ManagedMediaSource will not open without disableRemotePlayback set to false or source alternatives
18393          const MMS = self.ManagedMediaSource;
18394          media.disableRemotePlayback = media.disableRemotePlayback || MMS && ms instanceof MMS;
18395          removeSourceChildren(media);
18396          addSource(media, objectUrl);
18397          media.load();
18398        } catch (error) {
18399          media.src = objectUrl;
18400        }
18401      } else {
18402        media.src = objectUrl;
18403      }
18404      media.addEventListener('emptied', this._onMediaEmptied);
18405    }
18406  }
18407  onMediaDetaching() {
18408    const {
18409      media,
18410      mediaSource,
18411      _objectUrl
18412    } = this;
18413    if (mediaSource) {
18414      this.log('media source detaching');
18415      if (mediaSource.readyState === 'open') {
18416        try {
18417          // endOfStream could trigger exception if any sourcebuffer is in updating state
18418          // we don't really care about checking sourcebuffer state here,
18419          // as we are anyway detaching the MediaSource
18420          // let's just avoid this exception to propagate
18421          mediaSource.endOfStream();
18422        } catch (err) {
18423          this.warn(`onMediaDetaching: ${err.message} while calling endOfStream`);
18424        }
18425      }
18426      // Clean up the SourceBuffers by invoking onBufferReset
18427      this.onBufferReset();
18428      mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
18429      mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);
18430      mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);
18431      if (this.appendSource) {
18432        mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
18433        mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
18434      }
18435
18436      // Detach properly the MediaSource from the HTMLMediaElement as
18437      // suggested in https://github.com/w3c/media-source/issues/53.
18438      if (media) {
18439        media.removeEventListener('emptied', this._onMediaEmptied);
18440        if (_objectUrl) {
18441          self.URL.revokeObjectURL(_objectUrl);
18442        }
18443
18444        // clean up video tag src only if it's our own url. some external libraries might
18445        // hijack the video tag and change its 'src' without destroying the Hls instance first
18446        if (this.mediaSrc === _objectUrl) {
18447          media.removeAttribute('src');
18448          if (this.appendSource) {
18449            removeSourceChildren(media);
18450          }
18451          media.load();
18452        } else {
18453          this.warn('media|source.src was changed by a third party - skip cleanup');
18454        }
18455      }
18456      this.mediaSource = null;
18457      this.media = null;
18458      this._objectUrl = null;
18459      this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
18460      this.pendingTracks = {};
18461      this.tracks = {};
18462    }
18463    this.hls.trigger(Events.MEDIA_DETACHED, undefined);
18464  }
18465  onBufferReset() {
18466    this.getSourceBufferTypes().forEach(type => {
18467      this.resetBuffer(type);
18468    });
18469    this._initSourceBuffer();
18470  }
18471  resetBuffer(type) {
18472    const sb = this.sourceBuffer[type];
18473    try {
18474      if (sb) {
18475        var _this$mediaSource;
18476        this.removeBufferListeners(type);
18477        // Synchronously remove the SB from the map before the next call in order to prevent an async function from
18478        // accessing it
18479        this.sourceBuffer[type] = undefined;
18480        if ((_this$mediaSource = this.mediaSource) != null && _this$mediaSource.sourceBuffers.length) {
18481          this.mediaSource.removeSourceBuffer(sb);
18482        }
18483      }
18484    } catch (err) {
18485      this.warn(`onBufferReset ${type}`, err);
18486    }
18487  }
18488  onBufferCodecs(event, data) {
18489    const sourceBufferCount = this.getSourceBufferTypes().length;
18490    const trackNames = Object.keys(data);
18491    trackNames.forEach(trackName => {
18492      if (sourceBufferCount) {
18493        // check if SourceBuffer codec needs to change
18494        const track = this.tracks[trackName];
18495        if (track && typeof track.buffer.changeType === 'function') {
18496          var _trackCodec;
18497          const {
18498            id,
18499            codec,
18500            levelCodec,
18501            container,
18502            metadata
18503          } = data[trackName];
18504          const currentCodecFull = pickMostCompleteCodecName(track.codec, track.levelCodec);
18505          const currentCodec = currentCodecFull == null ? void 0 : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
18506          let trackCodec = pickMostCompleteCodecName(codec, levelCodec);
18507          const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
18508          if (trackCodec && currentCodec !== nextCodec) {
18509            if (trackName.slice(0, 5) === 'audio') {
18510              trackCodec = getCodecCompatibleName(trackCodec, this.appendSource);
18511            }
18512            const mimeType = `${container};codecs=${trackCodec}`;
18513            this.appendChangeType(trackName, mimeType);
18514            this.log(`switching codec ${currentCodecFull} to ${trackCodec}`);
18515            this.tracks[trackName] = {
18516              buffer: track.buffer,
18517              codec,
18518              container,
18519              levelCodec,
18520              metadata,
18521              id
18522            };
18523          }
18524        }
18525      } else {
18526        // if source buffer(s) not created yet, appended buffer tracks in this.pendingTracks
18527        this.pendingTracks[trackName] = data[trackName];
18528      }
18529    });
18530
18531    // if sourcebuffers already created, do nothing ...
18532    if (sourceBufferCount) {
18533      return;
18534    }
18535    const bufferCodecEventsExpected = Math.max(this.bufferCodecEventsExpected - 1, 0);
18536    if (this.bufferCodecEventsExpected !== bufferCodecEventsExpected) {
18537      this.log(`${bufferCodecEventsExpected} bufferCodec event(s) expected ${trackNames.join(',')}`);
18538      this.bufferCodecEventsExpected = bufferCodecEventsExpected;
18539    }
18540    if (this.mediaSource && this.mediaSource.readyState === 'open') {
18541      this.checkPendingTracks();
18542    }
18543  }
18544  appendChangeType(type, mimeType) {
18545    const {
18546      operationQueue
18547    } = this;
18548    const operation = {
18549      execute: () => {
18550        const sb = this.sourceBuffer[type];
18551        if (sb) {
18552          this.log(`changing ${type} sourceBuffer type to ${mimeType}`);
18553          sb.changeType(mimeType);
18554        }
18555        operationQueue.shiftAndExecuteNext(type);
18556      },
18557      onStart: () => {},
18558      onComplete: () => {},
18559      onError: error => {
18560        this.warn(`Failed to change ${type} SourceBuffer type`, error);
18561      }
18562    };
18563    operationQueue.append(operation, type, !!this.pendingTracks[type]);
18564  }
18565  onBufferAppending(event, eventData) {
18566    const {
18567      hls,
18568      operationQueue,
18569      tracks
18570    } = this;
18571    const {
18572      data,
18573      type,
18574      frag,
18575      part,
18576      chunkMeta
18577    } = eventData;
18578    const chunkStats = chunkMeta.buffering[type];
18579    const bufferAppendingStart = self.performance.now();
18580    chunkStats.start = bufferAppendingStart;
18581    const fragBuffering = frag.stats.buffering;
18582    const partBuffering = part ? part.stats.buffering : null;
18583    if (fragBuffering.start === 0) {
18584      fragBuffering.start = bufferAppendingStart;
18585    }
18586    if (partBuffering && partBuffering.start === 0) {
18587      partBuffering.start = bufferAppendingStart;
18588    }
18589
18590    // TODO: Only update timestampOffset when audio/mpeg fragment or part is not contiguous with previously appended
18591    // Adjusting `SourceBuffer.timestampOffset` (desired point in the timeline where the next frames should be appended)
18592    // in Chrome browser when we detect MPEG audio container and time delta between level PTS and `SourceBuffer.timestampOffset`
18593    // is greater than 100ms (this is enough to handle seek for VOD or level change for LIVE videos).
18594    // More info here: https://github.com/video-dev/hls.js/issues/332#issuecomment-257986486
18595    const audioTrack = tracks.audio;
18596    let checkTimestampOffset = false;
18597    if (type === 'audio' && (audioTrack == null ? void 0 : audioTrack.container) === 'audio/mpeg') {
18598      checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
18599      this.lastMpegAudioChunk = chunkMeta;
18600    }
18601    const fragStart = frag.start;
18602    const operation = {
18603      execute: () => {
18604        chunkStats.executeStart = self.performance.now();
18605        if (checkTimestampOffset) {
18606          const sb = this.sourceBuffer[type];
18607          if (sb) {
18608            const delta = fragStart - sb.timestampOffset;
18609            if (Math.abs(delta) >= 0.1) {
18610              this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);
18611              sb.timestampOffset = fragStart;
18612            }
18613          }
18614        }
18615        this.appendExecutor(data, type);
18616      },
18617      onStart: () => {
18618        // logger.debug(`[buffer-controller]: ${type} SourceBuffer updatestart`);
18619      },
18620      onComplete: () => {
18621        // logger.debug(`[buffer-controller]: ${type} SourceBuffer updateend`);
18622        const end = self.performance.now();
18623        chunkStats.executeEnd = chunkStats.end = end;
18624        if (fragBuffering.first === 0) {
18625          fragBuffering.first = end;
18626        }
18627        if (partBuffering && partBuffering.first === 0) {
18628          partBuffering.first = end;
18629        }
18630        const {
18631          sourceBuffer
18632        } = this;
18633        const timeRanges = {};
18634        for (const type in sourceBuffer) {
18635          timeRanges[type] = BufferHelper.getBuffered(sourceBuffer[type]);
18636        }
18637        this.appendErrors[type] = 0;
18638        if (type === 'audio' || type === 'video') {
18639          this.appendErrors.audiovideo = 0;
18640        } else {
18641          this.appendErrors.audio = 0;
18642          this.appendErrors.video = 0;
18643        }
18644        this.hls.trigger(Events.BUFFER_APPENDED, {
18645          type,
18646          frag,
18647          part,
18648          chunkMeta,
18649          parent: frag.type,
18650          timeRanges
18651        });
18652      },
18653      onError: error => {
18654        // in case any error occured while appending, put back segment in segments table
18655        const event = {
18656          type: ErrorTypes.MEDIA_ERROR,
18657          parent: frag.type,
18658          details: ErrorDetails.BUFFER_APPEND_ERROR,
18659          sourceBufferName: type,
18660          frag,
18661          part,
18662          chunkMeta,
18663          error,
18664          err: error,
18665          fatal: false
18666        };
18667        if (error.code === DOMException.QUOTA_EXCEEDED_ERR) {
18668          // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror
18669          // let's stop appending any segments, and report BUFFER_FULL_ERROR error
18670          event.details = ErrorDetails.BUFFER_FULL_ERROR;
18671        } else {
18672          const appendErrorCount = ++this.appendErrors[type];
18673          event.details = ErrorDetails.BUFFER_APPEND_ERROR;
18674          /* with UHD content, we could get loop of quota exceeded error until
18675            browser is able to evict some data from sourcebuffer. Retrying can help recover.
18676          */
18677          this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
18678          if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
18679            event.fatal = true;
18680          }
18681        }
18682        hls.trigger(Events.ERROR, event);
18683      }
18684    };
18685    operationQueue.append(operation, type, !!this.pendingTracks[type]);
18686  }
18687  onBufferFlushing(event, data) {
18688    const {
18689      operationQueue
18690    } = this;
18691    const flushOperation = type => ({
18692      execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
18693      onStart: () => {
18694        // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
18695      },
18696      onComplete: () => {
18697        // logger.debug(`[buffer-controller]: Finished flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
18698        this.hls.trigger(Events.BUFFER_FLUSHED, {
18699          type
18700        });
18701      },
18702      onError: error => {
18703        this.warn(`Failed to remove from ${type} SourceBuffer`, error);
18704      }
18705    });
18706    if (data.type) {
18707      operationQueue.append(flushOperation(data.type), data.type);
18708    } else {
18709      this.getSourceBufferTypes().forEach(type => {
18710        operationQueue.append(flushOperation(type), type);
18711      });
18712    }
18713  }
18714  onFragParsed(event, data) {
18715    const {
18716      frag,
18717      part
18718    } = data;
18719    const buffersAppendedTo = [];
18720    const elementaryStreams = part ? part.elementaryStreams : frag.elementaryStreams;
18721    if (elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]) {
18722      buffersAppendedTo.push('audiovideo');
18723    } else {
18724      if (elementaryStreams[ElementaryStreamTypes.AUDIO]) {
18725        buffersAppendedTo.push('audio');
18726      }
18727      if (elementaryStreams[ElementaryStreamTypes.VIDEO]) {
18728        buffersAppendedTo.push('video');
18729      }
18730    }
18731    const onUnblocked = () => {
18732      const now = self.performance.now();
18733      frag.stats.buffering.end = now;
18734      if (part) {
18735        part.stats.buffering.end = now;
18736      }
18737      const stats = part ? part.stats : frag.stats;
18738      this.hls.trigger(Events.FRAG_BUFFERED, {
18739        frag,
18740        part,
18741        stats,
18742        id: frag.type
18743      });
18744    };
18745    if (buffersAppendedTo.length === 0) {
18746      this.warn(`Fragments must have at least one ElementaryStreamType set. type: ${frag.type} level: ${frag.level} sn: ${frag.sn}`);
18747    }
18748    this.blockBuffers(onUnblocked, buffersAppendedTo);
18749  }
18750  onFragChanged(event, data) {
18751    this.trimBuffers();
18752  }
18753
18754  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
18755  // an undefined data.type will mark all buffers as EOS.
18756  onBufferEos(event, data) {
18757    const ended = this.getSourceBufferTypes().reduce((acc, type) => {
18758      const sb = this.sourceBuffer[type];
18759      if (sb && (!data.type || data.type === type)) {
18760        sb.ending = true;
18761        if (!sb.ended) {
18762          sb.ended = true;
18763          this.log(`${type} sourceBuffer now EOS`);
18764        }
18765      }
18766      return acc && !!(!sb || sb.ended);
18767    }, true);
18768    if (ended) {
18769      this.log(`Queueing mediaSource.endOfStream()`);
18770      this.blockBuffers(() => {
18771        this.getSourceBufferTypes().forEach(type => {
18772          const sb = this.sourceBuffer[type];
18773          if (sb) {
18774            sb.ending = false;
18775          }
18776        });
18777        const {
18778          mediaSource
18779        } = this;
18780        if (!mediaSource || mediaSource.readyState !== 'open') {
18781          if (mediaSource) {
18782            this.log(`Could not call mediaSource.endOfStream(). mediaSource.readyState: ${mediaSource.readyState}`);
18783          }
18784          return;
18785        }
18786        this.log(`Calling mediaSource.endOfStream()`);
18787        // Allow this to throw and be caught by the enqueueing function
18788        mediaSource.endOfStream();
18789      });
18790    }
18791  }
18792  onLevelUpdated(event, {
18793    details
18794  }) {
18795    if (!details.fragments.length) {
18796      return;
18797    }
18798    this.details = details;
18799    if (this.getSourceBufferTypes().length) {
18800      this.blockBuffers(this.updateMediaElementDuration.bind(this));
18801    } else {
18802      this.updateMediaElementDuration();
18803    }
18804  }
18805  trimBuffers() {
18806    const {
18807      hls,
18808      details,
18809      media
18810    } = this;
18811    if (!media || details === null) {
18812      return;
18813    }
18814    const sourceBufferTypes = this.getSourceBufferTypes();
18815    if (!sourceBufferTypes.length) {
18816      return;
18817    }
18818    const config = hls.config;
18819    const currentTime = media.currentTime;
18820    const targetDuration = details.levelTargetDuration;
18821
18822    // Support for deprecated liveBackBufferLength
18823    const backBufferLength = details.live && config.liveBackBufferLength !== null ? config.liveBackBufferLength : config.backBufferLength;
18824    if (isFiniteNumber(backBufferLength) && backBufferLength > 0) {
18825      const maxBackBufferLength = Math.max(backBufferLength, targetDuration);
18826      const targetBackBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength;
18827      this.flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition);
18828    }
18829    if (isFiniteNumber(config.frontBufferFlushThreshold) && config.frontBufferFlushThreshold > 0) {
18830      const frontBufferLength = Math.max(config.maxBufferLength, config.frontBufferFlushThreshold);
18831      const maxFrontBufferLength = Math.max(frontBufferLength, targetDuration);
18832      const targetFrontBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration + maxFrontBufferLength;
18833      this.flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition);
18834    }
18835  }
18836  flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition) {
18837    const {
18838      details,
18839      sourceBuffer
18840    } = this;
18841    const sourceBufferTypes = this.getSourceBufferTypes();
18842    sourceBufferTypes.forEach(type => {
18843      const sb = sourceBuffer[type];
18844      if (sb) {
18845        const buffered = BufferHelper.getBuffered(sb);
18846        // when target buffer start exceeds actual buffer start
18847        if (buffered.length > 0 && targetBackBufferPosition > buffered.start(0)) {
18848          this.hls.trigger(Events.BACK_BUFFER_REACHED, {
18849            bufferEnd: targetBackBufferPosition
18850          });
18851
18852          // Support for deprecated event:
18853          if (details != null && details.live) {
18854            this.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, {
18855              bufferEnd: targetBackBufferPosition
18856            });
18857          } else if (sb.ended && buffered.end(buffered.length - 1) - currentTime < targetDuration * 2) {
18858            this.log(`Cannot flush ${type} back buffer while SourceBuffer is in ended state`);
18859            return;
18860          }
18861          this.hls.trigger(Events.BUFFER_FLUSHING, {
18862            startOffset: 0,
18863            endOffset: targetBackBufferPosition,
18864            type
18865          });
18866        }
18867      }
18868    });
18869  }
18870  flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition) {
18871    const {
18872      sourceBuffer
18873    } = this;
18874    const sourceBufferTypes = this.getSourceBufferTypes();
18875    sourceBufferTypes.forEach(type => {
18876      const sb = sourceBuffer[type];
18877      if (sb) {
18878        const buffered = BufferHelper.getBuffered(sb);
18879        const numBufferedRanges = buffered.length;
18880        // The buffer is either empty or contiguous
18881        if (numBufferedRanges < 2) {
18882          return;
18883        }
18884        const bufferStart = buffered.start(numBufferedRanges - 1);
18885        const bufferEnd = buffered.end(numBufferedRanges - 1);
18886        // No flush if we can tolerate the current buffer length or the current buffer range we would flush is contiguous with current position
18887        if (targetFrontBufferPosition > bufferStart || currentTime >= bufferStart && currentTime <= bufferEnd) {
18888          return;
18889        } else if (sb.ended && currentTime - bufferEnd < 2 * targetDuration) {
18890          this.log(`Cannot flush ${type} front buffer while SourceBuffer is in ended state`);
18891          return;
18892        }
18893        this.hls.trigger(Events.BUFFER_FLUSHING, {
18894          startOffset: bufferStart,
18895          endOffset: Infinity,
18896          type
18897        });
18898      }
18899    });
18900  }
18901
18902  /**
18903   * Update Media Source duration to current level duration or override to Infinity if configuration parameter
18904   * 'liveDurationInfinity` is set to `true`
18905   * More details: https://github.com/video-dev/hls.js/issues/355
18906   */
18907  updateMediaElementDuration() {
18908    if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
18909      return;
18910    }
18911    const {
18912      details,
18913      hls,
18914      media,
18915      mediaSource
18916    } = this;
18917    const levelDuration = details.fragments[0].start + details.totalduration;
18918    const mediaDuration = media.duration;
18919    const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : 0;
18920    if (details.live && hls.config.liveDurationInfinity) {
18921      // Override duration to Infinity
18922      mediaSource.duration = Infinity;
18923      this.updateSeekableRange(details);
18924    } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
18925      // levelDuration was the last value we set.
18926      // not using mediaSource.duration as the browser may tweak this value
18927      // only update Media Source duration if its value increase, this is to avoid
18928      // flushing already buffered portion when switching between quality level
18929      this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
18930      mediaSource.duration = levelDuration;
18931    }
18932  }
18933  updateSeekableRange(levelDetails) {
18934    const mediaSource = this.mediaSource;
18935    const fragments = levelDetails.fragments;
18936    const len = fragments.length;
18937    if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) {
18938      const start = Math.max(0, fragments[0].start);
18939      const end = Math.max(start, start + levelDetails.totalduration);
18940      this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
18941      mediaSource.setLiveSeekableRange(start, end);
18942    }
18943  }
18944  checkPendingTracks() {
18945    const {
18946      bufferCodecEventsExpected,
18947      operationQueue,
18948      pendingTracks
18949    } = this;
18950
18951    // Check if we've received all of the expected bufferCodec events. When none remain, create all the sourceBuffers at once.
18952    // This is important because the MSE spec allows implementations to throw QuotaExceededErrors if creating new sourceBuffers after
18953    // data has been appended to existing ones.
18954    // 2 tracks is the max (one for audio, one for video). If we've reach this max go ahead and create the buffers.
18955    const pendingTracksCount = Object.keys(pendingTracks).length;
18956    if (pendingTracksCount && (!bufferCodecEventsExpected || pendingTracksCount === 2 || 'audiovideo' in pendingTracks)) {
18957      // ok, let's create them now !
18958      this.createSourceBuffers(pendingTracks);
18959      this.pendingTracks = {};
18960      // append any pending segments now !
18961      const buffers = this.getSourceBufferTypes();
18962      if (buffers.length) {
18963        this.hls.trigger(Events.BUFFER_CREATED, {
18964          tracks: this.tracks
18965        });
18966        buffers.forEach(type => {
18967          operationQueue.executeNext(type);
18968        });
18969      } else {
18970        const error = new Error('could not create source buffer for media codec(s)');
18971        this.hls.trigger(Events.ERROR, {
18972          type: ErrorTypes.MEDIA_ERROR,
18973          details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR,
18974          fatal: true,
18975          error,
18976          reason: error.message
18977        });
18978      }
18979    }
18980  }
18981  createSourceBuffers(tracks) {
18982    const {
18983      sourceBuffer,
18984      mediaSource
18985    } = this;
18986    if (!mediaSource) {
18987      throw Error('createSourceBuffers called when mediaSource was null');
18988    }
18989    for (const trackName in tracks) {
18990      if (!sourceBuffer[trackName]) {
18991        var _track$levelCodec;
18992        const track = tracks[trackName];
18993        if (!track) {
18994          throw Error(`source buffer exists for track ${trackName}, however track does not`);
18995        }
18996        // use levelCodec as first priority unless it contains multiple comma-separated codec values
18997        let codec = ((_track$levelCodec = track.levelCodec) == null ? void 0 : _track$levelCodec.indexOf(',')) === -1 ? track.levelCodec : track.codec;
18998        if (codec) {
18999          if (trackName.slice(0, 5) === 'audio') {
19000            codec = getCodecCompatibleName(codec, this.appendSource);
19001          }
19002        }
19003        const mimeType = `${track.container};codecs=${codec}`;
19004        this.log(`creating sourceBuffer(${mimeType})`);
19005        try {
19006          const sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType);
19007          const sbName = trackName;
19008          this.addBufferListener(sbName, 'updatestart', this._onSBUpdateStart);
19009          this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd);
19010          this.addBufferListener(sbName, 'error', this._onSBUpdateError);
19011          // ManagedSourceBuffer bufferedchange event
19012          if (this.appendSource) {
19013            this.addBufferListener(sbName, 'bufferedchange', (type, event) => {
19014              // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
19015              const removedRanges = event.removedRanges;
19016              if (removedRanges != null && removedRanges.length) {
19017                this.hls.trigger(Events.BUFFER_FLUSHED, {
19018                  type: trackName
19019                });
19020              }
19021            });
19022          }
19023          this.tracks[trackName] = {
19024            buffer: sb,
19025            codec: codec,
19026            container: track.container,
19027            levelCodec: track.levelCodec,
19028            metadata: track.metadata,
19029            id: track.id
19030          };
19031        } catch (err) {
19032          this.error(`error while trying to add sourceBuffer: ${err.message}`);
19033          this.hls.trigger(Events.ERROR, {
19034            type: ErrorTypes.MEDIA_ERROR,
19035            details: ErrorDetails.BUFFER_ADD_CODEC_ERROR,
19036            fatal: false,
19037            error: err,
19038            sourceBufferName: trackName,
19039            mimeType: mimeType
19040          });
19041        }
19042      }
19043    }
19044  }
19045  get mediaSrc() {
19046    var _this$media;
19047    const media = ((_this$media = this.media) == null ? void 0 : _this$media.firstChild) || this.media;
19048    return media == null ? void 0 : media.src;
19049  }
19050  _onSBUpdateStart(type) {
19051    const {
19052      operationQueue
19053    } = this;
19054    const operation = operationQueue.current(type);
19055    operation.onStart();
19056  }
19057  _onSBUpdateEnd(type) {
19058    var _this$mediaSource2;
19059    if (((_this$mediaSource2 = this.mediaSource) == null ? void 0 : _this$mediaSource2.readyState) === 'closed') {
19060      this.resetBuffer(type);
19061      return;
19062    }
19063    const {
19064      operationQueue
19065    } = this;
19066    const operation = operationQueue.current(type);
19067    operation.onComplete();
19068    operationQueue.shiftAndExecuteNext(type);
19069  }
19070  _onSBUpdateError(type, event) {
19071    var _this$mediaSource3;
19072    const error = new Error(`${type} SourceBuffer error. MediaSource readyState: ${(_this$mediaSource3 = this.mediaSource) == null ? void 0 : _this$mediaSource3.readyState}`);
19073    this.error(`${error}`, event);
19074    // according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error
19075    // SourceBuffer errors are not necessarily fatal; if so, the HTMLMediaElement will fire an error event
19076    this.hls.trigger(Events.ERROR, {
19077      type: ErrorTypes.MEDIA_ERROR,
19078      details: ErrorDetails.BUFFER_APPENDING_ERROR,
19079      sourceBufferName: type,
19080      error,
19081      fatal: false
19082    });
19083    // updateend is always fired after error, so we'll allow that to shift the current operation off of the queue
19084    const operation = this.operationQueue.current(type);
19085    if (operation) {
19086      operation.onError(error);
19087    }
19088  }
19089
19090  // This method must result in an updateend event; if remove is not called, _onSBUpdateEnd must be called manually
19091  removeExecutor(type, startOffset, endOffset) {
19092    const {
19093      media,
19094      mediaSource,
19095      operationQueue,
19096      sourceBuffer
19097    } = this;
19098    const sb = sourceBuffer[type];
19099    if (!media || !mediaSource || !sb) {
19100      this.warn(`Attempting to remove from the ${type} SourceBuffer, but it does not exist`);
19101      operationQueue.shiftAndExecuteNext(type);
19102      return;
19103    }
19104    const mediaDuration = isFiniteNumber(media.duration) ? media.duration : Infinity;
19105    const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : Infinity;
19106    const removeStart = Math.max(0, startOffset);
19107    const removeEnd = Math.min(endOffset, mediaDuration, msDuration);
19108    if (removeEnd > removeStart && (!sb.ending || sb.ended)) {
19109      sb.ended = false;
19110      this.log(`Removing [${removeStart},${removeEnd}] from the ${type} SourceBuffer`);
19111      sb.remove(removeStart, removeEnd);
19112    } else {
19113      // Cycle the queue
19114      operationQueue.shiftAndExecuteNext(type);
19115    }
19116  }
19117
19118  // This method must result in an updateend event; if append is not called, _onSBUpdateEnd must be called manually
19119  appendExecutor(data, type) {
19120    const sb = this.sourceBuffer[type];
19121    if (!sb) {
19122      if (!this.pendingTracks[type]) {
19123        throw new Error(`Attempting to append to the ${type} SourceBuffer, but it does not exist`);
19124      }
19125      return;
19126    }
19127    sb.ended = false;
19128    sb.appendBuffer(data);
19129  }
19130
19131  // Enqueues an operation to each SourceBuffer queue which, upon execution, resolves a promise. When all promises
19132  // resolve, the onUnblocked function is executed. Functions calling this method do not need to unblock the queue
19133  // upon completion, since we already do it here
19134  blockBuffers(onUnblocked, buffers = this.getSourceBufferTypes()) {
19135    if (!buffers.length) {
19136      this.log('Blocking operation requested, but no SourceBuffers exist');
19137      Promise.resolve().then(onUnblocked);
19138      return;
19139    }
19140    const {
19141      operationQueue
19142    } = this;
19143
19144    // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
19145    const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
19146    Promise.all(blockingOperations).then(() => {
19147      // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
19148      onUnblocked();
19149      buffers.forEach(type => {
19150        const sb = this.sourceBuffer[type];
19151        // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
19152        // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
19153        // While this is a workaround, it's probably useful to have around
19154        if (!(sb != null && sb.updating)) {
19155          operationQueue.shiftAndExecuteNext(type);
19156        }
19157      });
19158    });
19159  }
19160  getSourceBufferTypes() {
19161    return Object.keys(this.sourceBuffer);
19162  }
19163  addBufferListener(type, event, fn) {
19164    const buffer = this.sourceBuffer[type];
19165    if (!buffer) {
19166      return;
19167    }
19168    const listener = fn.bind(this, type);
19169    this.listeners[type].push({
19170      event,
19171      listener
19172    });
19173    buffer.addEventListener(event, listener);
19174  }
19175  removeBufferListeners(type) {
19176    const buffer = this.sourceBuffer[type];
19177    if (!buffer) {
19178      return;
19179    }
19180    this.listeners[type].forEach(l => {
19181      buffer.removeEventListener(l.event, l.listener);
19182    });
19183  }
19184}
19185function removeSourceChildren(node) {
19186  const sourceChildren = node.querySelectorAll('source');
19187  [].slice.call(sourceChildren).forEach(source => {
19188    node.removeChild(source);
19189  });
19190}
19191function addSource(media, url) {
19192  const source = self.document.createElement('source');
19193  source.type = 'video/mp4';
19194  source.src = url;
19195  media.appendChild(source);
vendor: 14,510 bytes, lines 19195-19770
19195
19196}
19197
19198/**
19199 *
19200 * This code was ported from the dash.js project at:
19201 *   https://github.com/Dash-Industry-Forum/dash.js/blob/development/externals/cea608-parser.js
19202 *   https://github.com/Dash-Industry-Forum/dash.js/commit/8269b26a761e0853bb21d78780ed945144ecdd4d#diff-71bc295a2d6b6b7093a1d3290d53a4b2
19203 *
19204 * The original copyright appears below:
19205 *
19206 * The copyright in this software is being made available under the BSD License,
19207 * included below. This software may be subject to other third party and contributor
19208 * rights, including patent rights, and no such rights are granted under this license.
19209 *
19210 * Copyright (c) 2015-2016, DASH Industry Forum.
19211 * All rights reserved.
19212 *
19213 * Redistribution and use in source and binary forms, with or without modification,
19214 * are permitted provided that the following conditions are met:
19215 *  1. Redistributions of source code must retain the above copyright notice, this
19216 *  list of conditions and the following disclaimer.
19217 *  * Redistributions in binary form must reproduce the above copyright notice,
19218 *  this list of conditions and the following disclaimer in the documentation and/or
19219 *  other materials provided with the distribution.
19220 *  2. Neither the name of Dash Industry Forum nor the names of its
19221 *  contributors may be used to endorse or promote products derived from this software
19222 *  without specific prior written permission.
19223 *
19224 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
19225 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19226 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19227 *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19228 *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19229 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
19230 *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
19231 *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
19232 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
19233 *  POSSIBILITY OF SUCH DAMAGE.
19234 */
19235/**
19236 *  Exceptions from regular ASCII. CodePoints are mapped to UTF-16 codes
19237 */
19238
19239const specialCea608CharsCodes = {
19240  0x2a: 0xe1,
19241  // lowercase a, acute accent
19242  0x5c: 0xe9,
19243  // lowercase e, acute accent
19244  0x5e: 0xed,
19245  // lowercase i, acute accent
19246  0x5f: 0xf3,
19247  // lowercase o, acute accent
19248  0x60: 0xfa,
19249  // lowercase u, acute accent
19250  0x7b: 0xe7,
19251  // lowercase c with cedilla
19252  0x7c: 0xf7,
19253  // division symbol
19254  0x7d: 0xd1,
19255  // uppercase N tilde
19256  0x7e: 0xf1,
19257  // lowercase n tilde
19258  0x7f: 0x2588,
19259  // Full block
19260  // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
19261  // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F
19262  // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES
19263  0x80: 0xae,
19264  // Registered symbol (R)
19265  0x81: 0xb0,
19266  // degree sign
19267  0x82: 0xbd,
19268  // 1/2 symbol
19269  0x83: 0xbf,
19270  // Inverted (open) question mark
19271  0x84: 0x2122,
19272  // Trademark symbol (TM)
19273  0x85: 0xa2,
19274  // Cents symbol
19275  0x86: 0xa3,
19276  // Pounds sterling
19277  0x87: 0x266a,
19278  // Music 8'th note
19279  0x88: 0xe0,
19280  // lowercase a, grave accent
19281  0x89: 0x20,
19282  // transparent space (regular)
19283  0x8a: 0xe8,
19284  // lowercase e, grave accent
19285  0x8b: 0xe2,
19286  // lowercase a, circumflex accent
19287  0x8c: 0xea,
19288  // lowercase e, circumflex accent
19289  0x8d: 0xee,
19290  // lowercase i, circumflex accent
19291  0x8e: 0xf4,
19292  // lowercase o, circumflex accent
19293  0x8f: 0xfb,
19294  // lowercase u, circumflex accent
19295  // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
19296  // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F
19297  0x90: 0xc1,
19298  // capital letter A with acute
19299  0x91: 0xc9,
19300  // capital letter E with acute
19301  0x92: 0xd3,
19302  // capital letter O with acute
19303  0x93: 0xda,
19304  // capital letter U with acute
19305  0x94: 0xdc,
19306  // capital letter U with diaresis
19307  0x95: 0xfc,
19308  // lowercase letter U with diaeresis
19309  0x96: 0x2018,
19310  // opening single quote
19311  0x97: 0xa1,
19312  // inverted exclamation mark
19313  0x98: 0x2a,
19314  // asterisk
19315  0x99: 0x2019,
19316  // closing single quote
19317  0x9a: 0x2501,
19318  // box drawings heavy horizontal
19319  0x9b: 0xa9,
19320  // copyright sign
19321  0x9c: 0x2120,
19322  // Service mark
19323  0x9d: 0x2022,
19324  // (round) bullet
19325  0x9e: 0x201c,
19326  // Left double quotation mark
19327  0x9f: 0x201d,
19328  // Right double quotation mark
19329  0xa0: 0xc0,
19330  // uppercase A, grave accent
19331  0xa1: 0xc2,
19332  // uppercase A, circumflex
19333  0xa2: 0xc7,
19334  // uppercase C with cedilla
19335  0xa3: 0xc8,
19336  // uppercase E, grave accent
19337  0xa4: 0xca,
19338  // uppercase E, circumflex
19339  0xa5: 0xcb,
19340  // capital letter E with diaresis
19341  0xa6: 0xeb,
19342  // lowercase letter e with diaresis
19343  0xa7: 0xce,
19344  // uppercase I, circumflex
19345  0xa8: 0xcf,
19346  // uppercase I, with diaresis
19347  0xa9: 0xef,
19348  // lowercase i, with diaresis
19349  0xaa: 0xd4,
19350  // uppercase O, circumflex
19351  0xab: 0xd9,
19352  // uppercase U, grave accent
19353  0xac: 0xf9,
19354  // lowercase u, grave accent
19355  0xad: 0xdb,
19356  // uppercase U, circumflex
19357  0xae: 0xab,
19358  // left-pointing double angle quotation mark
19359  0xaf: 0xbb,
19360  // right-pointing double angle quotation mark
19361  // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
19362  // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F
19363  0xb0: 0xc3,
19364  // Uppercase A, tilde
19365  0xb1: 0xe3,
19366  // Lowercase a, tilde
19367  0xb2: 0xcd,
19368  // Uppercase I, acute accent
19369  0xb3: 0xcc,
19370  // Uppercase I, grave accent
19371  0xb4: 0xec,
19372  // Lowercase i, grave accent
19373  0xb5: 0xd2,
19374  // Uppercase O, grave accent
19375  0xb6: 0xf2,
19376  // Lowercase o, grave accent
19377  0xb7: 0xd5,
19378  // Uppercase O, tilde
19379  0xb8: 0xf5,
19380  // Lowercase o, tilde
19381  0xb9: 0x7b,
19382  // Open curly brace
19383  0xba: 0x7d,
19384  // Closing curly brace
19385  0xbb: 0x5c,
19386  // Backslash
19387  0xbc: 0x5e,
19388  // Caret
19389  0xbd: 0x5f,
19390  // Underscore
19391  0xbe: 0x7c,
19392  // Pipe (vertical line)
19393  0xbf: 0x223c,
19394  // Tilde operator
19395  0xc0: 0xc4,
19396  // Uppercase A, umlaut
19397  0xc1: 0xe4,
19398  // Lowercase A, umlaut
19399  0xc2: 0xd6,
19400  // Uppercase O, umlaut
19401  0xc3: 0xf6,
19402  // Lowercase o, umlaut
19403  0xc4: 0xdf,
19404  // Esszett (sharp S)
19405  0xc5: 0xa5,
19406  // Yen symbol
19407  0xc6: 0xa4,
19408  // Generic currency sign
19409  0xc7: 0x2503,
19410  // Box drawings heavy vertical
19411  0xc8: 0xc5,
19412  // Uppercase A, ring
19413  0xc9: 0xe5,
19414  // Lowercase A, ring
19415  0xca: 0xd8,
19416  // Uppercase O, stroke
19417  0xcb: 0xf8,
19418  // Lowercase o, strok
19419  0xcc: 0x250f,
19420  // Box drawings heavy down and right
19421  0xcd: 0x2513,
19422  // Box drawings heavy down and left
19423  0xce: 0x2517,
19424  // Box drawings heavy up and right
19425  0xcf: 0x251b // Box drawings heavy up and left
19426};
19427
19428/**
19429 * Utils
19430 */
19431const getCharForByte = byte => String.fromCharCode(specialCea608CharsCodes[byte] || byte);
19432const NR_ROWS = 15;
19433const NR_COLS = 100;
19434// Tables to look up row from PAC data
19435const rowsLowCh1 = {
19436  0x11: 1,
19437  0x12: 3,
19438  0x15: 5,
19439  0x16: 7,
19440  0x17: 9,
19441  0x10: 11,
19442  0x13: 12,
19443  0x14: 14
19444};
19445const rowsHighCh1 = {
19446  0x11: 2,
19447  0x12: 4,
19448  0x15: 6,
19449  0x16: 8,
19450  0x17: 10,
19451  0x13: 13,
19452  0x14: 15
19453};
19454const rowsLowCh2 = {
19455  0x19: 1,
19456  0x1a: 3,
19457  0x1d: 5,
19458  0x1e: 7,
19459  0x1f: 9,
19460  0x18: 11,
19461  0x1b: 12,
19462  0x1c: 14
19463};
19464const rowsHighCh2 = {
19465  0x19: 2,
19466  0x1a: 4,
19467  0x1d: 6,
19468  0x1e: 8,
19469  0x1f: 10,
19470  0x1b: 13,
19471  0x1c: 15
19472};
19473const backgroundColors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'black', 'transparent'];
19474class CaptionsLogger {
19475  constructor() {
19476    this.time = null;
19477    this.verboseLevel = 0;
19478  }
19479  log(severity, msg) {
19480    if (this.verboseLevel >= severity) {
19481      const m = typeof msg === 'function' ? msg() : msg;
19482      logger.log(`${this.time} [${severity}] ${m}`);
19483    }
19484  }
19485}
19486const numArrayToHexArray = function numArrayToHexArray(numArray) {
19487  const hexArray = [];
19488  for (let j = 0; j < numArray.length; j++) {
19489    hexArray.push(numArray[j].toString(16));
19490  }
19491  return hexArray;
19492};
19493class PenState {
19494  constructor() {
19495    this.foreground = 'white';
19496    this.underline = false;
19497    this.italics = false;
19498    this.background = 'black';
19499    this.flash = false;
19500  }
19501  reset() {
19502    this.foreground = 'white';
19503    this.underline = false;
19504    this.italics = false;
19505    this.background = 'black';
19506    this.flash = false;
19507  }
19508  setStyles(styles) {
19509    const attribs = ['foreground', 'underline', 'italics', 'background', 'flash'];
19510    for (let i = 0; i < attribs.length; i++) {
19511      const style = attribs[i];
19512      if (styles.hasOwnProperty(style)) {
19513        this[style] = styles[style];
19514      }
19515    }
19516  }
19517  isDefault() {
19518    return this.foreground === 'white' && !this.underline && !this.italics && this.background === 'black' && !this.flash;
19519  }
19520  equals(other) {
19521    return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash;
19522  }
19523  copy(newPenState) {
19524    this.foreground = newPenState.foreground;
19525    this.underline = newPenState.underline;
19526    this.italics = newPenState.italics;
19527    this.background = newPenState.background;
19528    this.flash = newPenState.flash;
19529  }
19530  toString() {
19531    return 'color=' + this.foreground + ', underline=' + this.underline + ', italics=' + this.italics + ', background=' + this.background + ', flash=' + this.flash;
19532  }
19533}
19534
19535/**
19536 * Unicode character with styling and background.
19537 * @constructor
19538 */
19539class StyledUnicodeChar {
19540  constructor() {
19541    this.uchar = ' ';
19542    this.penState = new PenState();
19543  }
19544  reset() {
19545    this.uchar = ' ';
19546    this.penState.reset();
19547  }
19548  setChar(uchar, newPenState) {
19549    this.uchar = uchar;
19550    this.penState.copy(newPenState);
19551  }
19552  setPenState(newPenState) {
19553    this.penState.copy(newPenState);
19554  }
19555  equals(other) {
19556    return this.uchar === other.uchar && this.penState.equals(other.penState);
19557  }
19558  copy(newChar) {
19559    this.uchar = newChar.uchar;
19560    this.penState.copy(newChar.penState);
19561  }
19562  isEmpty() {
19563    return this.uchar === ' ' && this.penState.isDefault();
19564  }
19565}
19566
19567/**
19568 * CEA-608 row consisting of NR_COLS instances of StyledUnicodeChar.
19569 * @constructor
19570 */
19571class Row {
19572  constructor(logger) {
19573    this.chars = [];
19574    this.pos = 0;
19575    this.currPenState = new PenState();
19576    this.cueStartTime = null;
19577    this.logger = void 0;
19578    for (let i = 0; i < NR_COLS; i++) {
19579      this.chars.push(new StyledUnicodeChar());
19580    }
19581    this.logger = logger;
19582  }
19583  equals(other) {
19584    for (let i = 0; i < NR_COLS; i++) {
19585      if (!this.chars[i].equals(other.chars[i])) {
19586        return false;
19587      }
19588    }
19589    return true;
19590  }
19591  copy(other) {
19592    for (let i = 0; i < NR_COLS; i++) {
19593      this.chars[i].copy(other.chars[i]);
19594    }
19595  }
19596  isEmpty() {
19597    let empty = true;
19598    for (let i = 0; i < NR_COLS; i++) {
19599      if (!this.chars[i].isEmpty()) {
19600        empty = false;
19601        break;
19602      }
19603    }
19604    return empty;
19605  }
19606
19607  /**
19608   *  Set the cursor to a valid column.
19609   */
19610  setCursor(absPos) {
19611    if (this.pos !== absPos) {
19612      this.pos = absPos;
19613    }
19614    if (this.pos < 0) {
19615      this.logger.log(3, 'Negative cursor position ' + this.pos);
19616      this.pos = 0;
19617    } else if (this.pos > NR_COLS) {
19618      this.logger.log(3, 'Too large cursor position ' + this.pos);
19619      this.pos = NR_COLS;
19620    }
19621  }
19622
19623  /**
19624   * Move the cursor relative to current position.
19625   */
19626  moveCursor(relPos) {
19627    const newPos = this.pos + relPos;
19628    if (relPos > 1) {
19629      for (let i = this.pos + 1; i < newPos + 1; i++) {
19630        this.chars[i].setPenState(this.currPenState);
19631      }
19632    }
19633    this.setCursor(newPos);
19634  }
19635
19636  /**
19637   * Backspace, move one step back and clear character.
19638   */
19639  backSpace() {
19640    this.moveCursor(-1);
19641    this.chars[this.pos].setChar(' ', this.currPenState);
19642  }
19643  insertChar(byte) {
19644    if (byte >= 0x90) {
19645      // Extended char
19646      this.backSpace();
19647    }
19648    const char = getCharForByte(byte);
19649    if (this.pos >= NR_COLS) {
19650      this.logger.log(0, () => 'Cannot insert ' + byte.toString(16) + ' (' + char + ') at position ' + this.pos + '. Skipping it!');
19651      return;
19652    }
19653    this.chars[this.pos].setChar(char, this.currPenState);
19654    this.moveCursor(1);
19655  }
19656  clearFromPos(startPos) {
19657    let i;
19658    for (i = startPos; i < NR_COLS; i++) {
19659      this.chars[i].reset();
19660    }
19661  }
19662  clear() {
19663    this.clearFromPos(0);
19664    this.pos = 0;
19665    this.currPenState.reset();
19666  }
19667  clearToEndOfRow() {
19668    this.clearFromPos(this.pos);
19669  }
19670  getTextString() {
19671    const chars = [];
19672    let empty = true;
19673    for (let i = 0; i < NR_COLS; i++) {
19674      const char = this.chars[i].uchar;
19675      if (char !== ' ') {
19676        empty = false;
19677      }
19678      chars.push(char);
19679    }
19680    if (empty) {
19681      return '';
19682    } else {
19683      return chars.join('');
19684    }
19685  }
19686  setPenStyles(styles) {
19687    this.currPenState.setStyles(styles);
19688    const currChar = this.chars[this.pos];
19689    currChar.setPenState(this.currPenState);
19690  }
19691}
19692
19693/**
19694 * Keep a CEA-608 screen of 32x15 styled characters
19695 * @constructor
19696 */
19697class CaptionScreen {
19698  constructor(logger) {
19699    this.rows = [];
19700    this.currRow = NR_ROWS - 1;
19701    this.nrRollUpRows = null;
19702    this.lastOutputScreen = null;
19703    this.logger = void 0;
19704    for (let i = 0; i < NR_ROWS; i++) {
19705      this.rows.push(new Row(logger));
19706    }
19707    this.logger = logger;
19708  }
19709  reset() {
19710    for (let i = 0; i < NR_ROWS; i++) {
19711      this.rows[i].clear();
19712    }
19713    this.currRow = NR_ROWS - 1;
19714  }
19715  equals(other) {
19716    let equal = true;
19717    for (let i = 0; i < NR_ROWS; i++) {
19718      if (!this.rows[i].equals(other.rows[i])) {
19719        equal = false;
19720        break;
19721      }
19722    }
19723    return equal;
19724  }
19725  copy(other) {
19726    for (let i = 0; i < NR_ROWS; i++) {
19727      this.rows[i].copy(other.rows[i]);
19728    }
19729  }
19730  isEmpty() {
19731    let empty = true;
19732    for (let i = 0; i < NR_ROWS; i++) {
19733      if (!this.rows[i].isEmpty()) {
19734        empty = false;
19735        break;
19736      }
19737    }
19738    return empty;
19739  }
19740  backSpace() {
19741    const row = this.rows[this.currRow];
19742    row.backSpace();
19743  }
19744  clearToEndOfRow() {
19745    const row = this.rows[this.currRow];
19746    row.clearToEndOfRow();
19747  }
19748
19749  /**
19750   * Insert a character (without styling) in the current row.
19751   */
19752  insertChar(char) {
19753    const row = this.rows[this.currRow];
19754    row.insertChar(char);
19755  }
19756  setPen(styles) {
19757    const row = this.rows[this.currRow];
19758    row.setPenStyles(styles);
19759  }
19760  moveCursor(relPos) {
19761    const row = this.rows[this.currRow];
19762    row.moveCursor(relPos);
19763  }
19764  setCursor(absPos) {
19765    this.logger.log(2, 'setCursor: ' + absPos);
19766    const row = this.rows[this.currRow];
19767    row.setCursor(absPos);
19768  }
19769  setPAC(pacData) {
19770    this.logger.log(2, () => 'pacData = ' + 
vendor: 1,927 bytes, lines 19770-19821
19770JSON.stringify(pacData));
19771    let newRow = pacData.row - 1;
19772    if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) {
19773      newRow = this.nrRollUpRows - 1;
19774    }
19775
19776    // Make sure this only affects Roll-up Captions by checking this.nrRollUpRows
19777    if (this.nrRollUpRows && this.currRow !== newRow) {
19778      // clear all rows first
19779      for (let i = 0; i < NR_ROWS; i++) {
19780        this.rows[i].clear();
19781      }
19782
19783      // Copy this.nrRollUpRows rows from lastOutputScreen and place it in the newRow location
19784      // topRowIndex - the start of rows to copy (inclusive index)
19785      const topRowIndex = this.currRow + 1 - this.nrRollUpRows;
19786      // We only copy if the last position was already shown.
19787      // We use the cueStartTime value to check this.
19788      const lastOutputScreen = this.lastOutputScreen;
19789      if (lastOutputScreen) {
19790        const prevLineTime = lastOutputScreen.rows[topRowIndex].cueStartTime;
19791        const time = this.logger.time;
19792        if (prevLineTime !== null && time !== null && prevLineTime < time) {
19793          for (let i = 0; i < this.nrRollUpRows; i++) {
19794            this.rows[newRow - this.nrRollUpRows + i + 1].copy(lastOutputScreen.rows[topRowIndex + i]);
19795          }
19796        }
19797      }
19798    }
19799    this.currRow = newRow;
19800    const row = this.rows[this.currRow];
19801    if (pacData.indent !== null) {
19802      const indent = pacData.indent;
19803      const prevPos = Math.max(indent - 1, 0);
19804      row.setCursor(pacData.indent);
19805      pacData.color = row.chars[prevPos].penState.foreground;
19806    }
19807    const styles = {
19808      foreground: pacData.color,
19809      underline: pacData.underline,
19810      italics: pacData.italics,
19811      background: 'black',
19812      flash: false
19813    };
19814    this.setPen(styles);
19815  }
19816
19817  /**
19818   * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility).
19819   */
19820  setBkgData(bkgData) {
19821    this.logger.log(2, () => 'bkgData = ' + JSON.
vendor: 7,079 bytes, lines 19821-20061
19821stringify(bkgData));
19822    this.backSpace();
19823    this.setPen(bkgData);
19824    this.insertChar(0x20); // Space
19825  }
19826  setRollUpRows(nrRows) {
19827    this.nrRollUpRows = nrRows;
19828  }
19829  rollUp() {
19830    if (this.nrRollUpRows === null) {
19831      this.logger.log(3, 'roll_up but nrRollUpRows not set yet');
19832      return; // Not properly setup
19833    }
19834    this.logger.log(1, () => this.getDisplayText());
19835    const topRowIndex = this.currRow + 1 - this.nrRollUpRows;
19836    const topRow = this.rows.splice(topRowIndex, 1)[0];
19837    topRow.clear();
19838    this.rows.splice(this.currRow, 0, topRow);
19839    this.logger.log(2, 'Rolling up');
19840    // this.logger.log(VerboseLevel.TEXT, this.get_display_text())
19841  }
19842
19843  /**
19844   * Get all non-empty rows with as unicode text.
19845   */
19846  getDisplayText(asOneRow) {
19847    asOneRow = asOneRow || false;
19848    const displayText = [];
19849    let text = '';
19850    let rowNr = -1;
19851    for (let i = 0; i < NR_ROWS; i++) {
19852      const rowText = this.rows[i].getTextString();
19853      if (rowText) {
19854        rowNr = i + 1;
19855        if (asOneRow) {
19856          displayText.push('Row ' + rowNr + ": '" + rowText + "'");
19857        } else {
19858          displayText.push(rowText.trim());
19859        }
19860      }
19861    }
19862    if (displayText.length > 0) {
19863      if (asOneRow) {
19864        text = '[' + displayText.join(' | ') + ']';
19865      } else {
19866        text = displayText.join('\n');
19867      }
19868    }
19869    return text;
19870  }
19871  getTextAndFormat() {
19872    return this.rows;
19873  }
19874}
19875
19876// var modes = ['MODE_ROLL-UP', 'MODE_POP-ON', 'MODE_PAINT-ON', 'MODE_TEXT'];
19877
19878class Cea608Channel {
19879  constructor(channelNumber, outputFilter, logger) {
19880    this.chNr = void 0;
19881    this.outputFilter = void 0;
19882    this.mode = void 0;
19883    this.verbose = void 0;
19884    this.displayedMemory = void 0;
19885    this.nonDisplayedMemory = void 0;
19886    this.lastOutputScreen = void 0;
19887    this.currRollUpRow = void 0;
19888    this.writeScreen = void 0;
19889    this.cueStartTime = void 0;
19890    this.logger = void 0;
19891    this.chNr = channelNumber;
19892    this.outputFilter = outputFilter;
19893    this.mode = null;
19894    this.verbose = 0;
19895    this.displayedMemory = new CaptionScreen(logger);
19896    this.nonDisplayedMemory = new CaptionScreen(logger);
19897    this.lastOutputScreen = new CaptionScreen(logger);
19898    this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
19899    this.writeScreen = this.displayedMemory;
19900    this.mode = null;
19901    this.cueStartTime = null; // Keeps track of where a cue started.
19902    this.logger = logger;
19903  }
19904  reset() {
19905    this.mode = null;
19906    this.displayedMemory.reset();
19907    this.nonDisplayedMemory.reset();
19908    this.lastOutputScreen.reset();
19909    this.outputFilter.reset();
19910    this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
19911    this.writeScreen = this.displayedMemory;
19912    this.mode = null;
19913    this.cueStartTime = null;
19914  }
19915  getHandler() {
19916    return this.outputFilter;
19917  }
19918  setHandler(newHandler) {
19919    this.outputFilter = newHandler;
19920  }
19921  setPAC(pacData) {
19922    this.writeScreen.setPAC(pacData);
19923  }
19924  setBkgData(bkgData) {
19925    this.writeScreen.setBkgData(bkgData);
19926  }
19927  setMode(newMode) {
19928    if (newMode === this.mode) {
19929      return;
19930    }
19931    this.mode = newMode;
19932    this.logger.log(2, () => 'MODE=' + newMode);
19933    if (this.mode === 'MODE_POP-ON') {
19934      this.writeScreen = this.nonDisplayedMemory;
19935    } else {
19936      this.writeScreen = this.displayedMemory;
19937      this.writeScreen.reset();
19938    }
19939    if (this.mode !== 'MODE_ROLL-UP') {
19940      this.displayedMemory.nrRollUpRows = null;
19941      this.nonDisplayedMemory.nrRollUpRows = null;
19942    }
19943    this.mode = newMode;
19944  }
19945  insertChars(chars) {
19946    for (let i = 0; i < chars.length; i++) {
19947      this.writeScreen.insertChar(chars[i]);
19948    }
19949    const screen = this.writeScreen === this.displayedMemory ? 'DISP' : 'NON_DISP';
19950    this.logger.log(2, () => screen + ': ' + this.writeScreen.getDisplayText(true));
19951    if (this.mode === 'MODE_PAINT-ON' || this.mode === 'MODE_ROLL-UP') {
19952      this.logger.log(1, () => 'DISPLAYED: ' + this.displayedMemory.getDisplayText(true));
19953      this.outputDataUpdate();
19954    }
19955  }
19956  ccRCL() {
19957    // Resume Caption Loading (switch mode to Pop On)
19958    this.logger.log(2, 'RCL - Resume Caption Loading');
19959    this.setMode('MODE_POP-ON');
19960  }
19961  ccBS() {
19962    // BackSpace
19963    this.logger.log(2, 'BS - BackSpace');
19964    if (this.mode === 'MODE_TEXT') {
19965      return;
19966    }
19967    this.writeScreen.backSpace();
19968    if (this.writeScreen === this.displayedMemory) {
19969      this.outputDataUpdate();
19970    }
19971  }
19972  ccAOF() {
19973    // Reserved (formerly Alarm Off)
19974  }
19975  ccAON() {
19976    // Reserved (formerly Alarm On)
19977  }
19978  ccDER() {
19979    // Delete to End of Row
19980    this.logger.log(2, 'DER- Delete to End of Row');
19981    this.writeScreen.clearToEndOfRow();
19982    this.outputDataUpdate();
19983  }
19984  ccRU(nrRows) {
19985    // Roll-Up Captions-2,3,or 4 Rows
19986    this.logger.log(2, 'RU(' + nrRows + ') - Roll Up');
19987    this.writeScreen = this.displayedMemory;
19988    this.setMode('MODE_ROLL-UP');
19989    this.writeScreen.setRollUpRows(nrRows);
19990  }
19991  ccFON() {
19992    // Flash On
19993    this.logger.log(2, 'FON - Flash On');
19994    this.writeScreen.setPen({
19995      flash: true
19996    });
19997  }
19998  ccRDC() {
19999    // Resume Direct Captioning (switch mode to PaintOn)
20000    this.logger.log(2, 'RDC - Resume Direct Captioning');
20001    this.setMode('MODE_PAINT-ON');
20002  }
20003  ccTR() {
20004    // Text Restart in text mode (not supported, however)
20005    this.logger.log(2, 'TR');
20006    this.setMode('MODE_TEXT');
20007  }
20008  ccRTD() {
20009    // Resume Text Display in Text mode (not supported, however)
20010    this.logger.log(2, 'RTD');
20011    this.setMode('MODE_TEXT');
20012  }
20013  ccEDM() {
20014    // Erase Displayed Memory
20015    this.logger.log(2, 'EDM - Erase Displayed Memory');
20016    this.displayedMemory.reset();
20017    this.outputDataUpdate(true);
20018  }
20019  ccCR() {
20020    // Carriage Return
20021    this.logger.log(2, 'CR - Carriage Return');
20022    this.writeScreen.rollUp();
20023    this.outputDataUpdate(true);
20024  }
20025  ccENM() {
20026    // Erase Non-Displayed Memory
20027    this.logger.log(2, 'ENM - Erase Non-displayed Memory');
20028    this.nonDisplayedMemory.reset();
20029  }
20030  ccEOC() {
20031    // End of Caption (Flip Memories)
20032    this.logger.log(2, 'EOC - End Of Caption');
20033    if (this.mode === 'MODE_POP-ON') {
20034      const tmp = this.displayedMemory;
20035      this.displayedMemory = this.nonDisplayedMemory;
20036      this.nonDisplayedMemory = tmp;
20037      this.writeScreen = this.nonDisplayedMemory;
20038      this.logger.log(1, () => 'DISP: ' + this.displayedMemory.getDisplayText());
20039    }
20040    this.outputDataUpdate(true);
20041  }
20042  ccTO(nrCols) {
20043    // Tab Offset 1,2, or 3 columns
20044    this.logger.log(2, 'TO(' + nrCols + ') - Tab Offset');
20045    this.writeScreen.moveCursor(nrCols);
20046  }
20047  ccMIDROW(secondByte) {
20048    // Parse MIDROW command
20049    const styles = {
20050      flash: false
20051    };
20052    styles.underline = secondByte % 2 === 1;
20053    styles.italics = secondByte >= 0x2e;
20054    if (!styles.italics) {
20055      const colorIndex = Math.floor(secondByte / 2) - 0x10;
20056      const colors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta'];
20057      styles.foreground = colors[colorIndex];
20058    } else {
20059      styles.foreground = 'white';
20060    }
20061    this.logger.log(2, 'MIDROW: ' + 
20061JSON.
vendor: 10,155 bytes, lines 20061-20414
20061stringify(styles));
20062    this.writeScreen.setPen(styles);
20063  }
20064  outputDataUpdate(dispatch = false) {
20065    const time = this.logger.time;
20066    if (time === null) {
20067      return;
20068    }
20069    if (this.outputFilter) {
20070      if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) {
20071        // Start of a new cue
20072        this.cueStartTime = time;
20073      } else {
20074        if (!this.displayedMemory.equals(this.lastOutputScreen)) {
20075          this.outputFilter.newCue(this.cueStartTime, time, this.lastOutputScreen);
20076          if (dispatch && this.outputFilter.dispatchCue) {
20077            this.outputFilter.dispatchCue();
20078          }
20079          this.cueStartTime = this.displayedMemory.isEmpty() ? null : time;
20080        }
20081      }
20082      this.lastOutputScreen.copy(this.displayedMemory);
20083    }
20084  }
20085  cueSplitAtTime(t) {
20086    if (this.outputFilter) {
20087      if (!this.displayedMemory.isEmpty()) {
20088        if (this.outputFilter.newCue) {
20089          this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory);
20090        }
20091        this.cueStartTime = t;
20092      }
20093    }
20094  }
20095}
20096
20097// Will be 1 or 2 when parsing captions
20098
20099class Cea608Parser {
20100  constructor(field, out1, out2) {
20101    this.channels = void 0;
20102    this.currentChannel = 0;
20103    this.cmdHistory = createCmdHistory();
20104    this.logger = void 0;
20105    const logger = this.logger = new CaptionsLogger();
20106    this.channels = [null, new Cea608Channel(field, out1, logger), new Cea608Channel(field + 1, out2, logger)];
20107  }
20108  getHandler(channel) {
20109    return this.channels[channel].getHandler();
20110  }
20111  setHandler(channel, newHandler) {
20112    this.channels[channel].setHandler(newHandler);
20113  }
20114
20115  /**
20116   * Add data for time t in forms of list of bytes (unsigned ints). The bytes are treated as pairs.
20117   */
20118  addData(time, byteList) {
20119    this.logger.time = time;
20120    for (let i = 0; i < byteList.length; i += 2) {
20121      const a = byteList[i] & 0x7f;
20122      const b = byteList[i + 1] & 0x7f;
20123      let cmdFound = false;
20124      let charsFound = null;
20125      if (a === 0 && b === 0) {
20126        continue;
20127      } else {
20128        this.logger.log(3, () => '[' + numArrayToHexArray([byteList[i], byteList[i + 1]]) + '] -> (' + numArrayToHexArray([a, b]) + ')');
20129      }
20130      const cmdHistory = this.cmdHistory;
20131      const isControlCode = a >= 0x10 && a <= 0x1f;
20132      if (isControlCode) {
20133        // Skip redundant control codes
20134        if (hasCmdRepeated(a, b, cmdHistory)) {
20135          setLastCmd(null, null, cmdHistory);
20136          this.logger.log(3, () => 'Repeated command (' + numArrayToHexArray([a, b]) + ') is dropped');
20137          continue;
20138        }
20139        setLastCmd(a, b, this.cmdHistory);
20140        cmdFound = this.parseCmd(a, b);
20141        if (!cmdFound) {
20142          cmdFound = this.parseMidrow(a, b);
20143        }
20144        if (!cmdFound) {
20145          cmdFound = this.parsePAC(a, b);
20146        }
20147        if (!cmdFound) {
20148          cmdFound = this.parseBackgroundAttributes(a, b);
20149        }
20150      } else {
20151        setLastCmd(null, null, cmdHistory);
20152      }
20153      if (!cmdFound) {
20154        charsFound = this.parseChars(a, b);
20155        if (charsFound) {
20156          const currChNr = this.currentChannel;
20157          if (currChNr && currChNr > 0) {
20158            const channel = this.channels[currChNr];
20159            channel.insertChars(charsFound);
20160          } else {
20161            this.logger.log(2, 'No channel found yet. TEXT-MODE?');
20162          }
20163        }
20164      }
20165      if (!cmdFound && !charsFound) {
20166        this.logger.log(2, () => "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + ' orig: ' + numArrayToHexArray([byteList[i], byteList[i + 1]]));
20167      }
20168    }
20169  }
20170
20171  /**
20172   * Parse Command.
20173   * @returns True if a command was found
20174   */
20175  parseCmd(a, b) {
20176    const cond1 = (a === 0x14 || a === 0x1c || a === 0x15 || a === 0x1d) && b >= 0x20 && b <= 0x2f;
20177    const cond2 = (a === 0x17 || a === 0x1f) && b >= 0x21 && b <= 0x23;
20178    if (!(cond1 || cond2)) {
20179      return false;
20180    }
20181    const chNr = a === 0x14 || a === 0x15 || a === 0x17 ? 1 : 2;
20182    const channel = this.channels[chNr];
20183    if (a === 0x14 || a === 0x15 || a === 0x1c || a === 0x1d) {
20184      if (b === 0x20) {
20185        channel.ccRCL();
20186      } else if (b === 0x21) {
20187        channel.ccBS();
20188      } else if (b === 0x22) {
20189        channel.ccAOF();
20190      } else if (b === 0x23) {
20191        channel.ccAON();
20192      } else if (b === 0x24) {
20193        channel.ccDER();
20194      } else if (b === 0x25) {
20195        channel.ccRU(2);
20196      } else if (b === 0x26) {
20197        channel.ccRU(3);
20198      } else if (b === 0x27) {
20199        channel.ccRU(4);
20200      } else if (b === 0x28) {
20201        channel.ccFON();
20202      } else if (b === 0x29) {
20203        channel.ccRDC();
20204      } else if (b === 0x2a) {
20205        channel.ccTR();
20206      } else if (b === 0x2b) {
20207        channel.ccRTD();
20208      } else if (b === 0x2c) {
20209        channel.ccEDM();
20210      } else if (b === 0x2d) {
20211        channel.ccCR();
20212      } else if (b === 0x2e) {
20213        channel.ccENM();
20214      } else if (b === 0x2f) {
20215        channel.ccEOC();
20216      }
20217    } else {
20218      // a == 0x17 || a == 0x1F
20219      channel.ccTO(b - 0x20);
20220    }
20221    this.currentChannel = chNr;
20222    return true;
20223  }
20224
20225  /**
20226   * Parse midrow styling command
20227   */
20228  parseMidrow(a, b) {
20229    let chNr = 0;
20230    if ((a === 0x11 || a === 0x19) && b >= 0x20 && b <= 0x2f) {
20231      if (a === 0x11) {
20232        chNr = 1;
20233      } else {
20234        chNr = 2;
20235      }
20236      if (chNr !== this.currentChannel) {
20237        this.logger.log(0, 'Mismatch channel in midrow parsing');
20238        return false;
20239      }
20240      const channel = this.channels[chNr];
20241      if (!channel) {
20242        return false;
20243      }
20244      channel.ccMIDROW(b);
20245      this.logger.log(3, () => 'MIDROW (' + numArrayToHexArray([a, b]) + ')');
20246      return true;
20247    }
20248    return false;
20249  }
20250
20251  /**
20252   * Parse Preable Access Codes (Table 53).
20253   * @returns {Boolean} Tells if PAC found
20254   */
20255  parsePAC(a, b) {
20256    let row;
20257    const case1 = (a >= 0x11 && a <= 0x17 || a >= 0x19 && a <= 0x1f) && b >= 0x40 && b <= 0x7f;
20258    const case2 = (a === 0x10 || a === 0x18) && b >= 0x40 && b <= 0x5f;
20259    if (!(case1 || case2)) {
20260      return false;
20261    }
20262    const chNr = a <= 0x17 ? 1 : 2;
20263    if (b >= 0x40 && b <= 0x5f) {
20264      row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a];
20265    } else {
20266      // 0x60 <= b <= 0x7F
20267      row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a];
20268    }
20269    const channel = this.channels[chNr];
20270    if (!channel) {
20271      return false;
20272    }
20273    channel.setPAC(this.interpretPAC(row, b));
20274    this.currentChannel = chNr;
20275    return true;
20276  }
20277
20278  /**
20279   * Interpret the second byte of the pac, and return the information.
20280   * @returns pacData with style parameters
20281   */
20282  interpretPAC(row, byte) {
20283    let pacIndex;
20284    const pacData = {
20285      color: null,
20286      italics: false,
20287      indent: null,
20288      underline: false,
20289      row: row
20290    };
20291    if (byte > 0x5f) {
20292      pacIndex = byte - 0x60;
20293    } else {
20294      pacIndex = byte - 0x40;
20295    }
20296    pacData.underline = (pacIndex & 1) === 1;
20297    if (pacIndex <= 0xd) {
20298      pacData.color = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'white'][Math.floor(pacIndex / 2)];
20299    } else if (pacIndex <= 0xf) {
20300      pacData.italics = true;
20301      pacData.color = 'white';
20302    } else {
20303      pacData.indent = Math.floor((pacIndex - 0x10) / 2) * 4;
20304    }
20305    return pacData; // Note that row has zero offset. The spec uses 1.
20306  }
20307
20308  /**
20309   * Parse characters.
20310   * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise.
20311   */
20312  parseChars(a, b) {
20313    let channelNr;
20314    let charCodes = null;
20315    let charCode1 = null;
20316    if (a >= 0x19) {
20317      channelNr = 2;
20318      charCode1 = a - 8;
20319    } else {
20320      channelNr = 1;
20321      charCode1 = a;
20322    }
20323    if (charCode1 >= 0x11 && charCode1 <= 0x13) {
20324      // Special character
20325      let oneCode;
20326      if (charCode1 === 0x11) {
20327        oneCode = b + 0x50;
20328      } else if (charCode1 === 0x12) {
20329        oneCode = b + 0x70;
20330      } else {
20331        oneCode = b + 0x90;
20332      }
20333      this.logger.log(2, () => "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr);
20334      charCodes = [oneCode];
20335    } else if (a >= 0x20 && a <= 0x7f) {
20336      charCodes = b === 0 ? [a] : [a, b];
20337    }
20338    if (charCodes) {
20339      this.logger.log(3, () => 'Char codes =  ' + numArrayToHexArray(charCodes).join(','));
20340    }
20341    return charCodes;
20342  }
20343
20344  /**
20345   * Parse extended background attributes as well as new foreground color black.
20346   * @returns True if background attributes are found
20347   */
20348  parseBackgroundAttributes(a, b) {
20349    const case1 = (a === 0x10 || a === 0x18) && b >= 0x20 && b <= 0x2f;
20350    const case2 = (a === 0x17 || a === 0x1f) && b >= 0x2d && b <= 0x2f;
20351    if (!(case1 || case2)) {
20352      return false;
20353    }
20354    let index;
20355    const bkgData = {};
20356    if (a === 0x10 || a === 0x18) {
20357      index = Math.floor((b - 0x20) / 2);
20358      bkgData.background = backgroundColors[index];
20359      if (b % 2 === 1) {
20360        bkgData.background = bkgData.background + '_semi';
20361      }
20362    } else if (b === 0x2d) {
20363      bkgData.background = 'transparent';
20364    } else {
20365      bkgData.foreground = 'black';
20366      if (b === 0x2f) {
20367        bkgData.underline = true;
20368      }
20369    }
20370    const chNr = a <= 0x17 ? 1 : 2;
20371    const channel = this.channels[chNr];
20372    channel.setBkgData(bkgData);
20373    return true;
20374  }
20375
20376  /**
20377   * Reset state of parser and its channels.
20378   */
20379  reset() {
20380    for (let i = 0; i < Object.keys(this.channels).length; i++) {
20381      const channel = this.channels[i];
20382      if (channel) {
20383        channel.reset();
20384      }
20385    }
20386    setLastCmd(null, null, this.cmdHistory);
20387  }
20388
20389  /**
20390   * Trigger the generation of a cue, and the start of a new one if displayScreens are not empty.
20391   */
20392  cueSplitAtTime(t) {
20393    for (let i = 0; i < this.channels.length; i++) {
20394      const channel = this.channels[i];
20395      if (channel) {
20396        channel.cueSplitAtTime(t);
20397      }
20398    }
20399  }
20400}
20401function setLastCmd(a, b, cmdHistory) {
20402  cmdHistory.a = a;
20403  cmdHistory.b = b;
20404}
20405function hasCmdRepeated(a, b, cmdHistory) {
20406  return cmdHistory.a === a && cmdHistory.b === b;
20407}
20408function createCmdHistory() {
20409  return {
20410    a: null,
20411    b: null
20412  };
20413}
20414
20415class OutputFilter {
20416  constructor(timelineController, trackName) {
20417    this.timelineController = void 0;
20418    this.cueRanges = [];
20419    this.trackName = void 0;
20420    this.startTime = null;
20421    this.endTime = null;
20422    this.screen = null;
20423    this.timelineController = timelineController;
20424    this.trackName = trackName;
20425  }
20426  dispatchCue() {
20427    if (this.startTime === null) {
20428      return;
20429    }
20430    this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges);
20431    this.startTime = null;
20432  }
20433  newCue(startTime, endTime, screen) {
20434    if (this.startTime === null || this.startTime > startTime) {
20435      this.startTime = startTime;
20436    }
20437    this.endTime = endTime;
20438    this.screen = screen;
20439    this.timelineController.createCaptionsTrack(this.trackName);
20440  }
20441  reset() {
20442    this.cueRanges = [];
20443    this.startTime = null;
vendor: 7,872 bytes, lines 20443-20720
20443
20444  }
20445}
20446
20447/**
20448 * Copyright 2013 vtt.js Contributors
20449 *
20450 * Licensed under the Apache License, Version 2.0 (the 'License');
20451 * you may not use this file except in compliance with the License.
20452 * You may obtain a copy of the License at
20453 *
20454 *   http://www.apache.org/licenses/LICENSE-2.0
20455 *
20456 * Unless required by applicable law or agreed to in writing, software
20457 * distributed under the License is distributed on an 'AS IS' BASIS,
20458 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20459 * See the License for the specific language governing permissions and
20460 * limitations under the License.
20461 */
20462
20463var VTTCue = (function () {
20464  if (optionalSelf != null && optionalSelf.VTTCue) {
20465    return self.VTTCue;
20466  }
20467  const AllowedDirections = ['', 'lr', 'rl'];
20468  const AllowedAlignments = ['start', 'middle', 'end', 'left', 'right'];
20469  function isAllowedValue(allowed, value) {
20470    if (typeof value !== 'string') {
20471      return false;
20472    }
20473    // necessary for assuring the generic conforms to the Array interface
20474    if (!Array.isArray(allowed)) {
20475      return false;
20476    }
20477    // reset the type so that the next narrowing works well
20478    const lcValue = value.toLowerCase();
20479    // use the allow list to narrow the type to a specific subset of strings
20480    if (~allowed.indexOf(lcValue)) {
20481      return lcValue;
20482    }
20483    return false;
20484  }
20485  function findDirectionSetting(value) {
20486    return isAllowedValue(AllowedDirections, value);
20487  }
20488  function findAlignSetting(value) {
20489    return isAllowedValue(AllowedAlignments, value);
20490  }
20491  function extend(obj, ...rest) {
20492    let i = 1;
20493    for (; i < arguments.length; i++) {
20494      const cobj = arguments[i];
20495      for (const p in cobj) {
20496        obj[p] = cobj[p];
20497      }
20498    }
20499    return obj;
20500  }
20501  function VTTCue(startTime, endTime, text) {
20502    const cue = this;
20503    const baseObj = {
20504      enumerable: true
20505    };
20506    /**
20507     * Shim implementation specific properties. These properties are not in
20508     * the spec.
20509     */
20510
20511    // Lets us know when the VTTCue's data has changed in such a way that we need
20512    // to recompute its display state. This lets us compute its display state
20513    // lazily.
20514    cue.hasBeenReset = false;
20515
20516    /**
20517     * VTTCue and TextTrackCue properties
20518     * http://dev.w3.org/html5/webvtt/#vttcue-interface
20519     */
20520
20521    let _id = '';
20522    let _pauseOnExit = false;
20523    let _startTime = startTime;
20524    let _endTime = endTime;
20525    let _text = text;
20526    let _region = null;
20527    let _vertical = '';
20528    let _snapToLines = true;
20529    let _line = 'auto';
20530    let _lineAlign = 'start';
20531    let _position = 50;
20532    let _positionAlign = 'middle';
20533    let _size = 50;
20534    let _align = 'middle';
20535    Object.defineProperty(cue, 'id', extend({}, baseObj, {
20536      get: function () {
20537        return _id;
20538      },
20539      set: function (value) {
20540        _id = '' + value;
20541      }
20542    }));
20543    Object.defineProperty(cue, 'pauseOnExit', extend({}, baseObj, {
20544      get: function () {
20545        return _pauseOnExit;
20546      },
20547      set: function (value) {
20548        _pauseOnExit = !!value;
20549      }
20550    }));
20551    Object.defineProperty(cue, 'startTime', extend({}, baseObj, {
20552      get: function () {
20553        return _startTime;
20554      },
20555      set: function (value) {
20556        if (typeof value !== 'number') {
20557          throw new TypeError('Start time must be set to a number.');
20558        }
20559        _startTime = value;
20560        this.hasBeenReset = true;
20561      }
20562    }));
20563    Object.defineProperty(cue, 'endTime', extend({}, baseObj, {
20564      get: function () {
20565        return _endTime;
20566      },
20567      set: function (value) {
20568        if (typeof value !== 'number') {
20569          throw new TypeError('End time must be set to a number.');
20570        }
20571        _endTime = value;
20572        this.hasBeenReset = true;
20573      }
20574    }));
20575    Object.defineProperty(cue, 'text', extend({}, baseObj, {
20576      get: function () {
20577        return _text;
20578      },
20579      set: function (value) {
20580        _text = '' + value;
20581        this.hasBeenReset = true;
20582      }
20583    }));
20584
20585    // todo: implement VTTRegion polyfill?
20586    Object.defineProperty(cue, 'region', extend({}, baseObj, {
20587      get: function () {
20588        return _region;
20589      },
20590      set: function (value) {
20591        _region = value;
20592        this.hasBeenReset = true;
20593      }
20594    }));
20595    Object.defineProperty(cue, 'vertical', extend({}, baseObj, {
20596      get: function () {
20597        return _vertical;
20598      },
20599      set: function (value) {
20600        const setting = findDirectionSetting(value);
20601        // Have to check for false because the setting an be an empty string.
20602        if (setting === false) {
20603          throw new SyntaxError('An invalid or illegal string was specified.');
20604        }
20605        _vertical = setting;
20606        this.hasBeenReset = true;
20607      }
20608    }));
20609    Object.defineProperty(cue, 'snapToLines', extend({}, baseObj, {
20610      get: function () {
20611        return _snapToLines;
20612      },
20613      set: function (value) {
20614        _snapToLines = !!value;
20615        this.hasBeenReset = true;
20616      }
20617    }));
20618    Object.defineProperty(cue, 'line', extend({}, baseObj, {
20619      get: function () {
20620        return _line;
20621      },
20622      set: function (value) {
20623        if (typeof value !== 'number' && value !== 'auto') {
20624          throw new SyntaxError('An invalid number or illegal string was specified.');
20625        }
20626        _line = value;
20627        this.hasBeenReset = true;
20628      }
20629    }));
20630    Object.defineProperty(cue, 'lineAlign', extend({}, baseObj, {
20631      get: function () {
20632        return _lineAlign;
20633      },
20634      set: function (value) {
20635        const setting = findAlignSetting(value);
20636        if (!setting) {
20637          throw new SyntaxError('An invalid or illegal string was specified.');
20638        }
20639        _lineAlign = setting;
20640        this.hasBeenReset = true;
20641      }
20642    }));
20643    Object.defineProperty(cue, 'position', extend({}, baseObj, {
20644      get: function () {
20645        return _position;
20646      },
20647      set: function (value) {
20648        if (value < 0 || value > 100) {
20649          throw new Error('Position must be between 0 and 100.');
20650        }
20651        _position = value;
20652        this.hasBeenReset = true;
20653      }
20654    }));
20655    Object.defineProperty(cue, 'positionAlign', extend({}, baseObj, {
20656      get: function () {
20657        return _positionAlign;
20658      },
20659      set: function (value) {
20660        const setting = findAlignSetting(value);
20661        if (!setting) {
20662          throw new SyntaxError('An invalid or illegal string was specified.');
20663        }
20664        _positionAlign = setting;
20665        this.hasBeenReset = true;
20666      }
20667    }));
20668    Object.defineProperty(cue, 'size', extend({}, baseObj, {
20669      get: function () {
20670        return _size;
20671      },
20672      set: function (value) {
20673        if (value < 0 || value > 100) {
20674          throw new Error('Size must be between 0 and 100.');
20675        }
20676        _size = value;
20677        this.hasBeenReset = true;
20678      }
20679    }));
20680    Object.defineProperty(cue, 'align', extend({}, baseObj, {
20681      get: function () {
20682        return _align;
20683      },
20684      set: function (value) {
20685        const setting = findAlignSetting(value);
20686        if (!setting) {
20687          throw new SyntaxError('An invalid or illegal string was specified.');
20688        }
20689        _align = setting;
20690        this.hasBeenReset = true;
20691      }
20692    }));
20693
20694    /**
20695     * Other <track> spec defined properties
20696     */
20697
20698    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
20699    cue.displayState = undefined;
20700  }
20701
20702  /**
20703   * VTTCue methods
20704   */
20705
20706  VTTCue.prototype.getCueAsHTML = function () {
20707    // Assume WebVTT.convertCueToDOMTree is on the global.
20708    const WebVTT = self.WebVTT;
20709    return WebVTT.convertCueToDOMTree(self, this.text);
20710  };
20711  // this is a polyfill hack
20712  return VTTCue;
20713})();
20714
20715/*
20716 * Source: https://github.com/mozilla/vtt.js/blob/master/dist/vtt.js
20717 */
20718
20719class StringDecoder {
20720  
20720// eslint-disable-next-line @typescript-eslint/no-unused-vars
vendor: 14,009 bytes, lines 20720-21156
20720
20721  decode(data, options) {
20722    if (!data) {
20723      return '';
20724    }
20725    if (typeof data !== 'string') {
20726      throw new Error('Error - expected string data.');
20727    }
20728    return decodeURIComponent(encodeURIComponent(data));
20729  }
20730}
20731
20732// Try to parse input as a time stamp.
20733function parseTimeStamp(input) {
20734  function computeSeconds(h, m, s, f) {
20735    return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + parseFloat(f || 0);
20736  }
20737  const m = input.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/);
20738  if (!m) {
20739    return null;
20740  }
20741  if (parseFloat(m[2]) > 59) {
20742    // Timestamp takes the form of [hours]:[minutes].[milliseconds]
20743    // First position is hours as it's over 59.
20744    return computeSeconds(m[2], m[3], 0, m[4]);
20745  }
20746  // Timestamp takes the form of [hours (optional)]:[minutes]:[seconds].[milliseconds]
20747  return computeSeconds(m[1], m[2], m[3], m[4]);
20748}
20749
20750// A settings object holds key/value pairs and will ignore anything but the first
20751// assignment to a specific key.
20752class Settings {
20753  constructor() {
20754    this.values = Object.create(null);
20755  }
20756  // Only accept the first assignment to any key.
20757  set(k, v) {
20758    if (!this.get(k) && v !== '') {
20759      this.values[k] = v;
20760    }
20761  }
20762  // Return the value for a key, or a default value.
20763  // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
20764  // a number of possible default values as properties where 'defaultKey' is
20765  // the key of the property that will be chosen; otherwise it's assumed to be
20766  // a single value.
20767  get(k, dflt, defaultKey) {
20768    if (defaultKey) {
20769      return this.has(k) ? this.values[k] : dflt[defaultKey];
20770    }
20771    return this.has(k) ? this.values[k] : dflt;
20772  }
20773  // Check whether we have a value for a key.
20774  has(k) {
20775    return k in this.values;
20776  }
20777  // Accept a setting if its one of the given alternatives.
20778  alt(k, v, a) {
20779    for (let n = 0; n < a.length; ++n) {
20780      if (v === a[n]) {
20781        this.set(k, v);
20782        break;
20783      }
20784    }
20785  }
20786  // Accept a setting if its a valid (signed) integer.
20787  integer(k, v) {
20788    if (/^-?\d+$/.test(v)) {
20789      // integer
20790      this.set(k, parseInt(v, 10));
20791    }
20792  }
20793  // Accept a setting if its a valid percentage.
20794  percent(k, v) {
20795    if (/^([\d]{1,3})(\.[\d]*)?%$/.test(v)) {
20796      const percent = parseFloat(v);
20797      if (percent >= 0 && percent <= 100) {
20798        this.set(k, percent);
20799        return true;
20800      }
20801    }
20802    return false;
20803  }
20804}
20805
20806// Helper function to parse input into groups separated by 'groupDelim', and
20807// interpret each group as a key/value pair separated by 'keyValueDelim'.
20808function parseOptions(input, callback, keyValueDelim, groupDelim) {
20809  const groups = groupDelim ? input.split(groupDelim) : [input];
20810  for (const i in groups) {
20811    if (typeof groups[i] !== 'string') {
20812      continue;
20813    }
20814    const kv = groups[i].split(keyValueDelim);
20815    if (kv.length !== 2) {
20816      continue;
20817    }
20818    const k = kv[0];
20819    const v = kv[1];
20820    callback(k, v);
20821  }
20822}
20823const defaults = new VTTCue(0, 0, '');
20824// 'middle' was changed to 'center' in the spec: https://github.com/w3c/webvtt/pull/244
20825//  Safari doesn't yet support this change, but FF and Chrome do.
20826const center = defaults.align === 'middle' ? 'middle' : 'center';
20827function parseCue(input, cue, regionList) {
20828  // Remember the original input if we need to throw an error.
20829  const oInput = input;
20830  // 4.1 WebVTT timestamp
20831  function consumeTimeStamp() {
20832    const ts = parseTimeStamp(input);
20833    if (ts === null) {
20834      throw new Error('Malformed timestamp: ' + oInput);
20835    }
20836
20837    // Remove time stamp from input.
20838    input = input.replace(/^[^\sa-zA-Z-]+/, '');
20839    return ts;
20840  }
20841
20842  // 4.4.2 WebVTT cue settings
20843  function consumeCueSettings(input, cue) {
20844    const settings = new Settings();
20845    parseOptions(input, function (k, v) {
20846      let vals;
20847      switch (k) {
20848        case 'region':
20849          // Find the last region we parsed with the same region id.
20850          for (let i = regionList.length - 1; i >= 0; i--) {
20851            if (regionList[i].id === v) {
20852              settings.set(k, regionList[i].region);
20853              break;
20854            }
20855          }
20856          break;
20857        case 'vertical':
20858          settings.alt(k, v, ['rl', 'lr']);
20859          break;
20860        case 'line':
20861          vals = v.split(',');
20862          settings.integer(k, vals[0]);
20863          if (settings.percent(k, vals[0])) {
20864            settings.set('snapToLines', false);
20865          }
20866          settings.alt(k, vals[0], ['auto']);
20867          if (vals.length === 2) {
20868            settings.alt('lineAlign', vals[1], ['start', center, 'end']);
20869          }
20870          break;
20871        case 'position':
20872          vals = v.split(',');
20873          settings.percent(k, vals[0]);
20874          if (vals.length === 2) {
20875            settings.alt('positionAlign', vals[1], ['start', center, 'end', 'line-left', 'line-right', 'auto']);
20876          }
20877          break;
20878        case 'size':
20879          settings.percent(k, v);
20880          break;
20881        case 'align':
20882          settings.alt(k, v, ['start', center, 'end', 'left', 'right']);
20883          break;
20884      }
20885    }, /:/, /\s/);
20886
20887    // Apply default values for any missing fields.
20888    cue.region = settings.get('region', null);
20889    cue.vertical = settings.get('vertical', '');
20890    let line = settings.get('line', 'auto');
20891    if (line === 'auto' && defaults.line === -1) {
20892      // set numeric line number for Safari
20893      line = -1;
20894    }
20895    cue.line = line;
20896    cue.lineAlign = settings.get('lineAlign', 'start');
20897    cue.snapToLines = settings.get('snapToLines', true);
20898    cue.size = settings.get('size', 100);
20899    cue.align = settings.get('align', center);
20900    let position = settings.get('position', 'auto');
20901    if (position === 'auto' && defaults.position === 50) {
20902      // set numeric position for Safari
20903      position = cue.align === 'start' || cue.align === 'left' ? 0 : cue.align === 'end' || cue.align === 'right' ? 100 : 50;
20904    }
20905    cue.position = position;
20906  }
20907  function skipWhitespace() {
20908    input = input.replace(/^\s+/, '');
20909  }
20910
20911  // 4.1 WebVTT cue timings.
20912  skipWhitespace();
20913  cue.startTime = consumeTimeStamp(); // (1) collect cue start time
20914  skipWhitespace();
20915  if (input.slice(0, 3) !== '-->') {
20916    // (3) next characters must match '-->'
20917    throw new Error("Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
20918  }
20919  input = input.slice(3);
20920  skipWhitespace();
20921  cue.endTime = consumeTimeStamp(); // (5) collect cue end time
20922
20923  // 4.1 WebVTT cue settings list.
20924  skipWhitespace();
20925  consumeCueSettings(input, cue);
20926}
20927function fixLineBreaks(input) {
20928  return input.replace(/<br(?: \/)?>/gi, '\n');
20929}
20930class VTTParser {
20931  constructor() {
20932    this.state = 'INITIAL';
20933    this.buffer = '';
20934    this.decoder = new StringDecoder();
20935    this.regionList = [];
20936    this.cue = null;
20937    this.oncue = void 0;
20938    this.onparsingerror = void 0;
20939    this.onflush = void 0;
20940  }
20941  parse(data) {
20942    const _this = this;
20943
20944    // If there is no data then we won't decode it, but will just try to parse
20945    // whatever is in buffer already. This may occur in circumstances, for
20946    // example when flush() is called.
20947    if (data) {
20948      // Try to decode the data that we received.
20949      _this.buffer += _this.decoder.decode(data, {
20950        stream: true
20951      });
20952    }
20953    function collectNextLine() {
20954      let buffer = _this.buffer;
20955      let pos = 0;
20956      buffer = fixLineBreaks(buffer);
20957      while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
20958        ++pos;
20959      }
20960      const line = buffer.slice(0, pos);
20961      // Advance the buffer early in case we fail below.
20962      if (buffer[pos] === '\r') {
20963        ++pos;
20964      }
20965      if (buffer[pos] === '\n') {
20966        ++pos;
20967      }
20968      _this.buffer = buffer.slice(pos);
20969      return line;
20970    }
20971
20972    // 3.2 WebVTT metadata header syntax
20973    function parseHeader(input) {
20974      parseOptions(input, function (k, v) {
20975        // switch (k) {
20976        // case 'region':
20977        // 3.3 WebVTT region metadata header syntax
20978        // console.log('parse region', v);
20979        // parseRegion(v);
20980        // break;
20981        // }
20982      }, /:/);
20983    }
20984
20985    // 5.1 WebVTT file parsing.
20986    try {
20987      let line = '';
20988      if (_this.state === 'INITIAL') {
20989        // We can't start parsing until we have the first line.
20990        if (!/\r\n|\n/.test(_this.buffer)) {
20991          return this;
20992        }
20993        line = collectNextLine();
20994        // strip of UTF-8 BOM if any
20995        // https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
20996        const m = line.match(/^()?WEBVTT([ \t].*)?$/);
20997        if (!(m != null && m[0])) {
20998          throw new Error('Malformed WebVTT signature.');
20999        }
21000        _this.state = 'HEADER';
21001      }
21002      let alreadyCollectedLine = false;
21003      while (_this.buffer) {
21004        // We can't parse a line until we have the full line.
21005        if (!/\r\n|\n/.test(_this.buffer)) {
21006          return this;
21007        }
21008        if (!alreadyCollectedLine) {
21009          line = collectNextLine();
21010        } else {
21011          alreadyCollectedLine = false;
21012        }
21013        switch (_this.state) {
21014          case 'HEADER':
21015            // 13-18 - Allow a header (metadata) under the WEBVTT line.
21016            if (/:/.test(line)) {
21017              parseHeader(line);
21018            } else if (!line) {
21019              // An empty line terminates the header and starts the body (cues).
21020              _this.state = 'ID';
21021            }
21022            continue;
21023          case 'NOTE':
21024            // Ignore NOTE blocks.
21025            if (!line) {
21026              _this.state = 'ID';
21027            }
21028            continue;
21029          case 'ID':
21030            // Check for the start of NOTE blocks.
21031            if (/^NOTE($|[ \t])/.test(line)) {
21032              _this.state = 'NOTE';
21033              break;
21034            }
21035            // 19-29 - Allow any number of line terminators, then initialize new cue values.
21036            if (!line) {
21037              continue;
21038            }
21039            _this.cue = new VTTCue(0, 0, '');
21040            _this.state = 'CUE';
21041            // 30-39 - Check if self line contains an optional identifier or timing data.
21042            if (line.indexOf('-->') === -1) {
21043              _this.cue.id = line;
21044              continue;
21045            }
21046          // Process line as start of a cue.
21047          /* falls through */
21048          case 'CUE':
21049            // 40 - Collect cue timings and settings.
21050            if (!_this.cue) {
21051              _this.state = 'BADCUE';
21052              continue;
21053            }
21054            try {
21055              parseCue(line, _this.cue, _this.regionList);
21056            } catch (e) {
21057              // In case of an error ignore rest of the cue.
21058              _this.cue = null;
21059              _this.state = 'BADCUE';
21060              continue;
21061            }
21062            _this.state = 'CUETEXT';
21063            continue;
21064          case 'CUETEXT':
21065            {
21066              const hasSubstring = line.indexOf('-->') !== -1;
21067              // 34 - If we have an empty line then report the cue.
21068              // 35 - If we have the special substring '-->' then report the cue,
21069              // but do not collect the line as we need to process the current
21070              // one as a new cue.
21071              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
21072                // We are done parsing self cue.
21073                if (_this.oncue && _this.cue) {
21074                  _this.oncue(_this.cue);
21075                }
21076                _this.cue = null;
21077                _this.state = 'ID';
21078                continue;
21079              }
21080              if (_this.cue === null) {
21081                continue;
21082              }
21083              if (_this.cue.text) {
21084                _this.cue.text += '\n';
21085              }
21086              _this.cue.text += line;
21087            }
21088            continue;
21089          case 'BADCUE':
21090            // 54-62 - Collect and discard the remaining cue.
21091            if (!line) {
21092              _this.state = 'ID';
21093            }
21094        }
21095      }
21096    } catch (e) {
21097      // If we are currently parsing a cue, report what we have.
21098      if (_this.state === 'CUETEXT' && _this.cue && _this.oncue) {
21099        _this.oncue(_this.cue);
21100      }
21101      _this.cue = null;
21102      // Enter BADWEBVTT state if header was not parsed correctly otherwise
21103      // another exception occurred so enter BADCUE state.
21104      _this.state = _this.state === 'INITIAL' ? 'BADWEBVTT' : 'BADCUE';
21105    }
21106    return this;
21107  }
21108  flush() {
21109    const _this = this;
21110    try {
21111      // Finish decoding the stream.
21112      // _this.buffer += _this.decoder.decode();
21113      // Synthesize the end of the current cue or region.
21114      if (_this.cue || _this.state === 'HEADER') {
21115        _this.buffer += '\n\n';
21116        _this.parse();
21117      }
21118      // If we've flushed, parsed, and we're still on the INITIAL state then
21119      // that means we don't have enough of the stream to parse the first
21120      // line.
21121      if (_this.state === 'INITIAL' || _this.state === 'BADWEBVTT') {
21122        throw new Error('Malformed WebVTT signature.');
21123      }
21124    } catch (e) {
21125      if (_this.onparsingerror) {
21126        _this.onparsingerror(e);
21127      }
21128    }
21129    if (_this.onflush) {
21130      _this.onflush();
21131    }
21132    return this;
21133  }
21134}
21135
21136const LINEBREAKS = /\r\n|\n\r|\n|\r/g;
21137
21138// String.prototype.startsWith is not supported in IE11
21139const startsWith = function startsWith(inputString, searchString, position = 0) {
21140  return inputString.slice(position, position + searchString.length) === searchString;
21141};
21142const cueString2millis = function cueString2millis(timeString) {
21143  let ts = parseInt(timeString.slice(-3));
21144  const secs = parseInt(timeString.slice(-6, -4));
21145  const mins = parseInt(timeString.slice(-9, -7));
21146  const hours = timeString.length > 9 ? parseInt(timeString.substring(0, timeString.indexOf(':'))) : 0;
21147  if (!isFiniteNumber(ts) || !isFiniteNumber(secs) || !isFiniteNumber(mins) || !isFiniteNumber(hours)) {
21148    throw Error(`Malformed X-TIMESTAMP-MAP: Local:${timeString}`);
21149  }
21150  ts += 1000 * secs;
21151  ts += 60 * 1000 * mins;
21152  ts += 60 * 60 * 1000 * hours;
21153  return ts;
21154};
21155
21156// 
vendor: 11,662 bytes, lines 21156-21496
21156From https://github.com/darkskyapp/string-hash
21157const hash = function hash(text) {
21158  let _hash = 5381;
21159  let i = text.length;
21160  while (i) {
21161    _hash = _hash * 33 ^ text.charCodeAt(--i);
21162  }
21163  return (_hash >>> 0).toString();
21164};
21165
21166// Create a unique hash id for a cue based on start/end times and text.
21167// This helps timeline-controller to avoid showing repeated captions.
21168function generateCueId(startTime, endTime, text) {
21169  return hash(startTime.toString()) + hash(endTime.toString()) + hash(text);
21170}
21171const calculateOffset = function calculateOffset(vttCCs, cc, presentationTime) {
21172  let currCC = vttCCs[cc];
21173  let prevCC = vttCCs[currCC.prevCC];
21174
21175  // This is the first discontinuity or cues have been processed since the last discontinuity
21176  // Offset = current discontinuity time
21177  if (!prevCC || !prevCC.new && currCC.new) {
21178    vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start;
21179    currCC.new = false;
21180    return;
21181  }
21182
21183  // There have been discontinuities since cues were last parsed.
21184  // Offset = time elapsed
21185  while ((_prevCC = prevCC) != null && _prevCC.new) {
21186    var _prevCC;
21187    vttCCs.ccOffset += currCC.start - prevCC.start;
21188    currCC.new = false;
21189    currCC = prevCC;
21190    prevCC = vttCCs[currCC.prevCC];
21191  }
21192  vttCCs.presentationOffset = presentationTime;
21193};
21194function parseWebVTT(vttByteArray, initPTS, vttCCs, cc, timeOffset, callBack, errorCallBack) {
21195  const parser = new VTTParser();
21196  // Convert byteArray into string, replacing any somewhat exotic linefeeds with "\n", then split on that character.
21197  // Uint8Array.prototype.reduce is not implemented in IE11
21198  const vttLines = utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, '\n').split('\n');
21199  const cues = [];
21200  const init90kHz = initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale) : 0;
21201  let cueTime = '00:00.000';
21202  let timestampMapMPEGTS = 0;
21203  let timestampMapLOCAL = 0;
21204  let parsingError;
21205  let inHeader = true;
21206  parser.oncue = function (cue) {
21207    // Adjust cue timing; clamp cues to start no earlier than - and drop cues that don't end after - 0 on timeline.
21208    const currCC = vttCCs[cc];
21209    let cueOffset = vttCCs.ccOffset;
21210
21211    // Calculate subtitle PTS offset
21212    const webVttMpegTsMapOffset = (timestampMapMPEGTS - init90kHz) / 90000;
21213
21214    // Update offsets for new discontinuities
21215    if (currCC != null && currCC.new) {
21216      if (timestampMapLOCAL !== undefined) {
21217        // When local time is provided, offset = discontinuity start time - local time
21218        cueOffset = vttCCs.ccOffset = currCC.start;
21219      } else {
21220        calculateOffset(vttCCs, cc, webVttMpegTsMapOffset);
21221      }
21222    }
21223    if (webVttMpegTsMapOffset) {
21224      if (!initPTS) {
21225        parsingError = new Error('Missing initPTS for VTT MPEGTS');
21226        return;
21227      }
21228      // If we have MPEGTS, offset = presentation time + discontinuity offset
21229      cueOffset = webVttMpegTsMapOffset - vttCCs.presentationOffset;
21230    }
21231    const duration = cue.endTime - cue.startTime;
21232    const startTime = normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 90000, timeOffset * 90000) / 90000;
21233    cue.startTime = Math.max(startTime, 0);
21234    cue.endTime = Math.max(startTime + duration, 0);
21235
21236    //trim trailing webvtt block whitespaces
21237    const text = cue.text.trim();
21238
21239    // Fix encoding of special characters
21240    cue.text = decodeURIComponent(encodeURIComponent(text));
21241
21242    // If the cue was not assigned an id from the VTT file (line above the content), create one.
21243    if (!cue.id) {
21244      cue.id = generateCueId(cue.startTime, cue.endTime, text);
21245    }
21246    if (cue.endTime > 0) {
21247      cues.push(cue);
21248    }
21249  };
21250  parser.onparsingerror = function (error) {
21251    parsingError = error;
21252  };
21253  parser.onflush = function () {
21254    if (parsingError) {
21255      errorCallBack(parsingError);
21256      return;
21257    }
21258    callBack(cues);
21259  };
21260
21261  // Go through contents line by line.
21262  vttLines.forEach(line => {
21263    if (inHeader) {
21264      // Look for X-TIMESTAMP-MAP in header.
21265      if (startsWith(line, 'X-TIMESTAMP-MAP=')) {
21266        // Once found, no more are allowed anyway, so stop searching.
21267        inHeader = false;
21268        // Extract LOCAL and MPEGTS.
21269        line.slice(16).split(',').forEach(timestamp => {
21270          if (startsWith(timestamp, 'LOCAL:')) {
21271            cueTime = timestamp.slice(6);
21272          } else if (startsWith(timestamp, 'MPEGTS:')) {
21273            timestampMapMPEGTS = parseInt(timestamp.slice(7));
21274          }
21275        });
21276        try {
21277          // Convert cue time to seconds
21278          timestampMapLOCAL = cueString2millis(cueTime) / 1000;
21279        } catch (error) {
21280          parsingError = error;
21281        }
21282        // Return without parsing X-TIMESTAMP-MAP line.
21283        return;
21284      } else if (line === '') {
21285        inHeader = false;
21286      }
21287    }
21288    // Parse line by default.
21289    parser.parse(line + '\n');
21290  });
21291  parser.flush();
21292}
21293
21294const IMSC1_CODEC = 'stpp.ttml.im1t';
21295
21296// Time format: h:m:s:frames(.subframes)
21297const HMSF_REGEX = /^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/;
21298
21299// Time format: hours, minutes, seconds, milliseconds, frames, ticks
21300const TIME_UNIT_REGEX = /^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/;
21301const textAlignToLineAlign = {
21302  left: 'start',
21303  center: 'center',
21304  right: 'end',
21305  start: 'start',
21306  end: 'end'
21307};
21308function parseIMSC1(payload, initPTS, callBack, errorCallBack) {
21309  const results = findBox(new Uint8Array(payload), ['mdat']);
21310  if (results.length === 0) {
21311    errorCallBack(new Error('Could not parse IMSC1 mdat'));
21312    return;
21313  }
21314  const ttmlList = results.map(mdat => utf8ArrayToStr(mdat));
21315  const syncTime = toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale);
21316  try {
21317    ttmlList.forEach(ttml => callBack(parseTTML(ttml, syncTime)));
21318  } catch (error) {
21319    errorCallBack(error);
21320  }
21321}
21322function parseTTML(ttml, syncTime) {
21323  const parser = new DOMParser();
21324  const xmlDoc = parser.parseFromString(ttml, 'text/xml');
21325  const tt = xmlDoc.getElementsByTagName('tt')[0];
21326  if (!tt) {
21327    throw new Error('Invalid ttml');
21328  }
21329  const defaultRateInfo = {
21330    frameRate: 30,
21331    subFrameRate: 1,
21332    frameRateMultiplier: 0,
21333    tickRate: 0
21334  };
21335  const rateInfo = Object.keys(defaultRateInfo).reduce((result, key) => {
21336    result[key] = tt.getAttribute(`ttp:${key}`) || defaultRateInfo[key];
21337    return result;
21338  }, {});
21339  const trim = tt.getAttribute('xml:space') !== 'preserve';
21340  const styleElements = collectionToDictionary(getElementCollection(tt, 'styling', 'style'));
21341  const regionElements = collectionToDictionary(getElementCollection(tt, 'layout', 'region'));
21342  const cueElements = getElementCollection(tt, 'body', '[begin]');
21343  return [].map.call(cueElements, cueElement => {
21344    const cueText = getTextContent(cueElement, trim);
21345    if (!cueText || !cueElement.hasAttribute('begin')) {
21346      return null;
21347    }
21348    const startTime = parseTtmlTime(cueElement.getAttribute('begin'), rateInfo);
21349    const duration = parseTtmlTime(cueElement.getAttribute('dur'), rateInfo);
21350    let endTime = parseTtmlTime(cueElement.getAttribute('end'), rateInfo);
21351    if (startTime === null) {
21352      throw timestampParsingError(cueElement);
21353    }
21354    if (endTime === null) {
21355      if (duration === null) {
21356        throw timestampParsingError(cueElement);
21357      }
21358      endTime = startTime + duration;
21359    }
21360    const cue = new VTTCue(startTime - syncTime, endTime - syncTime, cueText);
21361    cue.id = generateCueId(cue.startTime, cue.endTime, cue.text);
21362    const region = regionElements[cueElement.getAttribute('region')];
21363    const style = styleElements[cueElement.getAttribute('style')];
21364
21365    // Apply styles to cue
21366    const styles = getTtmlStyles(region, style, styleElements);
21367    const {
21368      textAlign
21369    } = styles;
21370    if (textAlign) {
21371      // cue.positionAlign not settable in FF~2016
21372      const lineAlign = textAlignToLineAlign[textAlign];
21373      if (lineAlign) {
21374        cue.lineAlign = lineAlign;
21375      }
21376      cue.align = textAlign;
21377    }
21378    _extends(cue, styles);
21379    return cue;
21380  }).filter(cue => cue !== null);
21381}
21382function getElementCollection(fromElement, parentName, childName) {
21383  const parent = fromElement.getElementsByTagName(parentName)[0];
21384  if (parent) {
21385    return [].slice.call(parent.querySelectorAll(childName));
21386  }
21387  return [];
21388}
21389function collectionToDictionary(elementsWithId) {
21390  return elementsWithId.reduce((dict, element) => {
21391    const id = element.getAttribute('xml:id');
21392    if (id) {
21393      dict[id] = element;
21394    }
21395    return dict;
21396  }, {});
21397}
21398function getTextContent(element, trim) {
21399  return [].slice.call(element.childNodes).reduce((str, node, i) => {
21400    var _node$childNodes;
21401    if (node.nodeName === 'br' && i) {
21402      return str + '\n';
21403    }
21404    if ((_node$childNodes = node.childNodes) != null && _node$childNodes.length) {
21405      return getTextContent(node, trim);
21406    } else if (trim) {
21407      return str + node.textContent.trim().replace(/\s+/g, ' ');
21408    }
21409    return str + node.textContent;
21410  }, '');
21411}
21412function getTtmlStyles(region, style, styleElements) {
21413  const ttsNs = 'http://www.w3.org/ns/ttml#styling';
21414  let regionStyle = null;
21415  const styleAttributes = ['displayAlign', 'textAlign', 'color', 'backgroundColor', 'fontSize', 'fontFamily'
21416  // 'fontWeight',
21417  // 'lineHeight',
21418  // 'wrapOption',
21419  // 'fontStyle',
21420  // 'direction',
21421  // 'writingMode'
21422  ];
21423  const regionStyleName = region != null && region.hasAttribute('style') ? region.getAttribute('style') : null;
21424  if (regionStyleName && styleElements.hasOwnProperty(regionStyleName)) {
21425    regionStyle = styleElements[regionStyleName];
21426  }
21427  return styleAttributes.reduce((styles, name) => {
21428    const value = getAttributeNS(style, ttsNs, name) || getAttributeNS(region, ttsNs, name) || getAttributeNS(regionStyle, ttsNs, name);
21429    if (value) {
21430      styles[name] = value;
21431    }
21432    return styles;
21433  }, {});
21434}
21435function getAttributeNS(element, ns, name) {
21436  if (!element) {
21437    return null;
21438  }
21439  return element.hasAttributeNS(ns, name) ? element.getAttributeNS(ns, name) : null;
21440}
21441function timestampParsingError(node) {
21442  return new Error(`Could not parse ttml timestamp ${node}`);
21443}
21444function parseTtmlTime(timeAttributeValue, rateInfo) {
21445  if (!timeAttributeValue) {
21446    return null;
21447  }
21448  let seconds = parseTimeStamp(timeAttributeValue);
21449  if (seconds === null) {
21450    if (HMSF_REGEX.test(timeAttributeValue)) {
21451      seconds = parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo);
21452    } else if (TIME_UNIT_REGEX.test(timeAttributeValue)) {
21453      seconds = parseTimeUnits(timeAttributeValue, rateInfo);
21454    }
21455  }
21456  return seconds;
21457}
21458function parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo) {
21459  const m = HMSF_REGEX.exec(timeAttributeValue);
21460  const frames = (m[4] | 0) + (m[5] | 0) / rateInfo.subFrameRate;
21461  return (m[1] | 0) * 3600 + (m[2] | 0) * 60 + (m[3] | 0) + frames / rateInfo.frameRate;
21462}
21463function parseTimeUnits(timeAttributeValue, rateInfo) {
21464  const m = TIME_UNIT_REGEX.exec(timeAttributeValue);
21465  const value = Number(m[1]);
21466  const unit = m[2];
21467  switch (unit) {
21468    case 'h':
21469      return value * 3600;
21470    case 'm':
21471      return value * 60;
21472    case 'ms':
21473      return value * 1000;
21474    case 'f':
21475      return value / rateInfo.frameRate;
21476    case 't':
21477      return value / rateInfo.tickRate;
21478  }
21479  return value;
21480}
21481
21482class TimelineController {
21483  constructor(hls) {
21484    this.hls = void 0;
21485    this.media = null;
21486    this.config = void 0;
21487    this.enabled = true;
21488    this.Cues = void 0;
21489    this.textTracks = [];
21490    this.tracks = [];
21491    this.initPTS = [];
21492    this.unparsedVttFrags = [];
21493    this.captionsTracks = {};
21494    this.nonNativeCaptionsTracks = {};
21495    this.cea608Parser1 = void 0;
21496    this.cea608Parser2 = void 0;
21497    this.lastCc = -1;
21498    // Last video (CEA-608) fragment CC
21499    this.lastSn = -1;
21500    // Last video (CEA-608) fragment MSN
21501    this.lastPartIndex = -1;
21502    // Last video (CEA-608) fragment Part Index
21503    this.prevCC = -1;
21504    // Last subtitle fragment CC
21505    this.vttCCs = newVTTCCs();
21506    this.captionsProperties = void 0;
21507    this.hls = hls;
21508    this.config = hls.config;
21509    this.Cues = hls.config.cueHandler;
21510    this.captionsProperties = {
21511      textTrack1: {
21512        label: this.config.captionsTextTrack1Label,
21513        languageCode: this.config.captionsTextTrack1LanguageCode
21514      },
21515      textTrack2: {
21516        label: this.config.captionsTextTrack2Label,
21517        languageCode: this.config.captionsTextTrack2LanguageCode
21518      },
21519      textTrack3: {
21520        label: this.config.captionsTextTrack3Label,
21521        languageCode: this.config.captionsTextTrack3LanguageCode
21522      },
21523      textTrack4: {
21524        label: this.config.captionsTextTrack4Label,
21525        languageCode: this.config.captionsTextTrack4LanguageCode
21526      }
21527    };
21528    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
21529    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
21530    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
21531    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
21532    hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
21533    hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
21534    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
21535    hls.on(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);
21536    hls.on(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);
21537    hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
21538    hls.on(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
21539    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
21540  }
21541  destroy() {
21542    const {
21543      hls
21544    } = this;
21545    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
21546    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
21547    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
21548    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
21549    hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
21550    hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
21551    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
21552    hls.off(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);
21553    hls.off(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);
21554    hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
21555    hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
21556    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
21557    // @ts-ignore
21558    this.hls = this.config = null;
21559    this.cea608Parser1 = this.cea608Parser2 = undefined;
21560  }
21561  initCea608Parsers() {
21562    if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
21563      const channel1 = new OutputFilter(this, 'textTrack1');
21564      const channel2 = new OutputFilter(this, 'textTrack2');
21565      const channel3 = new OutputFilter(this, 'textTrack3');
21566      const channel4 = new OutputFilter(this, 'textTrack4');
21567      this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
21568      this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
21569    }
21570  }
21571  addCues(trackName, startTime, endTime, screen, cueRanges) {
21572    // skip cues which overlap more than 50% with previously parsed time ranges
21573    let merged = false;
21574    for (let i = cueRanges.length; i--;) {
21575      const cueRange = cueRanges[i];
21576      const overlap = intersection(cueRange[0], cueRange[1], startTime, endTime);
21577      if (overlap >= 0) {
21578        cueRange[0] = Math.min(cueRange[0], startTime);
21579        cueRange[1] = Math.max(cueRange[1], endTime);
21580        merged = true;
21581        if (overlap / (endTime - startTime) > 0.5) {
21582          return;
21583        }
21584      }
21585    }
21586    if (!merged) {
21587      cueRanges.push([startTime, endTime]);
21588    }
21589    if (this.config.renderTextTracksNatively) {
21590      const track = this.captionsTracks[trackName];
21591      this.Cues.newCue(track, startTime, endTime, screen);
21592    } else {
21593      const cues = this.Cues.newCue(null, startTime, endTime, screen);
21594      this.hls.trigger(Events.CUES_PARSED, {
21595        type: 'captions',
21596        cues,
21597        track: trackName
21598      });
21599    }
21600  }
21601
21602  // Triggered when an initial PTS is found; used for synchronisation of WebVTT.
21603  onInitPtsFound(event, {
21604    frag,
21605    id,
21606    initPTS,
21607    timescale
21608  }) {
21609    const {
21610      unparsedVttFrags
21611    } = this;
21612    if (id === 'main') {
21613      this.initPTS[frag.cc] = {
21614        baseTime: initPTS,
21615        timescale
21616      };
21617    }
21618
21619    // Due to asynchronous processing, initial PTS may arrive later than the first VTT fragments are loaded.
21620    // Parse any unparsed fragments upon receiving the initial PTS.
21621    if (unparsedVttFrags.length) {
21622      this.unparsedVttFrags = [];
vendor: 12,390 bytes, lines 21623-21996
21623      unparsedVttFrags.forEach(frag => {
21624        this.onFragLoaded(Events.FRAG_LOADED, frag);
21625      });
21626    }
21627  }
21628  getExistingTrack(label, language) {
21629    const {
21630      media
21631    } = this;
21632    if (media) {
21633      for (let i = 0; i < media.textTracks.length; i++) {
21634        const textTrack = media.textTracks[i];
21635        if (canReuseVttTextTrack(textTrack, {
21636          name: label,
21637          lang: language})) {
21638          return textTrack;
21639        }
21640      }
21641    }
21642    return null;
21643  }
21644  createCaptionsTrack(trackName) {
21645    if (this.config.renderTextTracksNatively) {
21646      this.createNativeTrack(trackName);
21647    } else {
21648      this.createNonNativeTrack(trackName);
21649    }
21650  }
21651  createNativeTrack(trackName) {
21652    if (this.captionsTracks[trackName]) {
21653      return;
21654    }
21655    const {
21656      captionsProperties,
21657      captionsTracks,
21658      media
21659    } = this;
21660    const {
21661      label,
21662      languageCode
21663    } = captionsProperties[trackName];
21664    // Enable reuse of existing text track.
21665    const existingTrack = this.getExistingTrack(label, languageCode);
21666    if (!existingTrack) {
21667      const textTrack = this.createTextTrack('captions', label, languageCode);
21668      if (textTrack) {
21669        // Set a special property on the track so we know it's managed by Hls.js
21670        textTrack[trackName] = true;
21671        captionsTracks[trackName] = textTrack;
21672      }
21673    } else {
21674      captionsTracks[trackName] = existingTrack;
21675      clearCurrentCues(captionsTracks[trackName]);
21676      sendAddTrackEvent(captionsTracks[trackName], media);
21677    }
21678  }
21679  createNonNativeTrack(trackName) {
21680    if (this.nonNativeCaptionsTracks[trackName]) {
21681      return;
21682    }
21683    // Create a list of a single track for the provider to consume
21684    const trackProperties = this.captionsProperties[trackName];
21685    if (!trackProperties) {
21686      return;
21687    }
21688    const label = trackProperties.label;
21689    const track = {
21690      _id: trackName,
21691      label,
21692      kind: 'captions',
21693      default: trackProperties.media ? !!trackProperties.media.default : false,
21694      closedCaptions: trackProperties.media
21695    };
21696    this.nonNativeCaptionsTracks[trackName] = track;
21697    this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {
21698      tracks: [track]
21699    });
21700  }
21701  createTextTrack(kind, label, lang) {
21702    const media = this.media;
21703    if (!media) {
21704      return;
21705    }
21706    return media.addTextTrack(kind, label, lang);
21707  }
21708  onMediaAttaching(event, data) {
21709    this.media = data.media;
21710    this._cleanTracks();
21711  }
21712  onMediaDetaching() {
21713    const {
21714      captionsTracks
21715    } = this;
21716    Object.keys(captionsTracks).forEach(trackName => {
21717      clearCurrentCues(captionsTracks[trackName]);
21718      delete captionsTracks[trackName];
21719    });
21720    this.nonNativeCaptionsTracks = {};
21721  }
21722  onManifestLoading() {
21723    // Detect discontinuity in video fragment (CEA-608) parsing
21724    this.lastCc = -1;
21725    this.lastSn = -1;
21726    this.lastPartIndex = -1;
21727    // Detect discontinuity in subtitle manifests
21728    this.prevCC = -1;
21729    this.vttCCs = newVTTCCs();
21730    // Reset tracks
21731    this._cleanTracks();
21732    this.tracks = [];
21733    this.captionsTracks = {};
21734    this.nonNativeCaptionsTracks = {};
21735    this.textTracks = [];
21736    this.unparsedVttFrags = [];
21737    this.initPTS = [];
21738    if (this.cea608Parser1 && this.cea608Parser2) {
21739      this.cea608Parser1.reset();
21740      this.cea608Parser2.reset();
21741    }
21742  }
21743  _cleanTracks() {
21744    // clear outdated subtitles
21745    const {
21746      media
21747    } = this;
21748    if (!media) {
21749      return;
21750    }
21751    const textTracks = media.textTracks;
21752    if (textTracks) {
21753      for (let i = 0; i < textTracks.length; i++) {
21754        clearCurrentCues(textTracks[i]);
21755      }
21756    }
21757  }
21758  onSubtitleTracksUpdated(event, data) {
21759    const tracks = data.subtitleTracks || [];
21760    const hasIMSC1 = tracks.some(track => track.textCodec === IMSC1_CODEC);
21761    if (this.config.enableWebVTT || hasIMSC1 && this.config.enableIMSC1) {
21762      const listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks);
21763      if (listIsIdentical) {
21764        this.tracks = tracks;
21765        return;
21766      }
21767      this.textTracks = [];
21768      this.tracks = tracks;
21769      if (this.config.renderTextTracksNatively) {
21770        const media = this.media;
21771        const inUseTracks = media ? filterSubtitleTracks(media.textTracks) : null;
21772        this.tracks.forEach((track, index) => {
21773          // Reuse tracks with the same label and lang, but do not reuse 608/708 tracks
21774          let textTrack;
21775          if (inUseTracks) {
21776            let inUseTrack = null;
21777            for (let i = 0; i < inUseTracks.length; i++) {
21778              if (inUseTracks[i] && canReuseVttTextTrack(inUseTracks[i], track)) {
21779                inUseTrack = inUseTracks[i];
21780                inUseTracks[i] = null;
21781                break;
21782              }
21783            }
21784            if (inUseTrack) {
21785              textTrack = inUseTrack;
21786            }
21787          }
21788          if (textTrack) {
21789            clearCurrentCues(textTrack);
21790          } else {
21791            const textTrackKind = captionsOrSubtitlesFromCharacteristics(track);
21792            textTrack = this.createTextTrack(textTrackKind, track.name, track.lang);
21793            if (textTrack) {
21794              textTrack.mode = 'disabled';
21795            }
21796          }
21797          if (textTrack) {
21798            this.textTracks.push(textTrack);
21799          }
21800        });
21801        // Warn when video element has captions or subtitle TextTracks carried over from another source
21802        if (inUseTracks != null && inUseTracks.length) {
21803          const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
21804          if (unusedTextTracks.length) {
21805            logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
21806          }
21807        }
21808      } else if (this.tracks.length) {
21809        // Create a list of tracks for the provider to consume
21810        const tracksList = this.tracks.map(track => {
21811          return {
21812            label: track.name,
21813            kind: track.type.toLowerCase(),
21814            default: track.default,
21815            subtitleTrack: track
21816          };
21817        });
21818        this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {
21819          tracks: tracksList
21820        });
21821      }
21822    }
21823  }
21824  onManifestLoaded(event, data) {
21825    if (this.config.enableCEA708Captions && data.captions) {
21826      data.captions.forEach(captionsTrack => {
21827        const instreamIdMatch = /(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId);
21828        if (!instreamIdMatch) {
21829          return;
21830        }
21831        const trackName = `textTrack${instreamIdMatch[1]}`;
21832        const trackProperties = this.captionsProperties[trackName];
21833        if (!trackProperties) {
21834          return;
21835        }
21836        trackProperties.label = captionsTrack.name;
21837        if (captionsTrack.lang) {
21838          // optional attribute
21839          trackProperties.languageCode = captionsTrack.lang;
21840        }
21841        trackProperties.media = captionsTrack;
21842      });
21843    }
21844  }
21845  closedCaptionsForLevel(frag) {
21846    const level = this.hls.levels[frag.level];
21847    return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
21848  }
21849  onFragLoading(event, data) {
21850    // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
21851    if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
21852      var _data$part$index, _data$part;
21853      const {
21854        cea608Parser1,
21855        cea608Parser2,
21856        lastSn
21857      } = this;
21858      const {
21859        cc,
21860        sn
21861      } = data.frag;
21862      const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
21863      if (cea608Parser1 && cea608Parser2) {
21864        if (sn !== lastSn + 1 || sn === lastSn && partIndex !== this.lastPartIndex + 1 || cc !== this.lastCc) {
21865          cea608Parser1.reset();
21866          cea608Parser2.reset();
21867        }
21868      }
21869      this.lastCc = cc;
21870      this.lastSn = sn;
21871      this.lastPartIndex = partIndex;
21872    }
21873  }
21874  onFragLoaded(event, data) {
21875    const {
21876      frag,
21877      payload
21878    } = data;
21879    if (frag.type === PlaylistLevelType.SUBTITLE) {
21880      // If fragment is subtitle type, parse as WebVTT.
21881      if (payload.byteLength) {
21882        const decryptData = frag.decryptdata;
21883        // fragment after decryption has a stats object
21884        const decrypted = ('stats' in data);
21885        // If the subtitles are not encrypted, parse VTTs now. Otherwise, we need to wait.
21886        if (decryptData == null || !decryptData.encrypted || decrypted) {
21887          const trackPlaylistMedia = this.tracks[frag.level];
21888          const vttCCs = this.vttCCs;
21889          if (!vttCCs[frag.cc]) {
21890            vttCCs[frag.cc] = {
21891              start: frag.start,
21892              prevCC: this.prevCC,
21893              new: true
21894            };
21895            this.prevCC = frag.cc;
21896          }
21897          if (trackPlaylistMedia && trackPlaylistMedia.textCodec === IMSC1_CODEC) {
21898            this._parseIMSC1(frag, payload);
21899          } else {
21900            this._parseVTTs(data);
21901          }
21902        }
21903      } else {
21904        // In case there is no payload, finish unsuccessfully.
21905        this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21906          success: false,
21907          frag,
21908          error: new Error('Empty subtitle payload')
21909        });
21910      }
21911    }
21912  }
21913  _parseIMSC1(frag, payload) {
21914    const hls = this.hls;
21915    parseIMSC1(payload, this.initPTS[frag.cc], cues => {
21916      this._appendCues(cues, frag.level);
21917      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21918        success: true,
21919        frag: frag
21920      });
21921    }, error => {
21922      logger.log(`Failed to parse IMSC1: ${error}`);
21923      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21924        success: false,
21925        frag: frag,
21926        error
21927      });
21928    });
21929  }
21930  _parseVTTs(data) {
21931    var _frag$initSegment;
21932    const {
21933      frag,
21934      payload
21935    } = data;
21936    // We need an initial synchronisation PTS. Store fragments as long as none has arrived
21937    const {
21938      initPTS,
21939      unparsedVttFrags
21940    } = this;
21941    const maxAvCC = initPTS.length - 1;
21942    if (!initPTS[frag.cc] && maxAvCC === -1) {
21943      unparsedVttFrags.push(data);
21944      return;
21945    }
21946    const hls = this.hls;
21947    // Parse the WebVTT file contents.
21948    const payloadWebVTT = (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)) : payload;
21949    parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, cues => {
21950      this._appendCues(cues, frag.level);
21951      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21952        success: true,
21953        frag: frag
21954      });
21955    }, error => {
21956      const missingInitPTS = error.message === 'Missing initPTS for VTT MPEGTS';
21957      if (missingInitPTS) {
21958        unparsedVttFrags.push(data);
21959      } else {
21960        this._fallbackToIMSC1(frag, payload);
21961      }
21962      // Something went wrong while parsing. Trigger event with success false.
21963      logger.log(`Failed to parse VTT cue: ${error}`);
21964      if (missingInitPTS && maxAvCC > frag.cc) {
21965        return;
21966      }
21967      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
21968        success: false,
21969        frag: frag,
21970        error
21971      });
21972    });
21973  }
21974  _fallbackToIMSC1(frag, payload) {
21975    // If textCodec is unknown, try parsing as IMSC1. Set textCodec based on the result
21976    const trackPlaylistMedia = this.tracks[frag.level];
21977    if (!trackPlaylistMedia.textCodec) {
21978      parseIMSC1(payload, this.initPTS[frag.cc], () => {
21979        trackPlaylistMedia.textCodec = IMSC1_CODEC;
21980        this._parseIMSC1(frag, payload);
21981      }, () => {
21982        trackPlaylistMedia.textCodec = 'wvtt';
21983      });
21984    }
21985  }
21986  _appendCues(cues, fragLevel) {
21987    const hls = this.hls;
21988    if (this.config.renderTextTracksNatively) {
21989      const textTrack = this.textTracks[fragLevel];
21990      // WebVTTParser.parse is an async method and if the currently selected text track mode is set to "disabled"
21991      // before parsing is done then don't try to access currentTrack.cues.getCueById as cues will be null
21992      // and trying to access getCueById method of cues will throw an exception
21993      // Because we check if the mode is disabled, we can force check `cues` below. They can't be null.
21994      if (!textTrack || textTrack.mode === 'disabled') {
21995        return;
21996      }
21997      cues.forEach(cue => addCueToTrack(textTrack, cue));
21998    } else {
21999      const currentTrack = this.tracks[fragLevel];
22000      if (!currentTrack) {
22001        return;
22002      }
22003      const track = currentTrack.default ? 'default' : 'subtitles' + fragLevel;
22004      hls.trigger(Events.CUES_PARSED, {
22005        type: 'subtitles',
22006        cues,
22007        track
22008      });
22009    }
22010  }
22011  onFragDecrypted(event, data) {
22012    const {
22013      frag
22014    } = data;
22015    if (frag.type === PlaylistLevelType.SUBTITLE) {
22016      this.onFragLoaded(Events.FRAG_LOADED, data);
22017    }
22018  }
22019  onSubtitleTracksCleared() {
22020    this.tracks = [];
22021    this.captionsTracks = {};
22022  }
22023  onFragParsingUserdata(event, data) {
22024    this.initCea608Parsers();
22025    const {
22026      cea608Parser1,
22027      cea608Parser2
22028    } = this;
22029    if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
22030      return;
22031    }
22032    const {
22033      frag,
22034      samples
22035    } = data;
22036    if (frag.type === PlaylistLevelType.MAIN && this.closedCaptionsForLevel(frag) === 'NONE') {
22037      return;
22038    }
22039    // If the event contains captions (found in the bytes property), push all bytes into the parser immediately
22040    // It will create the proper timestamps based on the PTS value
22041    for (let i = 0; i < samples.length; i++) {
22042      const ccBytes = samples[i].bytes;
22043      if (ccBytes) {
22044        const ccdatas = this.extractCea608Data(ccBytes);
22045        cea608Parser1.addData(samples[i].pts, ccdatas[0]);
22046        cea608Parser2.addData(samples[i].pts, ccdatas[1]);
22047      }
22048    }
22049  }
22050  onBufferFlushing(event, {
22051    startOffset,
22052    endOffset,
22053    endOffsetSubtitles,
22054    type
22055  }) {
22056    const {
22057      media
22058    } = this;
22059    if (!media || media.currentTime < endOffset) {
22060      return;
22061    }
22062    // Clear 608 caption cues from the captions TextTracks when the video back buffer is flushed
22063    // Forward cues are never removed because we can loose streamed 608 content from recent fragments
22064    if (!type || type === 'video') {
22065      const {
22066        captionsTracks
22067      } = this;
vendor: 8,530 bytes, lines 22068-22317
22068      Object.keys(captionsTracks).forEach(trackName => removeCuesInRange(captionsTracks[trackName], startOffset, endOffset));
22069    }
22070    if (this.config.renderTextTracksNatively) {
22071      // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed
22072      if (startOffset === 0 && endOffsetSubtitles !== undefined) {
22073        const {
22074          textTracks
22075        } = this;
22076        Object.keys(textTracks).forEach(trackName => removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles));
22077      }
22078    }
22079  }
22080  extractCea608Data(byteArray) {
22081    const actualCCBytes = [[], []];
22082    const count = byteArray[0] & 0x1f;
22083    let position = 2;
22084    for (let j = 0; j < count; j++) {
22085      const tmpByte = byteArray[position++];
22086      const ccbyte1 = 0x7f & byteArray[position++];
22087      const ccbyte2 = 0x7f & byteArray[position++];
22088      if (ccbyte1 === 0 && ccbyte2 === 0) {
22089        continue;
22090      }
22091      const ccValid = (0x04 & tmpByte) !== 0; // Support all four channels
22092      if (ccValid) {
22093        const ccType = 0x03 & tmpByte;
22094        if (0x00 /* CEA608 field1*/ === ccType || 0x01 /* CEA608 field2*/ === ccType) {
22095          // Exclude CEA708 CC data.
22096          actualCCBytes[ccType].push(ccbyte1);
22097          actualCCBytes[ccType].push(ccbyte2);
22098        }
22099      }
22100    }
22101    return actualCCBytes;
22102  }
22103}
22104function captionsOrSubtitlesFromCharacteristics(track) {
22105  if (track.characteristics) {
22106    if (/transcribes-spoken-dialog/gi.test(track.characteristics) && /describes-music-and-sound/gi.test(track.characteristics)) {
22107      return 'captions';
22108    }
22109  }
22110  return 'subtitles';
22111}
22112function canReuseVttTextTrack(inUseTrack, manifestTrack) {
22113  return !!inUseTrack && inUseTrack.kind === captionsOrSubtitlesFromCharacteristics(manifestTrack) && subtitleTrackMatchesTextTrack(manifestTrack, inUseTrack);
22114}
22115function intersection(x1, x2, y1, y2) {
22116  return Math.min(x2, y2) - Math.max(x1, y1);
22117}
22118function newVTTCCs() {
22119  return {
22120    ccOffset: 0,
22121    presentationOffset: 0,
22122    0: {
22123      start: 0,
22124      prevCC: -1,
22125      new: true
22126    }
22127  };
22128}
22129
22130class CapLevelController {
22131  constructor(hls) {
22132    this.hls = void 0;
22133    this.autoLevelCapping = void 0;
22134    this.firstLevel = void 0;
22135    this.media = void 0;
22136    this.restrictedLevels = void 0;
22137    this.timer = void 0;
22138    this.clientRect = void 0;
22139    this.streamController = void 0;
22140    this.hls = hls;
22141    this.autoLevelCapping = Number.POSITIVE_INFINITY;
22142    this.firstLevel = -1;
22143    this.media = null;
22144    this.restrictedLevels = [];
22145    this.timer = undefined;
22146    this.clientRect = null;
22147    this.registerListeners();
22148  }
22149  setStreamController(streamController) {
22150    this.streamController = streamController;
22151  }
22152  destroy() {
22153    if (this.hls) {
22154      this.unregisterListener();
22155    }
22156    if (this.timer) {
22157      this.stopCapping();
22158    }
22159    this.media = null;
22160    this.clientRect = null;
22161    // @ts-ignore
22162    this.hls = this.streamController = null;
22163  }
22164  registerListeners() {
22165    const {
22166      hls
22167    } = this;
22168    hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
22169    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
22170    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
22171    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
22172    hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
22173    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
22174  }
22175  unregisterListener() {
22176    const {
22177      hls
22178    } = this;
22179    hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
22180    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
22181    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
22182    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
22183    hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
22184    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
22185  }
22186  onFpsDropLevelCapping(event, data) {
22187    // Don't add a restricted level more than once
22188    const level = this.hls.levels[data.droppedLevel];
22189    if (this.isLevelAllowed(level)) {
22190      this.restrictedLevels.push({
22191        bitrate: level.bitrate,
22192        height: level.height,
22193        width: level.width
22194      });
22195    }
22196  }
22197  onMediaAttaching(event, data) {
22198    this.media = data.media instanceof HTMLVideoElement ? data.media : null;
22199    this.clientRect = null;
22200    if (this.timer && this.hls.levels.length) {
22201      this.detectPlayerSize();
22202    }
22203  }
22204  onManifestParsed(event, data) {
22205    const hls = this.hls;
22206    this.restrictedLevels = [];
22207    this.firstLevel = data.firstLevel;
22208    if (hls.config.capLevelToPlayerSize && data.video) {
22209      // Start capping immediately if the manifest has signaled video codecs
22210      this.startCapping();
22211    }
22212  }
22213  onLevelsUpdated(event, data) {
22214    if (this.timer && isFiniteNumber(this.autoLevelCapping)) {
22215      this.detectPlayerSize();
22216    }
22217  }
22218
22219  // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted
22220  // to the first level
22221  onBufferCodecs(event, data) {
22222    const hls = this.hls;
22223    if (hls.config.capLevelToPlayerSize && data.video) {
22224      // If the manifest did not signal a video codec capping has been deferred until we're certain video is present
22225      this.startCapping();
22226    }
22227  }
22228  onMediaDetaching() {
22229    this.stopCapping();
22230  }
22231  detectPlayerSize() {
22232    if (this.media) {
22233      if (this.mediaHeight <= 0 || this.mediaWidth <= 0) {
22234        this.clientRect = null;
22235        return;
22236      }
22237      const levels = this.hls.levels;
22238      if (levels.length) {
22239        const hls = this.hls;
22240        const maxLevel = this.getMaxLevel(levels.length - 1);
22241        if (maxLevel !== this.autoLevelCapping) {
22242          logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
22243        }
22244        hls.autoLevelCapping = maxLevel;
22245        if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
22246          // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
22247          // usually happen when the user go to the fullscreen mode.
22248          this.streamController.nextLevelSwitch();
22249        }
22250        this.autoLevelCapping = hls.autoLevelCapping;
22251      }
22252    }
22253  }
22254
22255  /*
22256   * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
22257   */
22258  getMaxLevel(capLevelIndex) {
22259    const levels = this.hls.levels;
22260    if (!levels.length) {
22261      return -1;
22262    }
22263    const validLevels = levels.filter((level, index) => this.isLevelAllowed(level) && index <= capLevelIndex);
22264    this.clientRect = null;
22265    return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight);
22266  }
22267  startCapping() {
22268    if (this.timer) {
22269      // Don't reset capping if started twice; this can happen if the manifest signals a video codec
22270      return;
22271    }
22272    this.autoLevelCapping = Number.POSITIVE_INFINITY;
22273    self.clearInterval(this.timer);
22274    this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000);
22275    this.detectPlayerSize();
22276  }
22277  stopCapping() {
22278    this.restrictedLevels = [];
22279    this.firstLevel = -1;
22280    this.autoLevelCapping = Number.POSITIVE_INFINITY;
22281    if (this.timer) {
22282      self.clearInterval(this.timer);
22283      this.timer = undefined;
22284    }
22285  }
22286  getDimensions() {
22287    if (this.clientRect) {
22288      return this.clientRect;
22289    }
22290    const media = this.media;
22291    const boundsRect = {
22292      width: 0,
22293      height: 0
22294    };
22295    if (media) {
22296      const clientRect = media.getBoundingClientRect();
22297      boundsRect.width = clientRect.width;
22298      boundsRect.height = clientRect.height;
22299      if (!boundsRect.width && !boundsRect.height) {
22300        // When the media element has no width or height (equivalent to not being in the DOM),
22301        // then use its width and height attributes (media.width, media.height)
22302        boundsRect.width = clientRect.right - clientRect.left || media.width || 0;
22303        boundsRect.height = clientRect.bottom - clientRect.top || media.height || 0;
22304      }
22305    }
22306    this.clientRect = boundsRect;
22307    return boundsRect;
22308  }
22309  get mediaWidth() {
22310    return this.getDimensions().width * this.contentScaleFactor;
22311  }
22312  get mediaHeight() {
22313    return this.getDimensions().height * this.contentScaleFactor;
22314  }
22315  get contentScaleFactor() {
22316    let pixelRatio = 1;
22317    if (!this.hls.config.ignoreDevicePixelRatio) {
vendor: 5,064 bytes, lines 22318-22454
22318      try {
22319        pixelRatio = self.devicePixelRatio;
22320      } catch (e) {
22321        /* no-op */
22322      }
22323    }
22324    return pixelRatio;
22325  }
22326  isLevelAllowed(level) {
22327    const restrictedLevels = this.restrictedLevels;
22328    return !restrictedLevels.some(restrictedLevel => {
22329      return level.bitrate === restrictedLevel.bitrate && level.width === restrictedLevel.width && level.height === restrictedLevel.height;
22330    });
22331  }
22332  static getMaxLevelByMediaSize(levels, width, height) {
22333    if (!(levels != null && levels.length)) {
22334      return -1;
22335    }
22336
22337    // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next
22338    // to determine whether we've chosen the greatest bandwidth for the media's dimensions
22339    const atGreatestBandwidth = (curLevel, nextLevel) => {
22340      if (!nextLevel) {
22341        return true;
22342      }
22343      return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height;
22344    };
22345
22346    // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to
22347    // the max level
22348    let maxLevelIndex = levels.length - 1;
22349    // Prevent changes in aspect-ratio from causing capping to toggle back and forth
22350    const squareSize = Math.max(width, height);
22351    for (let i = 0; i < levels.length; i += 1) {
22352      const level = levels[i];
22353      if ((level.width >= squareSize || level.height >= squareSize) && atGreatestBandwidth(level, levels[i + 1])) {
22354        maxLevelIndex = i;
22355        break;
22356      }
22357    }
22358    return maxLevelIndex;
22359  }
22360}
22361
22362class FPSController {
22363  constructor(hls) {
22364    this.hls = void 0;
22365    this.isVideoPlaybackQualityAvailable = false;
22366    this.timer = void 0;
22367    this.media = null;
22368    this.lastTime = void 0;
22369    this.lastDroppedFrames = 0;
22370    this.lastDecodedFrames = 0;
22371    // stream controller must be provided as a dependency!
22372    this.streamController = void 0;
22373    this.hls = hls;
22374    this.registerListeners();
22375  }
22376  setStreamController(streamController) {
22377    this.streamController = streamController;
22378  }
22379  registerListeners() {
22380    this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
22381  }
22382  unregisterListeners() {
22383    this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
22384  }
22385  destroy() {
22386    if (this.timer) {
22387      clearInterval(this.timer);
22388    }
22389    this.unregisterListeners();
22390    this.isVideoPlaybackQualityAvailable = false;
22391    this.media = null;
22392  }
22393  onMediaAttaching(event, data) {
22394    const config = this.hls.config;
22395    if (config.capLevelOnFPSDrop) {
22396      const media = data.media instanceof self.HTMLVideoElement ? data.media : null;
22397      this.media = media;
22398      if (media && typeof media.getVideoPlaybackQuality === 'function') {
22399        this.isVideoPlaybackQualityAvailable = true;
22400      }
22401      self.clearInterval(this.timer);
22402      this.timer = self.setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod);
22403    }
22404  }
22405  checkFPS(video, decodedFrames, droppedFrames) {
22406    const currentTime = performance.now();
22407    if (decodedFrames) {
22408      if (this.lastTime) {
22409        const currentPeriod = currentTime - this.lastTime;
22410        const currentDropped = droppedFrames - this.lastDroppedFrames;
22411        const currentDecoded = decodedFrames - this.lastDecodedFrames;
22412        const droppedFPS = 1000 * currentDropped / currentPeriod;
22413        const hls = this.hls;
22414        hls.trigger(Events.FPS_DROP, {
22415          currentDropped: currentDropped,
22416          currentDecoded: currentDecoded,
22417          totalDroppedFrames: droppedFrames
22418        });
22419        if (droppedFPS > 0) {
22420          // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
22421          if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
22422            let currentLevel = hls.currentLevel;
22423            logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
22424            if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
22425              currentLevel = currentLevel - 1;
22426              hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
22427                level: currentLevel,
22428                droppedLevel: hls.currentLevel
22429              });
22430              hls.autoLevelCapping = currentLevel;
22431              this.streamController.nextLevelSwitch();
22432            }
22433          }
22434        }
22435      }
22436      this.lastTime = currentTime;
22437      this.lastDroppedFrames = droppedFrames;
22438      this.lastDecodedFrames = decodedFrames;
22439    }
22440  }
22441  checkFPSInterval() {
22442    const video = this.media;
22443    if (video) {
22444      if (this.isVideoPlaybackQualityAvailable) {
22445        const videoPlaybackQuality = video.getVideoPlaybackQuality();
22446        this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);
22447      } else {
22448        // HTMLVideoElement doesn't include the webkit types
22449        this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount);
22450      }
22451    }
22452  }
22453}
22454
vendor: 5,040 bytes, lines 22455-22582
22455const LOGGER_PREFIX = '[eme]';
22456/**
22457 * Controller to deal with encrypted media extensions (EME)
22458 * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
22459 *
22460 * @class
22461 * @constructor
22462 */
22463class EMEController {
22464  constructor(hls) {
22465    this.hls = void 0;
22466    this.config = void 0;
22467    this.media = null;
22468    this.keyFormatPromise = null;
22469    this.keySystemAccessPromises = {};
22470    this._requestLicenseFailureCount = 0;
22471    this.mediaKeySessions = [];
22472    this.keyIdToKeySessionPromise = {};
22473    this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
22474    this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
22475    this.onWaitingForKey = this._onWaitingForKey.bind(this);
22476    this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
22477    this.log = logger.log.bind(logger, LOGGER_PREFIX);
22478    this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
22479    this.error = logger.error.bind(logger, LOGGER_PREFIX);
22480    this.hls = hls;
22481    this.config = hls.config;
22482    this.registerListeners();
22483  }
22484  destroy() {
22485    this.unregisterListeners();
22486    this.onMediaDetached();
22487    // Remove any references that could be held in config options or callbacks
22488    const config = this.config;
22489    config.requestMediaKeySystemAccessFunc = null;
22490    config.licenseXhrSetup = config.licenseResponseCallback = undefined;
22491    config.drmSystems = config.drmSystemOptions = {};
22492    // @ts-ignore
22493    this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
22494    // @ts-ignore
22495    this.config = null;
22496  }
22497  registerListeners() {
22498    this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
22499    this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
22500    this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
22501    this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
22502  }
22503  unregisterListeners() {
22504    this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
22505    this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
22506    this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
22507    this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
22508  }
22509  getLicenseServerUrl(keySystem) {
22510    const {
22511      drmSystems,
22512      widevineLicenseUrl
22513    } = this.config;
22514    const keySystemConfiguration = drmSystems[keySystem];
22515    if (keySystemConfiguration) {
22516      return keySystemConfiguration.licenseUrl;
22517    }
22518
22519    // For backward compatibility
22520    if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
22521      return widevineLicenseUrl;
22522    }
22523    throw new Error(`no license server URL configured for key-system "${keySystem}"`);
22524  }
22525  getServerCertificateUrl(keySystem) {
22526    const {
22527      drmSystems
22528    } = this.config;
22529    const keySystemConfiguration = drmSystems[keySystem];
22530    if (keySystemConfiguration) {
22531      return keySystemConfiguration.serverCertificateUrl;
22532    } else {
22533      this.log(`No Server Certificate in config.drmSystems["${keySystem}"]`);
22534    }
22535  }
22536  attemptKeySystemAccess(keySystemsToAttempt) {
22537    const levels = this.hls.levels;
22538    const uniqueCodec = (value, i, a) => !!value && a.indexOf(value) === i;
22539    const audioCodecs = levels.map(level => level.audioCodec).filter(uniqueCodec);
22540    const videoCodecs = levels.map(level => level.videoCodec).filter(uniqueCodec);
22541    if (audioCodecs.length + videoCodecs.length === 0) {
22542      videoCodecs.push('avc1.42e01e');
22543    }
22544    return new Promise((resolve, reject) => {
22545      const attempt = keySystems => {
22546        const keySystem = keySystems.shift();
22547        this.getMediaKeysPromise(keySystem, audioCodecs, videoCodecs).then(mediaKeys => resolve({
22548          keySystem,
22549          mediaKeys
22550        })).catch(error => {
22551          if (keySystems.length) {
22552            attempt(keySystems);
22553          } else if (error instanceof EMEKeyError) {
22554            reject(error);
22555          } else {
22556            reject(new EMEKeyError({
22557              type: ErrorTypes.KEY_SYSTEM_ERROR,
22558              details: ErrorDetails.KEY_SYSTEM_NO_ACCESS,
22559              error,
22560              fatal: true
22561            }, error.message));
22562          }
22563        });
22564      };
22565      attempt(keySystemsToAttempt);
22566    });
22567  }
22568  requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
22569    const {
22570      requestMediaKeySystemAccessFunc
22571    } = this.config;
22572    if (!(typeof requestMediaKeySystemAccessFunc === 'function')) {
22573      let errMessage = `Configured requestMediaKeySystemAccess is not a function ${requestMediaKeySystemAccessFunc}`;
22574      if (requestMediaKeySystemAccess === null && self.location.protocol === 'http:') {
22575        errMessage = `navigator.requestMediaKeySystemAccess is not available over insecure protocol ${location.protocol}`;
22576      }
22577      return Promise.reject(new Error(errMessage));
22578    }
22579    return requestMediaKeySystemAccessFunc(keySystem, supportedConfigurations);
22580  }
22581  getMediaKeysPromise(keySystem, audioCodecs, videoCodecs) {
22582    // This can throw, but is caught in event handler callpath
22583    const mediaKeySystemConfigs = getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, this.config.drmSystemOptions);
22584    const keySystemAccessPromises = this.keySystemAccessPromises[keySystem];
22585    let keySystemAccess = keySystemAccessPromises == null ? void 0 : keySystemAccessPromises.keySystemAccess;
22586    if (!keySystemAccess) {
22587      this.log(`Requesting encrypted media "${keySystem}" key-system access with config: ${JSON.stringify(mediaKeySystemConfigs)}`);
22588      keySystemAccess = this.requestMediaKeySystemAccess(keySystem, mediaKeySystemConfigs);
22589      const _keySystemAccessPromises = this.keySystemAccessPromises[keySystem] = {
22590        keySystemAccess
22591      };
22592      keySystemAccess.catch(error => {
22593        this.log(`Failed to obtain access to key-system "${keySystem}": ${error}`);
22594      });
22595      return keySystemAccess.then(mediaKeySystemAccess => {
22596        this.log(`Access for key-system "${mediaKeySystemAccess.keySystem}" obtained`);
22597        const certificateRequest = this.fetchServerCertificate(keySystem);
22598        this.log(`Create media-keys for "${keySystem}"`);
22599        _keySystemAccessPromises.mediaKeys = mediaKeySystemAccess.createMediaKeys().then(mediaKeys => {
22600          this.log(`Media-keys created for "${keySystem}"`);
22601          return certificateRequest.then(certificate => {
22602            if (certificate) {
22603              return this.setMediaKeysServerCertificate(mediaKeys, keySystem, certificate);
22604            }
22605            return mediaKeys;
22606          });
22607        });
22608        _keySystemAccessPromises.mediaKeys.catch(error => {
22609          this.error(`Failed to create media-keys for "${keySystem}"}: ${error}`);
22610        });
22611        return _keySystemAccessPromises.mediaKeys;
22612      });
22613    }
22614    return keySystemAccess.then(() => keySystemAccessPromises.mediaKeys);
22615  }
22616  createMediaKeySessionContext({
22617    decryptdata,
22618    keySystem,
22619    mediaKeys
22620  }) {
22621    this.log(`Creating key-system session "${keySystem}" keyId: ${Hex.hexDump(decryptdata.keyId || [])}`);
22622    const mediaKeysSession = mediaKeys.createSession();
22623    const mediaKeySessionContext = {
22624      decryptdata,
22625      keySystem,
22626      mediaKeys,
22627      mediaKeysSession,
22628      keyStatus: 'status-pending'
22629    };
22630    this.mediaKeySessions.push(mediaKeySessionContext);
22631    return mediaKeySessionContext;
22632  }
22633  renewKeySession(mediaKeySessionContext) {
22634    const decryptdata = mediaKeySessionContext.decryptdata;
22635    if (decryptdata.pssh) {
22636      const keySessionContext = this.createMediaKeySessionContext(mediaKeySessionContext);
22637      const keyId = this.getKeyIdString(decryptdata);
22638      const scheme = 'cenc';
22639      this.keyIdToKeySessionPromise[keyId] = this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'expired');
22640    } else {
22641      this.warn(`Could not renew expired session. Missing pssh initData.`);
22642    }
22643    this.removeSession(mediaKeySessionContext);
22644  }
22645  getKeyIdString(decryptdata) {
22646    if (!decryptdata) {
22647      throw new Error('Could not read keyId of undefined decryptdata');
22648    }
22649    if (decryptdata.keyId === null) {
22650      throw new Error('keyId is null');
22651    }
22652    return Hex.hexDump(decryptdata.keyId);
22653  }
22654  updateKeySession(mediaKeySessionContext, data) {
22655    var _mediaKeySessionConte;
22656    const keySession = mediaKeySessionContext.mediaKeysSession;
22657    this.log(`Updating key-session "${keySession.sessionId}" for keyID ${Hex.hexDump(((_mediaKeySessionConte = mediaKeySessionContext.decryptdata) == null ? void 0 : _mediaKeySessionConte.keyId) || [])}
22658      } (data length: ${data ? data.byteLength : data})`);
22659    return keySession.update(data);
22660  }
22661  selectKeySystemFormat(frag) {
22662    const keyFormats = Object.keys(frag.levelkeys || {});
22663    if (!this.keyFormatPromise) {
22664      this.log(`Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${frag.level}) key formats ${keyFormats.join(', ')}`);
22665      this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
22666    }
22667    return this.keyFormatPromise;
22668  }
22669  getKeyFormatPromise(keyFormats) {
22670    return new Promise((resolve, reject) => {
22671      const keySystemsInConfig = getKeySystemsForConfig(this.config);
22672      const keySystemsToAttempt = keyFormats.map(keySystemFormatToKeySystemDomain).filter(value => !!value && keySystemsInConfig.indexOf(value) !== -1);
22673      return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({
22674        keySystem
22675      }) => {
22676        const keySystemFormat = keySystemDomainToKeySystemFormat(keySystem);
22677        if (keySystemFormat) {
22678          resolve(keySystemFormat);
22679        } else {
22680          reject(new Error(`Unable to find format for key-system "${keySystem}"`));
22681        }
22682      }).catch(reject);
22683    });
22684  }
22685  loadKey(data) {
22686    const decryptdata = data.keyInfo.decryptdata;
22687    const keyId = this.getKeyIdString(decryptdata);
22688    const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`;
22689    this.log(`Starting session for key ${keyDetails}`);
22690    let keySessionContextPromise = this.keyIdToKeySessionPromise[keyId];
22691    if (!keySessionContextPromise) {
22692      keySessionContextPromise = this.keyIdToKeySessionPromise[keyId] = this.getKeySystemForKeyPromise(decryptdata).then(({
22693        keySystem,
22694        mediaKeys
22695      }) => {
22696        this.throwIfDestroyed();
22697        this.log(`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`);
22698        return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22699          this.throwIfDestroyed();
22700          const keySessionContext = this.createMediaKeySessionContext({
22701            keySystem,
22702            mediaKeys,
22703            decryptdata
22704          });
22705          const scheme = 'cenc';
22706          return this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'playlist-key');
22707        });
22708      });
22709      keySessionContextPromise.catch(error => this.handleError(error));
22710    }
22711    return keySessionContextPromise;
22712  }
22713  throwIfDestroyed(message = 'Invalid state') {
22714    if (!this.hls) {
22715      throw new Error('invalid state');
22716    }
22717  }
22718  handleError(error) {
22719    if (!this.hls) {
22720      return;
22721    }
22722    this.error(error.message);
22723    if (error instanceof EMEKeyError) {
22724      this.hls.trigger(Events.ERROR, error.data);
22725    } else {
22726      this.hls.trigger(Events.ERROR, {
22727        type: ErrorTypes.KEY_SYSTEM_ERROR,
22728        details: ErrorDetails.KEY_SYSTEM_NO_KEYS,
22729        error,
22730        fatal: true
22731      });
22732    }
22733  }
22734  getKeySystemForKeyPromise(decryptdata) {
22735    const keyId = this.getKeyIdString(decryptdata);
22736    const mediaKeySessionContext = this.keyIdToKeySessionPromise[keyId];
22737    if (!mediaKeySessionContext) {
22738      const keySystem = keySystemFormatToKeySystemDomain(decryptdata.keyFormat);
22739      const keySystemsToAttempt = keySystem ? [keySystem] : getKeySystemsForConfig(this.config);
22740      return this.attemptKeySystemAccess(keySystemsToAttempt);
22741    }
22742    return mediaKeySessionContext;
22743  }
22744  getKeySystemSelectionPromise(keySystemsToAttempt) {
22745    if (!keySystemsToAttempt.length) {
22746      keySystemsToAttempt = getKeySystemsForConfig(this.config);
22747    }
22748    if (keySystemsToAttempt.length === 0) {
22749      throw new EMEKeyError({
22750        type: ErrorTypes.KEY_SYSTEM_ERROR,
22751        details: ErrorDetails.KEY_SYSTEM_NO_CONFIGURED_LICENSE,
22752        fatal: true
22753      }, `Missing key-system license configuration options ${JSON.stringify({
22754        drmSystems: this.config.drmSystems
22755      })}`);
22756    }
22757    return this.attemptKeySystemAccess(keySystemsToAttempt);
22758  }
22759  _onMediaEncrypted(event) {
22760    const {
22761      initDataType,
22762      initData
22763    } = event;
22764    this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
22765
22766    // Ignore event when initData is null
22767    if (initData === null) {
22768      return;
22769    }
22770    let keyId;
22771    let keySystemDomain;
22772    if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
22773      // Match sinf keyId to playlist skd://keyId=
22774      const json = bin2str(new Uint8Array(initData));
22775      try {
22776        const sinf = base64Decode(JSON.parse(json).sinf);
22777        const tenc = parseSinf(new Uint8Array(sinf));
22778        if (!tenc) {
22779          return;
22780        }
22781        keyId = tenc.subarray(8, 24);
22782        keySystemDomain = KeySystems.FAIRPLAY;
22783      } catch (error) {
22784        this.warn('Failed to parse sinf "encrypted" event message initData');
22785        return;
22786      }
22787    } else {
22788      // Support clear-lead key-session creation (otherwise depend on playlist keys)
22789      const psshInfo = parsePssh(initData);
22790      if (psshInfo === null) {
22791        return;
22792      }
22793      if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
22794        keyId = psshInfo.data.subarray(8, 24);
22795      }
22796      keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
22797    }
22798    if (!keySystemDomain || !keyId) {
22799      return;
22800    }
22801    const keyIdHex = Hex.hexDump(keyId);
22802    const {
22803      keyIdToKeySessionPromise,
22804      mediaKeySessions
22805    } = this;
22806    let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22807    for (let i = 0; i < mediaKeySessions.length; i++) {
22808      // Match playlist key
22809      const keyContext = mediaKeySessions[i];
22810      const decryptdata = keyContext.decryptdata;
22811      if (decryptdata.pssh || !decryptdata.keyId) {
22812        continue;
22813      }
22814      const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
22815      if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22816        keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22817        delete keyIdToKeySessionPromise[oldKeyIdHex];
22818        decryptdata.pssh = new Uint8Array(initData);
22819        decryptdata.keyId = keyId;
22820        keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22821          return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22822        });
22823        break;
22824      }
22825    }
22826    if (!keySessionContextPromise) {
22827      // Clear-lead key (not encountered in playlist)
22828      keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
22829        keySystem,
22830        mediaKeys
22831      }) => {
22832        var _keySystemToKeySystem;
22833        this.throwIfDestroyed();
22834        const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
22835        decryptdata.pssh = new Uint8Array(initData);
22836        decryptdata.keyId = keyId;
22837        return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22838          this.throwIfDestroyed();
22839          const keySessionContext = this.createMediaKeySessionContext({
22840            decryptdata,
22841            keySystem,
22842            mediaKeys
22843          });
22844          return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
22845        });
22846      });
22847    }
22848    keySessionContextPromise.catch(error => this.handleError(error));
22849  }
22850  _onWaitingForKey(event) {
22851    this.log(`"${event.type}" event`);
22852  }
22853  attemptSetMediaKeys(keySystem, mediaKeys) {
22854    const queue = this.setMediaKeysQueue.slice();
22855    this.log(`Setting media-keys for "${keySystem}"`);
22856    // Only one setMediaKeys() can run at one time, and multiple setMediaKeys() operations
22857    // can be queued for execution for multiple key sessions.
22858    const setMediaKeysPromise = Promise.all(queue).then(() => {
22859      if (!this.media) {
22860        throw new Error('Attempted to set mediaKeys without media element attached');
22861      }
22862      return this.media.setMediaKeys(mediaKeys);
22863    });
22864    this.setMediaKeysQueue.push(setMediaKeysPromise);
22865    return setMediaKeysPromise.then(() => {
22866      this.log(`Media-keys set for "${keySystem}"`);
22867      queue.push(setMediaKeysPromise);
22868      this.setMediaKeysQueue = this.setMediaKeysQueue.filter(p => queue.indexOf(p) === -1);
22869    });
22870  }
22871  generateRequestWithPreferredKeySession(context, initDataType, initData, reason) {
22872    var _this$config$drmSyste, _this$config$drmSyste2;
22873    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;
22874    if (generateRequestFilter) {
22875      try {
22876        const mappedInitData = generateRequestFilter.call(this.hls, initDataType, initData, context);
22877        if (!mappedInitData) {
22878          throw new Error('Invalid response from configured generateRequest filter');
22879        }
22880        initDataType = mappedInitData.initDataType;
22881        initData = context.decryptdata.pssh = mappedInitData.initData ? new Uint8Array(mappedInitData.initData) : null;
22882      } catch (error) {
22883        var _this$hls;
22884        this.warn(error.message);
22885        if ((_this$hls = this.hls) != null && _this$hls.config.debug) {
22886          throw error;
22887        }
22888      }
22889    }
22890    if (initData === null) {
22891      this.log(`Skipping key-session request for "${reason}" (no initData)`);
22892      return Promise.resolve(context);
22893    }
22894    const keyId = this.getKeyIdString(context.decryptdata);
22895    this.log(`Generating key-session request for "${reason}": ${keyId} (init data type: ${initDataType} length: ${initData ? initData.byteLength : null})`);
22896    const licenseStatus = new EventEmitter();
22897    const onmessage = context._onmessage = event => {
22898      const keySession = context.mediaKeysSession;
22899      if (!keySession) {
22900        licenseStatus.emit('error', new Error('invalid state'));
22901        return;
22902      }
22903      const {
22904        messageType,
22905        message
22906      } = event;
22907      this.log(`"${messageType}" message event for session "${keySession.sessionId}" message size: ${message.byteLength}`);
22908      if (messageType === 'license-request' || messageType === 'license-renewal') {
22909        this.renewLicense(context, message).catch(error => {
22910          this.handleError(error);
22911          licenseStatus.emit('error', error);
22912        });
22913      } else if (messageType === 'license-release') {
22914        if (context.keySystem === KeySystems.FAIRPLAY) {
22915          this.updateKeySession(context, strToUtf8array('acknowledged'));
22916          this.removeSession(context);
22917        }
22918      } else {
22919        this.warn(`unhandled media key message type "${messageType}"`);
22920      }
22921    };
22922    const onkeystatuseschange = context._onkeystatuseschange = event => {
22923      const keySession = context.mediaKeysSession;
22924      if (!keySession) {
22925        licenseStatus.emit('error', new Error('invalid state'));
22926        return;
22927      }
22928      this.onKeyStatusChange(context);
22929      const keyStatus = context.keyStatus;
22930      licenseStatus.emit('keyStatus', keyStatus);
22931      if (keyStatus === 'expired') {
22932        this.warn(`${context.keySystem} expired for key ${keyId}`);
22933        this.renewKeySession(context);
22934      }
22935    };
22936    context.mediaKeysSession.addEventListener('message', onmessage);
22937    context.mediaKeysSession.addEventListener('keystatuseschange', onkeystatuseschange);
22938    const keyUsablePromise = new Promise((resolve, reject) => {
22939      licenseStatus.on('error', reject);
22940      licenseStatus.on('keyStatus', keyStatus => {
22941        if (keyStatus.startsWith('usable')) {
22942          resolve();
22943        } else if (keyStatus === 'output-restricted') {
22944          reject(new EMEKeyError({
22945            type: ErrorTypes.KEY_SYSTEM_ERROR,
22946            details: ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED,
22947            fatal: false
22948          }, 'HDCP level output restricted'));
22949        } else if (keyStatus === 'internal-error') {
22950          reject(new EMEKeyError({
22951            type: ErrorTypes.KEY_SYSTEM_ERROR,
22952            details: ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR,
22953            fatal: true
22954          }, `key status changed to "${keyStatus}"`));
22955        } else if (keyStatus === 'expired') {
22956          reject(new Error('key expired while generating request'));
22957        } else {
22958          this.warn(`unhandled key status change "${keyStatus}"`);
22959        }
22960      });
22961    });
22962    return context.mediaKeysSession.generateRequest(initDataType, initData).then(() => {
22963      var _context$mediaKeysSes;
22964      this.log(`Request generated for key-session "${(_context$mediaKeysSes = context.mediaKeysSession) == null ? void 0 : _context$mediaKeysSes.sessionId}" keyId: ${keyId}`);
22965    }).catch(error => {
22966      throw new EMEKeyError({
22967        type: ErrorTypes.KEY_SYSTEM_ERROR,
22968        details: ErrorDetails.KEY_SYSTEM_NO_SESSION,
22969        error,
22970        fatal: false
22971      }, `Error generating key-session request: ${error}`);
22972    }).then(() => keyUsablePromise).catch(error => {
22973      licenseStatus.removeAllListeners();
22974      this.removeSession(context);
22975      throw error;
22976    }).then(() => {
22977      licenseStatus.removeAllListeners();
22978      return context;
22979    });
22980  }
22981  onKeyStatusChange(mediaKeySessionContext) {
22982    mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach((status, keyId) => {
22983      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}`);
22984      mediaKeySessionContext.keyStatus = status;
22985    });
22986  }
22987  fetchServerCertificate(keySystem) {
22988    const config = this.config;
22989    const Loader = config.loader;
vendor: 9,755 bytes, lines 22990-23231
22990    const certLoader = new Loader(config);
22991    const url = this.getServerCertificateUrl(keySystem);
22992    if (!url) {
22993      return Promise.resolve();
22994    }
22995    this.log(`Fetching server certificate for "${keySystem}"`);
22996    return new Promise((resolve, reject) => {
22997      const loaderContext = {
22998        responseType: 'arraybuffer',
22999        url
23000      };
23001      const loadPolicy = config.certLoadPolicy.default;
23002      const loaderConfig = {
23003        loadPolicy,
23004        timeout: loadPolicy.maxLoadTimeMs,
23005        maxRetry: 0,
23006        retryDelay: 0,
23007        maxRetryDelay: 0
23008      };
23009      const loaderCallbacks = {
23010        onSuccess: (response, stats, context, networkDetails) => {
23011          resolve(response.data);
23012        },
23013        onError: (response, contex, networkDetails, stats) => {
23014          reject(new EMEKeyError({
23015            type: ErrorTypes.KEY_SYSTEM_ERROR,
23016            details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,
23017            fatal: true,
23018            networkDetails,
23019            response: _objectSpread2({
23020              url: loaderContext.url,
23021              data: undefined
23022            }, response)
23023          }, `"${keySystem}" certificate request failed (${url}). Status: ${response.code} (${response.text})`));
23024        },
23025        onTimeout: (stats, context, networkDetails) => {
23026          reject(new EMEKeyError({
23027            type: ErrorTypes.KEY_SYSTEM_ERROR,
23028            details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,
23029            fatal: true,
23030            networkDetails,
23031            response: {
23032              url: loaderContext.url,
23033              data: undefined
23034            }
23035          }, `"${keySystem}" certificate request timed out (${url})`));
23036        },
23037        onAbort: (stats, context, networkDetails) => {
23038          reject(new Error('aborted'));
23039        }
23040      };
23041      certLoader.load(loaderContext, loaderConfig, loaderCallbacks);
23042    });
23043  }
23044  setMediaKeysServerCertificate(mediaKeys, keySystem, cert) {
23045    return new Promise((resolve, reject) => {
23046      mediaKeys.setServerCertificate(cert).then(success => {
23047        this.log(`setServerCertificate ${success ? 'success' : 'not supported by CDM'} (${cert == null ? void 0 : cert.byteLength}) on "${keySystem}"`);
23048        resolve(mediaKeys);
23049      }).catch(error => {
23050        reject(new EMEKeyError({
23051          type: ErrorTypes.KEY_SYSTEM_ERROR,
23052          details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED,
23053          error,
23054          fatal: true
23055        }, error.message));
23056      });
23057    });
23058  }
23059  renewLicense(context, keyMessage) {
23060    return this.requestLicense(context, new Uint8Array(keyMessage)).then(data => {
23061      return this.updateKeySession(context, new Uint8Array(data)).catch(error => {
23062        throw new EMEKeyError({
23063          type: ErrorTypes.KEY_SYSTEM_ERROR,
23064          details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED,
23065          error,
23066          fatal: true
23067        }, error.message);
23068      });
23069    });
23070  }
23071  unpackPlayReadyKeyMessage(xhr, licenseChallenge) {
23072    // On Edge, the raw license message is UTF-16-encoded XML.  We need
23073    // to unpack the Challenge element (base64-encoded string containing the
23074    // actual license request) and any HttpHeader elements (sent as request
23075    // headers).
23076    // For PlayReady CDMs, we need to dig the Challenge out of the XML.
23077    const xmlString = String.fromCharCode.apply(null, new Uint16Array(licenseChallenge.buffer));
23078    if (!xmlString.includes('PlayReadyKeyMessage')) {
23079      // This does not appear to be a wrapped message as on Edge.  Some
23080      // clients do not need this unwrapping, so we will assume this is one of
23081      // them.  Note that "xml" at this point probably looks like random
23082      // garbage, since we interpreted UTF-8 as UTF-16.
23083      xhr.setRequestHeader('Content-Type', 'text/xml; charset=utf-8');
23084      return licenseChallenge;
23085    }
23086    const keyMessageXml = new DOMParser().parseFromString(xmlString, 'application/xml');
23087    // Set request headers.
23088    const headers = keyMessageXml.querySelectorAll('HttpHeader');
23089    if (headers.length > 0) {
23090      let header;
23091      for (let i = 0, len = headers.length; i < len; i++) {
23092        var _header$querySelector, _header$querySelector2;
23093        header = headers[i];
23094        const name = (_header$querySelector = header.querySelector('name')) == null ? void 0 : _header$querySelector.textContent;
23095        const value = (_header$querySelector2 = header.querySelector('value')) == null ? void 0 : _header$querySelector2.textContent;
23096        if (name && value) {
23097          xhr.setRequestHeader(name, value);
23098        }
23099      }
23100    }
23101    const challengeElement = keyMessageXml.querySelector('Challenge');
23102    const challengeText = challengeElement == null ? void 0 : challengeElement.textContent;
23103    if (!challengeText) {
23104      throw new Error(`Cannot find <Challenge> in key message`);
23105    }
23106    return strToUtf8array(atob(challengeText));
23107  }
23108  setupLicenseXHR(xhr, url, keysListItem, licenseChallenge) {
23109    const licenseXhrSetup = this.config.licenseXhrSetup;
23110    if (!licenseXhrSetup) {
23111      xhr.open('POST', url, true);
23112      return Promise.resolve({
23113        xhr,
23114        licenseChallenge
23115      });
23116    }
23117    return Promise.resolve().then(() => {
23118      if (!keysListItem.decryptdata) {
23119        throw new Error('Key removed');
23120      }
23121      return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);
23122    }).catch(error => {
23123      if (!keysListItem.decryptdata) {
23124        // Key session removed. Cancel license request.
23125        throw error;
23126      }
23127      // let's try to open before running setup
23128      xhr.open('POST', url, true);
23129      return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);
23130    }).then(licenseXhrSetupResult => {
23131      // if licenseXhrSetup did not yet call open, let's do it now
23132      if (!xhr.readyState) {
23133        xhr.open('POST', url, true);
23134      }
23135      const finalLicenseChallenge = licenseXhrSetupResult ? licenseXhrSetupResult : licenseChallenge;
23136      return {
23137        xhr,
23138        licenseChallenge: finalLicenseChallenge
23139      };
23140    });
23141  }
23142  requestLicense(keySessionContext, licenseChallenge) {
23143    const keyLoadPolicy = this.config.keyLoadPolicy.default;
23144    return new Promise((resolve, reject) => {
23145      const url = this.getLicenseServerUrl(keySessionContext.keySystem);
23146      this.log(`Sending license request to URL: ${url}`);
23147      const xhr = new XMLHttpRequest();
23148      xhr.responseType = 'arraybuffer';
23149      xhr.onreadystatechange = () => {
23150        if (!this.hls || !keySessionContext.mediaKeysSession) {
23151          return reject(new Error('invalid state'));
23152        }
23153        if (xhr.readyState === 4) {
23154          if (xhr.status === 200) {
23155            this._requestLicenseFailureCount = 0;
23156            let data = xhr.response;
23157            this.log(`License received ${data instanceof ArrayBuffer ? data.byteLength : data}`);
23158            const licenseResponseCallback = this.config.licenseResponseCallback;
23159            if (licenseResponseCallback) {
23160              try {
23161                data = licenseResponseCallback.call(this.hls, xhr, url, keySessionContext);
23162              } catch (error) {
23163                this.error(error);
23164              }
23165            }
23166            resolve(data);
23167          } else {
23168            const retryConfig = keyLoadPolicy.errorRetry;
23169            const maxNumRetry = retryConfig ? retryConfig.maxNumRetry : 0;
23170            this._requestLicenseFailureCount++;
23171            if (this._requestLicenseFailureCount > maxNumRetry || xhr.status >= 400 && xhr.status < 500) {
23172              reject(new EMEKeyError({
23173                type: ErrorTypes.KEY_SYSTEM_ERROR,
23174                details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED,
23175                fatal: true,
23176                networkDetails: xhr,
23177                response: {
23178                  url,
23179                  data: undefined,
23180                  code: xhr.status,
23181                  text: xhr.statusText
23182                }
23183              }, `License Request XHR failed (${url}). Status: ${xhr.status} (${xhr.statusText})`));
23184            } else {
23185              const attemptsLeft = maxNumRetry - this._requestLicenseFailureCount + 1;
23186              this.warn(`Retrying license request, ${attemptsLeft} attempts left`);
23187              this.requestLicense(keySessionContext, licenseChallenge).then(resolve, reject);
23188            }
23189          }
23190        }
23191      };
23192      if (keySessionContext.licenseXhr && keySessionContext.licenseXhr.readyState !== XMLHttpRequest.DONE) {
23193        keySessionContext.licenseXhr.abort();
23194      }
23195      keySessionContext.licenseXhr = xhr;
23196      this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then(({
23197        xhr,
23198        licenseChallenge
23199      }) => {
23200        if (keySessionContext.keySystem == KeySystems.PLAYREADY) {
23201          licenseChallenge = this.unpackPlayReadyKeyMessage(xhr, licenseChallenge);
23202        }
23203        xhr.send(licenseChallenge);
23204      });
23205    });
23206  }
23207  onMediaAttached(event, data) {
23208    if (!this.config.emeEnabled) {
23209      return;
23210    }
23211    const media = data.media;
23212
23213    // keep reference of media
23214    this.media = media;
23215    media.addEventListener('encrypted', this.onMediaEncrypted);
23216    media.addEventListener('waitingforkey', this.onWaitingForKey);
23217  }
23218  onMediaDetached() {
23219    const media = this.media;
23220    const mediaKeysList = this.mediaKeySessions;
23221    if (media) {
23222      media.removeEventListener('encrypted', this.onMediaEncrypted);
23223      media.removeEventListener('waitingforkey', this.onWaitingForKey);
23224      this.media = null;
23225    }
23226    this._requestLicenseFailureCount = 0;
23227    this.setMediaKeysQueue = [];
23228    this.mediaKeySessions = [];
23229    this.keyIdToKeySessionPromise = {};
23230    LevelKey.clearKeyUriToKeyIdMap();
23231
23232    // Close all sessions and remove media keys from the video element.
23233    const keySessionCount = mediaKeysList.length;
23234    EMEController.CDMCleanupPromise = Promise.all(mediaKeysList.map(mediaKeySessionContext => this.removeSession(mediaKeySessionContext)).concat(media == null ? void 0 : media.setMediaKeys(null).catch(error => {
23235      this.log(`Could not clear media keys: ${error}`);
23236    }))).then(() => {
23237      if (keySessionCount) {
23238        this.log('finished closing key sessions and clearing media keys');
23239        mediaKeysList.length = 0;
23240      }
23241    }).catch(error => {
23242      this.log(`Could not close sessions and clear media keys: ${error}`);
23243    });
23244  }
23245  onManifestLoading() {
23246    this.keyFormatPromise = null;
23247  }
23248  onManifestLoaded(event, {
23249    sessionKeys
23250  }) {
23251    if (!sessionKeys || !this.config.emeEnabled) {
23252      return;
23253    }
23254    if (!this.keyFormatPromise) {
23255      const keyFormats = sessionKeys.reduce((formats, sessionKey) => {
23256        if (formats.indexOf(sessionKey.keyFormat) === -1) {
23257          formats.push(sessionKey.keyFormat);
23258        }
23259        return formats;
23260      }, []);
23261      this.log(`Selecting key-system from session-keys ${keyFormats.join(', ')}`);
23262      this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
23263    }
23264  }
23265  removeSession(mediaKeySessionContext) {
23266    const {
23267      mediaKeysSession,
23268      licenseXhr
23269    } = mediaKeySessionContext;
23270    if (mediaKeysSession) {
23271      this.log(`Remove licenses and keys and close session ${mediaKeysSession.sessionId}`);
23272      if (mediaKeySessionContext._onmessage) {
23273        mediaKeysSession.removeEventListener('message', mediaKeySessionContext._onmessage);
23274        mediaKeySessionContext._onmessage = undefined;
23275      }
23276      if (mediaKeySessionContext._onkeystatuseschange) {
23277        mediaKeysSession.removeEventListener('keystatuseschange', mediaKeySessionContext._onkeystatuseschange);
23278        mediaKeySessionContext._onkeystatuseschange = undefined;
23279      }
23280      if (licenseXhr && licenseXhr.readyState !== XMLHttpRequest.DONE) {
23281        licenseXhr.abort();
23282      }
23283      mediaKeySessionContext.mediaKeysSession = mediaKeySessionContext.decryptdata = mediaKeySessionContext.licenseXhr = undefined;
23284      const index = this.mediaKeySessions.indexOf(mediaKeySessionContext);
23285      if (index > -1) {
23286        this.mediaKeySessions.splice(index, 1);
23287      }
23288      return mediaKeysSession.remove().catch(error => {
23289        this.log(`Could not remove session: ${error}`);
23290      }).then(() => {
23291        return mediaKeysSession.close();
23292      }).catch(error => {
23293        this.log(`Could not close session: ${error}`);
23294      });
23295    }
23296  }
23297}
23298EMEController.CDMCleanupPromise = void 0;
23299class EMEKeyError extends Error {
23300  constructor(data, message) {
23301    super(message);
23302    this.data = void 0;
23303    data.error || (data.error = new Error(message));
23304    this.data = data;
23305    data.err = data.error;
23306  }
23307}
23308
23309/**
23310 * Common Media Object Type
23311 *
23312 * @group CMCD
23313 * @group CMSD
23314 *
23315 * @beta
23316 */
23317var CmObjectType;
23318(function (CmObjectType) {
23319  /**
23320   * text file, such as a manifest or playlist
23321   */
23322  CmObjectType["MANIFEST"] = "m";
23323  /**
23324   * audio only
23325   */
23326  CmObjectType["AUDIO"] = "a";
23327  /**
23328   * video only
23329   */
23330  CmObjectType["VIDEO"] = "v";
23331  /**
23332   * muxed audio and video
23333   */
23334  CmObjectType["MUXED"] = "av";
23335  /**
23336   * init segment
23337   */
23338  CmObjectType["INIT"] = "i";
23339  /**
23340   * caption or subtitle
23341   */
23342  CmObjectType["CAPTION"] = "c";
23343  /**
23344   * ISOBMFF timed text track
23345   */
23346  CmObjectType["TIMED_TEXT"] = "tt";
23347  /**
23348   * cryptographic key, license or certificate.
23349   */
23350  CmObjectType["KEY"] = "k";
23351  /**
23352   * other
23353   */
23354  CmObjectType["OTHER"] = "o";
23355})(CmObjectType || (CmObjectType = {}));
23356
23357/**
23358 * Common Media Streaming Format
23359 *
23360 * @group CMCD
23361 * @group CMSD
23362 *
23363 * @beta
23364 */
23365var CmStreamingFormat;
23366(function (CmStreamingFormat) {
23367  /**
23368   * MPEG DASH
23369   */
23370  CmStreamingFormat["DASH"] = "d";
23371  /**
23372   * HTTP Live Streaming (HLS)
23373   */
23374  CmStreamingFormat["HLS"] = "h";
23375  /**
23376   * Smooth Streaming
23377   */
23378  CmStreamingFormat["SMOOTH"] = "s";
23379  /**
23380   * Other
23381   */
23382  CmStreamingFormat["OTHER"] = "o";
23383})(CmStreamingFormat || (CmStreamingFormat = {}));
23384
23385/**
23386 * CMCD header fields.
23387 *
23388 * @group CMCD
23389 *
23390 * @beta
23391 */
23392var CmcdHeaderField;
23393(function (CmcdHeaderField) {
23394  /**
23395   * keys whose values vary with the object being requested.
23396   */
23397  CmcdHeaderField["OBJECT"] = "CMCD-Object";
23398  /**
23399   * keys whose values vary with each request.
23400   */
23401  CmcdHeaderField["REQUEST"] = "CMCD-Request";
23402  /**
23403   * keys whose values are expected to be invariant over the life of the session.
23404   */
23405  CmcdHeaderField["SESSION"] = "CMCD-Session";
23406  /**
23407   * keys whose values do not vary with every request or object.
23408   */
23409  CmcdHeaderField["STATUS"] = "CMCD-Status";
23410})(CmcdHeaderField || (CmcdHeaderField = {}));
23411
23412/**
23413 * The map of CMCD header fields to official CMCD keys.
23414 *
23415 * @internal
23416 *
23417 * @group CMCD
23418 */
23419const CmcdHeaderMap = {
23420  [CmcdHeaderField.OBJECT]: ['br', 'd', 'ot', 'tb'],
23421  [CmcdHeaderField.REQUEST]: ['bl', 'dl', 'mtp', 'nor', 'nrr', 'su'],
23422  [CmcdHeaderField.SESSION]: ['cid', 'pr', 'sf', 'sid', 'st', 'v'],
23423  [CmcdHeaderField.STATUS]: ['bs', 'rtp']
23424};
23425
23426/**
23427 * Structured Field Item
23428 *
23429 * @group Structured Field
23430 *
23431 * @beta
23432 */
23433class SfItem {
23434  constructor(value, params) {
23435    this.value = void 0;
23436    this.params = void 0;
23437    if (Array.isArray(value)) {
23438      value = value.map(v => v instanceof SfItem ? v : new SfItem(v));
23439    }
23440    this.value = value;
23441    this.params = params;
23442  }
23443}
23444
23445/**
23446 * A class to represent structured field tokens when `Symbol` is not available.
23447 *
23448 * @group Structured Field
23449 *
23450 * @beta
23451 */
23452class SfToken {
23453  constructor(description) {
23454    this.description = void 0;
23455    this.description = description;
23456  }
23457}
23458
23459const DICT = 'Dict';
23460
23461function format(value) {
23462  if (Array.isArray(value)) {
23463    return JSON.stringify(value);
23464  }
23465  if (value instanceof Map) {
23466    return 'Map{}';
23467  }
23468  if (value instanceof Set) {
23469    return 'Set{}';
23470  }
23471  if (typeof value === 'object') {
23472    return JSON.stringify(value);
23473  }
23474  return String(value);
23475}
23476function throwError(action, src, type, cause) {
23477  return new Error(`failed to ${action} "${format(src)}" as ${type}`, {
23478    cause
23479  });
23480}
23481
23482const BARE_ITEM = 'Bare Item';
23483
23484const BOOLEAN = 'Boolean';
23485
23486const BYTES = 'Byte Sequence';
23487
23488const DECIMAL = 'Decimal';
23489
23490const INTEGER = 'Integer';
23491
23492function isInvalidInt(value) {
23493  return value < -999999999999999 || 999999999999999 < value;
23494}
23495
23496const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
23497
23498const TOKEN = 'Token';
23499
23500const KEY = 'Key';
23501
23502function serializeError(src, type, cause) {
23503  return throwError('serialize', src, type, cause);
23504}
23505
23506// 4.1.9.  Serializing a Boolean
23507//
23508// Given a Boolean as input_boolean, return an ASCII string suitable for
23509// use in a HTTP field value.
23510//
23511// 1.  If input_boolean is not a boolean, fail serialization.
23512//
23513// 2.  Let output be an empty string.
23514//
23515// 3.  Append "?" to output.
23516//
23517// 4.  If input_boolean is true, append "1" to output.
23518//
23519// 5.  If input_boolean is false, append "0" to output.
23520//
23521// 6.  Return output.
23522function serializeBoolean(value) {
23523  if (typeof value !== 'boolean') {
23524    throw serializeError(value, BOOLEAN);
23525  }
23526  return value ? '?1' : '?0';
23527}
23528
23529/**
23530 * Encodes binary data to base64
23531 *
23532 * @param binary - The binary data to encode
23533 * @returns The base64 encoded string
23534 *
23535 * @group Utils
23536 *
23537 * @beta
23538 */
23539function base64encode(binary) {
23540  return btoa(String.fromCharCode(...binary));
23541}
23542
23543// 4.1.8.  Serializing a Byte Sequence
23544//
23545// Given a Byte Sequence as input_bytes, return an ASCII string suitable
23546// for use in a HTTP field value.
23547//
23548// 1.  If input_bytes is not a sequence of bytes, fail serialization.
23549//
23550// 2.  Let output be an empty string.
23551//
23552// 3.  Append ":" to output.
23553//
23554// 4.  Append the result of base64-encoding input_bytes as per
23555//     [RFC4648], Section 4, taking account of the requirements below.
23556//
23557// 5.  Append ":" to output.
23558//
23559// 6.  Return output.
23560//
23561// The encoded data is required to be padded with "=", as per [RFC4648],
23562// Section 3.2.
23563//
23564// Likewise, encoded data SHOULD have pad bits set to zero, as per
23565// [RFC4648], Section 3.5, unless it is not possible to do so due to
23566// implementation constraints.
23567function serializeByteSequence(value) {
23568  if (ArrayBuffer.isView(value) === false) {
23569    throw serializeError(value, BYTES);
23570  }
23571  return `:${base64encode(value)}:`;
23572}
23573
23574// 4.1.4.  Serializing an Integer
23575//
23576// Given an Integer as input_integer, return an ASCII string suitable
23577// for use in a HTTP field value.
23578//
23579// 1.  If input_integer is not an integer in the range of
23580//     -999,999,999,999,999 to 999,999,999,999,999 inclusive, fail
23581//     serialization.
23582//
23583// 2.  Let output be an empty string.
23584//
23585// 3.  If input_integer is less than (but not equal to) 0, append "-" to
23586//     output.
23587//
23588// 4.  Append input_integer's numeric value represented in base 10 using
23589//     only decimal digits to output.
23590//
23591// 5.  Return output.
23592function serializeInteger(value) {
23593  if (isInvalidInt(value)) {
vendor: 8,490 bytes, lines 23594-23869
23594    throw serializeError(value, INTEGER);
23595  }
23596  return value.toString();
23597}
23598
23599// 4.1.10.  Serializing a Date
23600//
23601// Given a Date as input_integer, return an ASCII string suitable for
23602// use in an HTTP field value.
23603// 1.  Let output be "@".
23604// 2.  Append to output the result of running Serializing an Integer
23605//     with input_date (Section 4.1.4).
23606// 3.  Return output.
23607function serializeDate(value) {
23608  return `@${serializeInteger(value.getTime() / 1000)}`;
23609}
23610
23611/**
23612 * This implements the rounding procedure described in step 2 of the "Serializing a Decimal" specification.
23613 * This rounding style is known as "even rounding", "banker's rounding", or "commercial rounding".
23614 *
23615 * @param value - The value to round
23616 * @param precision - The number of decimal places to round to
23617 * @returns The rounded value
23618 *
23619 * @group Utils
23620 *
23621 * @beta
23622 */
23623function roundToEven(value, precision) {
23624  if (value < 0) {
23625    return -roundToEven(-value, precision);
23626  }
23627  const decimalShift = Math.pow(10, precision);
23628  const isEquidistant = Math.abs(value * decimalShift % 1 - 0.5) < Number.EPSILON;
23629  if (isEquidistant) {
23630    // If the tail of the decimal place is 'equidistant' we round to the nearest even value
23631    const flooredValue = Math.floor(value * decimalShift);
23632    return (flooredValue % 2 === 0 ? flooredValue : flooredValue + 1) / decimalShift;
23633  } else {
23634    // Otherwise, proceed as normal
23635    return Math.round(value * decimalShift) / decimalShift;
23636  }
23637}
23638
23639// 4.1.5.  Serializing a Decimal
23640//
23641// Given a decimal number as input_decimal, return an ASCII string
23642// suitable for use in a HTTP field value.
23643//
23644// 1.   If input_decimal is not a decimal number, fail serialization.
23645//
23646// 2.   If input_decimal has more than three significant digits to the
23647//      right of the decimal point, round it to three decimal places,
23648//      rounding the final digit to the nearest value, or to the even
23649//      value if it is equidistant.
23650//
23651// 3.   If input_decimal has more than 12 significant digits to the left
23652//      of the decimal point after rounding, fail serialization.
23653//
23654// 4.   Let output be an empty string.
23655//
23656// 5.   If input_decimal is less than (but not equal to) 0, append "-"
23657//      to output.
23658//
23659// 6.   Append input_decimal's integer component represented in base 10
23660//      (using only decimal digits) to output; if it is zero, append
23661//      "0".
23662//
23663// 7.   Append "." to output.
23664//
23665// 8.   If input_decimal's fractional component is zero, append "0" to
23666//      output.
23667//
23668// 9.   Otherwise, append the significant digits of input_decimal's
23669//      fractional component represented in base 10 (using only decimal
23670//      digits) to output.
23671//
23672// 10.  Return output.
23673function serializeDecimal(value) {
23674  const roundedValue = roundToEven(value, 3); // round to 3 decimal places
23675  if (Math.floor(Math.abs(roundedValue)).toString().length > 12) {
23676    throw serializeError(value, DECIMAL);
23677  }
23678  const stringValue = roundedValue.toString();
23679  return stringValue.includes('.') ? stringValue : `${stringValue}.0`;
23680}
23681
23682const STRING = 'String';
23683
23684// 4.1.6.  Serializing a String
23685//
23686// Given a String as input_string, return an ASCII string suitable for
23687// use in a HTTP field value.
23688//
23689// 1.  Convert input_string into a sequence of ASCII characters; if
23690//     conversion fails, fail serialization.
23691//
23692// 2.  If input_string contains characters in the range %x00-1f or %x7f
23693//     (i.e., not in VCHAR or SP), fail serialization.
23694//
23695// 3.  Let output be the string DQUOTE.
23696//
23697// 4.  For each character char in input_string:
23698//
23699//     1.  If char is "\" or DQUOTE:
23700//
23701//         1.  Append "\" to output.
23702//
23703//     2.  Append char to output.
23704//
23705// 5.  Append DQUOTE to output.
23706//
23707// 6.  Return output.
23708function serializeString(value) {
23709  if (STRING_REGEX.test(value)) {
23710    throw serializeError(value, STRING);
23711  }
23712  return `"${value.replace(/\\/g, `\\\\`).replace(/"/g, `\\"`)}"`;
23713}
23714
23715function symbolToStr(symbol) {
23716  return symbol.description || symbol.toString().slice(7, -1);
23717}
23718
23719function serializeToken(token) {
23720  const value = symbolToStr(token);
23721  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
23722    throw serializeError(value, TOKEN);
23723  }
23724  return value;
23725}
23726
23727// 4.1.3.1.  Serializing a Bare Item
23728//
23729// Given an Item as input_item, return an ASCII string suitable for use
23730// in a HTTP field value.
23731//
23732// 1.  If input_item is an Integer, return the result of running
23733//     Serializing an Integer (Section 4.1.4) with input_item.
23734//
23735// 2.  If input_item is a Decimal, return the result of running
23736//     Serializing a Decimal (Section 4.1.5) with input_item.
23737//
23738// 3.  If input_item is a String, return the result of running
23739//     Serializing a String (Section 4.1.6) with input_item.
23740//
23741// 4.  If input_item is a Token, return the result of running
23742//     Serializing a Token (Section 4.1.7) with input_item.
23743//
23744// 5.  If input_item is a Boolean, return the result of running
23745//     Serializing a Boolean (Section 4.1.9) with input_item.
23746//
23747// 6.  If input_item is a Byte Sequence, return the result of running
23748//     Serializing a Byte Sequence (Section 4.1.8) with input_item.
23749//
23750// 7.  If input_item is a Date, return the result of running Serializing
23751//     a Date (Section 4.1.10) with input_item.
23752//
23753// 8.  Otherwise, fail serialization.
23754function serializeBareItem(value) {
23755  switch (typeof value) {
23756    case 'number':
23757      if (!isFiniteNumber(value)) {
23758        throw serializeError(value, BARE_ITEM);
23759      }
23760      if (Number.isInteger(value)) {
23761        return serializeInteger(value);
23762      }
23763      return serializeDecimal(value);
23764    case 'string':
23765      return serializeString(value);
23766    case 'symbol':
23767      return serializeToken(value);
23768    case 'boolean':
23769      return serializeBoolean(value);
23770    case 'object':
23771      if (value instanceof Date) {
23772        return serializeDate(value);
23773      }
23774      if (value instanceof Uint8Array) {
23775        return serializeByteSequence(value);
23776      }
23777      if (value instanceof SfToken) {
23778        return serializeToken(value);
23779      }
23780    default:
23781      // fail
23782      throw serializeError(value, BARE_ITEM);
23783  }
23784}
23785
23786// 4.1.1.3.  Serializing a Key
23787//
23788// Given a key as input_key, return an ASCII string suitable for use in
23789// a HTTP field value.
23790//
23791// 1.  Convert input_key into a sequence of ASCII characters; if
23792//     conversion fails, fail serialization.
23793//
23794// 2.  If input_key contains characters not in lcalpha, DIGIT, "_", "-",
23795//     ".", or "*" fail serialization.
23796//
23797// 3.  If the first character of input_key is not lcalpha or "*", fail
23798//     serialization.
23799//
23800// 4.  Let output be an empty string.
23801//
23802// 5.  Append input_key to output.
23803//
23804// 6.  Return output.
23805function serializeKey(value) {
23806  if (/^[a-z*][a-z0-9\-_.*]*$/.test(value) === false) {
23807    throw serializeError(value, KEY);
23808  }
23809  return value;
23810}
23811
23812// 4.1.1.2.  Serializing Parameters
23813//
23814// Given an ordered Dictionary as input_parameters (each member having a
23815// param_name and a param_value), return an ASCII string suitable for
23816// use in a HTTP field value.
23817//
23818// 1.  Let output be an empty string.
23819//
23820// 2.  For each param_name with a value of param_value in
23821//     input_parameters:
23822//
23823//     1.  Append ";" to output.
23824//
23825//     2.  Append the result of running Serializing a Key
23826//         (Section 4.1.1.3) with param_name to output.
23827//
23828//     3.  If param_value is not Boolean true:
23829//
23830//         1.  Append "=" to output.
23831//
23832//         2.  Append the result of running Serializing a bare Item
23833//             (Section 4.1.3.1) with param_value to output.
23834//
23835// 3.  Return output.
23836function serializeParams(params) {
23837  if (params == null) {
23838    return '';
23839  }
23840  return Object.entries(params).map(([key, value]) => {
23841    if (value === true) {
23842      return `;${serializeKey(key)}`; // omit true
23843    }
23844    return `;${serializeKey(key)}=${serializeBareItem(value)}`;
23845  }).join('');
23846}
23847
23848// 4.1.3.  Serializing an Item
23849//
23850// Given an Item as bare_item and Parameters as item_parameters, return
23851// an ASCII string suitable for use in a HTTP field value.
23852//
23853// 1.  Let output be an empty string.
23854//
23855// 2.  Append the result of running Serializing a Bare Item
23856//     Section 4.1.3.1 with bare_item to output.
23857//
23858// 3.  Append the result of running Serializing Parameters
23859//     Section 4.1.1.2 with item_parameters to output.
23860//
23861// 4.  Return output.
23862function serializeItem(value) {
23863  if (value instanceof SfItem) {
23864    return `${serializeBareItem(value.value)}${serializeParams(value.params)}`;
23865  } else {
23866    return serializeBareItem(value);
23867  }
23868}
23869
23870// 4.1.1.1.  Serializing an Inner List
23871//
23872// Given an array of (member_value, parameters) tuples as inner_list,
23873// and parameters as list_parameters, return an ASCII string suitable
23874// for use in a HTTP field value.
23875//
23876// 1.  Let output be the string "(".
23877//
23878// 2.  For each (member_value, parameters) of inner_list:
23879//
23880//     1.  Append the result of running Serializing an Item
23881//         (Section 4.1.3) with (member_value, parameters) to output.
23882//
23883//     2.  If more values remain in inner_list, append a single SP to
23884//         output.
23885//
23886// 3.  Append ")" to output.
23887//
23888// 4.  Append the result of running Serializing Parameters
23889//     (Section 4.1.1.2) with list_parameters to output.
23890//
23891// 5.  Return output.
23892function serializeInnerList(value) {
23893  return `(${value.value.map(serializeItem).join(' ')})${serializeParams(value.params)}`;
23894}
23895
23896// 4.1.2.  Serializing a Dictionary
23897//
23898// Given an ordered Dictionary as input_dictionary (each member having a
23899// member_name and a tuple value of (member_value, parameters)), return
23900// an ASCII string suitable for use in a HTTP field value.
23901//
23902// 1.  Let output be an empty string.
23903//
23904// 2.  For each member_name with a value of (member_value, parameters)
23905//     in input_dictionary:
23906//
23907//     1.  Append the result of running Serializing a Key
23908//         (Section 4.1.1.3) with member's member_name to output.
23909//
23910//     2.  If member_value is Boolean true:
23911//
23912//         1.  Append the result of running Serializing Parameters
23913//             (Section 4.1.1.2) with parameters to output.
23914//
23915//     3.  Otherwise:
23916//
23917//         1.  Append "=" to output.
23918//
23919//         2.  If member_value is an array, append the result of running
23920//             Serializing an Inner List (Section 4.1.1.1) with
23921//             (member_value, parameters) to output.
23922//
23923//         3.  Otherwise, append the result of running Serializing an
23924//             Item (Section 4.1.3) with (member_value, parameters) to
23925//             output.
23926//
23927//     4.  If more members remain in input_dictionary:
23928//
23929//         1.  Append "," to output.
23930//
23931//         2.  Append a single SP to output.
23932//
23933// 3.  Return output.
23934function serializeDict(dict, options = {
23935  whitespace: true
23936}) {
23937  if (typeof dict !== 'object') {
23938    throw serializeError(dict, DICT);
23939  }
23940  const entries = dict instanceof Map ? dict.entries() : Object.entries(dict);
23941  const optionalWhiteSpace = options != null && options.whitespace ? ' ' : '';
23942  return Array.from(entries).map(([key, item]) => {
23943    if (item instanceof SfItem === false) {
23944      item = new SfItem(item);
23945    }
23946    let output = serializeKey(key);
23947    if (item.value === true) {
23948      output += serializeParams(item.params);
23949    } else {
23950      output += '=';
23951      if (Array.isArray(item.value)) {
23952        output += serializeInnerList(item);
23953      } else {
23954        output += serializeItem(item);
23955      }
23956    }
23957    return output;
23958  }).join(`,${optionalWhiteSpace}`);
23959}
23960
23961/**
23962 * Encode an object into a structured field dictionary
23963 *
23964 * @param input - The structured field dictionary to encode
23965 * @returns The structured field string
23966 *
23967 * @group Structured Field
23968 *
23969 * @beta
23970 */
23971function encodeSfDict(value, options) {
23972  return serializeDict(value, options);
23973}
23974
23975/**
23976 * Checks if the given key is a token field.
23977 *
23978 * @param key - The key to check.
23979 *
23980 * @returns `true` if the key is a token field.
23981 *
23982 * @internal
23983 *
23984 * @group CMCD
23985 */
23986const isTokenField = key => key === 'ot' || key === 'sf' || key === 'st';
23987
23988const isValid = value => {
23989  if (typeof value === 'number') {
23990    return isFiniteNumber(value);
23991  }
23992  return value != null && value !== '' && value !== false;
23993};
23994
23995/**
23996 * Constructs a relative path from a URL.
23997 *
23998 * @param url - The destination URL
23999 * @param base - The base URL
24000 * @returns The relative path
24001 *
24002 * @group Utils
24003 *
24004 * @beta
24005 */
24006function urlToRelativePath(url, base) {
24007  const to = new URL(url);
24008  const from = new URL(base);
24009  if (to.origin !== from.origin) {
24010    return url;
24011  }
24012  const toPath = to.pathname.split('/').slice(1);
24013  const fromPath = from.pathname.split('/').slice(1, -1);
24014  // remove common parents
24015  while (toPath[0] === fromPath[0]) {
24016    toPath.shift();
24017    fromPath.shift();
24018  }
24019  // add back paths
24020  while (fromPath.length) {
24021    fromPath.shift();
24022    toPath.unshift('..');
24023  }
24024  return toPath.join('/');
24025}
24026
24027/**
24028 * Generate a random v4 UUID
24029 *
24030 * @returns A random v4 UUID
24031 *
24032 * @group Utils
24033 *
24034 * @beta
24035 */
24036function uuid() {
24037  try {
24038    return crypto.randomUUID();
24039  } catch (error) {
24040    try {
24041      const url = URL.createObjectURL(new Blob());
24042      const uuid = url.toString();
24043      URL.revokeObjectURL(url);
24044      return uuid.slice(uuid.lastIndexOf('/') + 1);
24045    } catch (error) {
24046      let dt = new Date().getTime();
24047      const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24048        const r = (dt + Math.random() * 16) % 16 | 0;
24049        dt = Math.floor(dt / 16);
24050        return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24051      });
24052      return uuid;
24053    }
24054  }
24055}
24056
24057const toRounded = value => Math.round(value);
24058const toUrlSafe = (value, options) => {
24059  if (options != null && options.baseUrl) {
24060    value = urlToRelativePath(value, options.baseUrl);
24061  }
24062  return encodeURIComponent(value);
24063};
24064const toHundred = value => toRounded(value / 100) * 100;
24065/**
24066 * The default formatters for CMCD values.
24067 *
24068 * @group CMCD
24069 *
24070 * @beta
24071 */
24072const CmcdFormatters = {
24073  /**
24074   * Bitrate (kbps) rounded integer
24075   */
24076  br: toRounded,
24077  /**
24078   * Duration (milliseconds) rounded integer
24079   */
24080  d: toRounded,
24081  /**
24082   * Buffer Length (milliseconds) rounded nearest 100ms
24083   */
24084  bl: toHundred,
24085  /**
24086   * Deadline (milliseconds) rounded nearest 100ms
24087   */
24088  dl: toHundred,
24089  /**
24090   * Measured Throughput (kbps) rounded nearest 100kbps
24091   */
24092  mtp: toHundred,
24093  /**
24094   * Next Object Request URL encoded
24095   */
24096  nor: toUrlSafe,
24097  /**
24098   * Requested maximum throughput (kbps) rounded nearest 100kbps
24099   */
24100  rtp: toHundred,
24101  /**
24102   * Top Bitrate (kbps) rounded integer
24103   */
24104  tb: toRounded
24105};
24106
24107/**
24108 * Internal CMCD processing function.
24109 *
24110 * @param obj - The CMCD object to process.
24111 * @param map - The mapping function to use.
24112 * @param options - Options for encoding.
24113 *
24114 * @internal
24115 *
24116 * @group CMCD
24117 */
24118function processCmcd(obj, options) {
24119  const results = {};
24120  if (obj == null || typeof obj !== 'object') {
24121    return results;
24122  }
24123  const keys = Object.keys(obj).sort();
24124  const formatters = _extends({}
24124, CmcdFormatters, options == null ? void 0 : options.formatters);
24125  const filter = options == null ? void 0 : options.filter;
24126  keys.forEach(key => {
24127    if (filter != null && filter(key)) {
24128      return;
24129    }
24130    let value = obj[key];
24131    const formatter = formatters[key];
24132    if (formatter) {
24133      value = formatter(value, options);
24134    }
24135    // Version should only be reported if not equal to 1.
24136    if (key === 'v' && value === 1) {
24137      return;
24138    }
24139    // Playback rate should only be sent if not equal to 1.
24140    if (key == 'pr' && value === 1) {
24141      return;
24142    }
24143    // ignore invalid values
24144    if (!isValid(value)) {
24145      return;
24146    }
24147    if (isTokenField(key) && typeof value === 'string') {
24148      value = new SfToken(value);
24149    }
24150    results[key] = value;
24151  });
24152  return results;
24153}
24154
24155/**
24156 * Encode a CMCD object to a string.
24157 *
24158 * @param cmcd - The CMCD object to encode.
24159 * @param options - Options for encoding.
24160 *
24161 * @returns The encoded CMCD string.
24162 *
24163 * @group CMCD
24164 *
24165 * @beta
24166 */
24167function encodeCmcd(cmcd, options = {}) {
24168  if (!cmcd) {
24169    return '';
24170  }
24171  return encodeSfDict(processCmcd(cmcd, options), _extends({
24172    whitespace: false
24173  }, options));
24174}
24175
24176/**
24177 * Convert a CMCD data object to request headers
24178 *
24179 * @param cmcd - The CMCD data object to convert.
24180 * @param options - Options for encoding the CMCD object.
24181 *
24182 * @returns The CMCD header shards.
24183 *
24184 * @group CMCD
24185 *
24186 * @beta
24187 */
24188function toCmcdHeaders(cmcd, options = {}) {
24189  if (!cmcd) {
24190    return {};
24191  }
24192  const entries = Object.entries(cmcd);
24193  const headerMap = Object.entries(CmcdHeaderMap).concat(Object.entries((options == null ? void 0 : options.customHeaderMap) || {}));
24194  const shards = entries.reduce((acc, entry) => {
24195    var _headerMap$find, _acc$field;
24196    const [key, value] = entry;
24197    const field = ((_headerMap$find = headerMap.find(entry => entry[1].includes(key))) == null ? void 0 : _headerMap$find[0]) || CmcdHeaderField.REQUEST;
24198    (_acc$field = acc[field]) != null ? _acc$field : acc[field] = {};
24199    acc[field][key] = value;
24200    return acc;
24201  }, {});
24202  return Object.entries(shards).reduce((acc, [field, value]) => {
24203    acc[field] = encodeCmcd(value, options);
24204    return acc;
24205  }, {});
24206}
24207
24208/**
24209 * Append CMCD query args to a header object.
24210 *
24211 * @param headers - The headers to append to.
24212 * @param cmcd - The CMCD object to append.
24213 * @param customHeaderMap - A map of custom CMCD keys to header fields.
24214 *
24215 * @returns The headers with the CMCD header shards appended.
24216 *
24217 * @group CMCD
24218 *
24219 * @beta
24220 */
24221function appendCmcdHeaders(headers, cmcd, options) {
24222  return _extends(headers, toCmcdHeaders(cmcd, options));
24223}
24224
24225/**
24226 * CMCD parameter name.
24227 *
24228 * @group CMCD
24229 *
24230 * @beta
24231 */
24232const CMCD_PARAM = 'CMCD';
24233
24234/**
24235 * Convert a CMCD data object to a query arg.
24236 *
24237 * @param cmcd - The CMCD object to convert.
24238 * @param options - Options for encoding the CMCD object.
24239 *
24240 * @returns The CMCD query arg.
24241 *
24242 * @group CMCD
24243 *
24244 * @beta
24245 */
24246function toCmcdQuery(cmcd, options = {}) {
24247  if (!cmcd) {
24248    return '';
24249  }
24250  const params = encodeCmcd(cmcd, options);
24251  return `${CMCD_PARAM}=${encodeURIComponent(params)}`;
24252}
24253
24254const REGEX = /CMCD=[^&#]+/;
24255/**
24256 * Append CMCD query args to a URL.
24257 *
24258 * @param url - The URL to append to.
24259 * @param cmcd - The CMCD object to append.
24260 * @param options - Options for encoding the CMCD object.
24261 *
24262 * @returns The URL with the CMCD query args appended.
24263 *
24264 * @group CMCD
24265 *
24266 * @beta
24267 */
24268function appendCmcdQuery(url, cmcd, options) {
24269  // TODO: Replace with URLSearchParams once we drop Safari < 10.1 & Chrome < 49 support.
24270  // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
24271  const query = toCmcdQuery(cmcd, options);
24272  if (!query) {
24273    return url;
24274  }
24275  if (REGEX.test(url)) {
24276    return url.replace(REGEX, query);
24277  }
24278  const separator = url.includes('?') ? '&' : '?';
24279  return `${url}${separator}${query}`;
24280}
24281
24282/**
24283 * Controller to deal with Common Media Client Data (CMCD)
24284 * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
24285 */
24286class CMCDController {
24287  // eslint-disable-line no-restricted-globals
24288
24289  constructor(hls) {
24290    this.hls = void 0;
24291    this.config = void 0;
24292    this.media = void 0;
24293    this.sid = void 0;
24294    this.cid = void 0;
24295    this.useHeaders = false;
vendor: 4,149 bytes, lines 24296-24431
24296    this.includeKeys = void 0;
24297    this.initialized = false;
24298    this.starved = false;
24299    this.buffering = true;
24300    this.audioBuffer = void 0;
24301    // eslint-disable-line no-restricted-globals
24302    this.videoBuffer = void 0;
24303    this.onWaiting = () => {
24304      if (this.initialized) {
24305        this.starved = true;
24306      }
24307      this.buffering = true;
24308    };
24309    this.onPlaying = () => {
24310      if (!this.initialized) {
24311        this.initialized = true;
24312      }
24313      this.buffering = false;
24314    };
24315    /**
24316     * Apply CMCD data to a manifest request.
24317     */
24318    this.applyPlaylistData = context => {
24319      try {
24320        this.apply(context, {
24321          ot: CmObjectType.MANIFEST,
24322          su: !this.initialized
24323        });
24324      } catch (error) {
24325        logger.warn('Could not generate manifest CMCD data.', error);
24326      }
24327    };
24328    /**
24329     * Apply CMCD data to a segment request
24330     */
24331    this.applyFragmentData = context => {
24332      try {
24333        const fragment = context.frag;
24334        const level = this.hls.levels[fragment.level];
24335        const ot = this.getObjectType(fragment);
24336        const data = {
24337          d: fragment.duration * 1000,
24338          ot
24339        };
24340        if (ot === CmObjectType.VIDEO || ot === CmObjectType.AUDIO || ot == CmObjectType.MUXED) {
24341          data.br = level.bitrate / 1000;
24342          data.tb = this.getTopBandwidth(ot) / 1000;
24343          data.bl = this.getBufferLength(ot);
24344        }
24345        this.apply(context, data);
24346      } catch (error) {
24347        logger.warn('Could not generate segment CMCD data.', error);
24348      }
24349    };
24350    this.hls = hls;
24351    const config = this.config = hls.config;
24352    const {
24353      cmcd
24354    } = config;
24355    if (cmcd != null) {
24356      config.pLoader = this.createPlaylistLoader();
24357      config.fLoader = this.createFragmentLoader();
24358      this.sid = cmcd.sessionId || uuid();
24359      this.cid = cmcd.contentId;
24360      this.useHeaders = cmcd.useHeaders === true;
24361      this.includeKeys = cmcd.includeKeys;
24362      this.registerListeners();
24363    }
24364  }
24365  registerListeners() {
24366    const hls = this.hls;
24367    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
24368    hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
24369    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
24370  }
24371  unregisterListeners() {
24372    const hls = this.hls;
24373    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
24374    hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
24375    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
24376  }
24377  destroy() {
24378    this.unregisterListeners();
24379    this.onMediaDetached();
24380
24381    // @ts-ignore
24382    this.hls = this.config = this.audioBuffer = this.videoBuffer = null;
24383    // @ts-ignore
24384    this.onWaiting = this.onPlaying = null;
24385  }
24386  onMediaAttached(event, data) {
24387    this.media = data.media;
24388    this.media.addEventListener('waiting', this.onWaiting);
24389    this.media.addEventListener('playing', this.onPlaying);
24390  }
24391  onMediaDetached() {
24392    if (!this.media) {
24393      return;
24394    }
24395    this.media.removeEventListener('waiting', this.onWaiting);
24396    this.media.removeEventListener('playing', this.onPlaying);
24397
24398    // @ts-ignore
24399    this.media = null;
24400  }
24401  onBufferCreated(event, data) {
24402    var _data$tracks$audio, _data$tracks$video;
24403    this.audioBuffer = (_data$tracks$audio = data.tracks.audio) == null ? void 0 : _data$tracks$audio.buffer;
24404    this.videoBuffer = (_data$tracks$video = data.tracks.video) == null ? void 0 : _data$tracks$video.buffer;
24405  }
24406  /**
24407   * Create baseline CMCD data
24408   */
24409  createData() {
24410    var _this$media;
24411    return {
24412      v: 1,
24413      sf: CmStreamingFormat.HLS,
24414      sid: this.sid,
24415      cid: this.cid,
24416      pr: (_this$media = this.media) == null ? void 0 : _this$media.playbackRate,
24417      mtp: this.hls.bandwidthEstimate / 1000
24418    };
24419  }
24420
24421  /**
24422   * Apply CMCD data to a request.
24423   */
24424  apply(context, data = {}) {
24425    // apply baseline data
24426    _extends(data, this.createData());
24427    const isVideo = data.ot === CmObjectType.INIT || data.ot === CmObjectType.VIDEO || data.ot === CmObjectType.MUXED;
24428    if (this.starved && isVideo) {
24429      data.bs = true;
24430      data.su = true;
24431      this.starved = false;
vendor: 5,096 bytes, lines 24432-24638
24432    }
24433    if (data.su == null) {
24434      data.su = this.buffering;
24435    }
24436
24437    // TODO: Implement rtp, nrr, nor, dl
24438
24439    const {
24440      includeKeys
24441    } = this;
24442    if (includeKeys) {
24443      data = Object.keys(data).reduce((acc, key) => {
24444        includeKeys.includes(key) && (acc[key] = data[key]);
24445        return acc;
24446      }, {});
24447    }
24448    if (this.useHeaders) {
24449      if (!context.headers) {
24450        context.headers = {};
24451      }
24452      appendCmcdHeaders(context.headers, data);
24453    } else {
24454      context.url = appendCmcdQuery(context.url, data);
24455    }
24456  }
24457  /**
24458   * The CMCD object type.
24459   */
24460  getObjectType(fragment) {
24461    const {
24462      type
24463    } = fragment;
24464    if (type === 'subtitle') {
24465      return CmObjectType.TIMED_TEXT;
24466    }
24467    if (fragment.sn === 'initSegment') {
24468      return CmObjectType.INIT;
24469    }
24470    if (type === 'audio') {
24471      return CmObjectType.AUDIO;
24472    }
24473    if (type === 'main') {
24474      if (!this.hls.audioTracks.length) {
24475        return CmObjectType.MUXED;
24476      }
24477      return CmObjectType.VIDEO;
24478    }
24479    return undefined;
24480  }
24481
24482  /**
24483   * Get the highest bitrate.
24484   */
24485  getTopBandwidth(type) {
24486    let bitrate = 0;
24487    let levels;
24488    const hls = this.hls;
24489    if (type === CmObjectType.AUDIO) {
24490      levels = hls.audioTracks;
24491    } else {
24492      const max = hls.maxAutoLevel;
24493      const len = max > -1 ? max + 1 : hls.levels.length;
24494      levels = hls.levels.slice(0, len);
24495    }
24496    for (const level of levels) {
24497      if (level.bitrate > bitrate) {
24498        bitrate = level.bitrate;
24499      }
24500    }
24501    return bitrate > 0 ? bitrate : NaN;
24502  }
24503
24504  /**
24505   * Get the buffer length for a media type in milliseconds
24506   */
24507  getBufferLength(type) {
24508    const media = this.hls.media;
24509    const buffer = type === CmObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
24510    if (!buffer || !media) {
24511      return NaN;
24512    }
24513    const info = BufferHelper.bufferInfo(buffer, media.currentTime, this.config.maxBufferHole);
24514    return info.len * 1000;
24515  }
24516
24517  /**
24518   * Create a playlist loader
24519   */
24520  createPlaylistLoader() {
24521    const {
24522      pLoader
24523    } = this.config;
24524    const apply = this.applyPlaylistData;
24525    const Ctor = pLoader || this.config.loader;
24526    return class CmcdPlaylistLoader {
24527      constructor(config) {
24528        this.loader = void 0;
24529        this.loader = new Ctor(config);
24530      }
24531      get stats() {
24532        return this.loader.stats;
24533      }
24534      get context() {
24535        return this.loader.context;
24536      }
24537      destroy() {
24538        this.loader.destroy();
24539      }
24540      abort() {
24541        this.loader.abort();
24542      }
24543      load(context, config, callbacks) {
24544        apply(context);
24545        this.loader.load(context, config, callbacks);
24546      }
24547    };
24548  }
24549
24550  /**
24551   * Create a playlist loader
24552   */
24553  createFragmentLoader() {
24554    const {
24555      fLoader
24556    } = this.config;
24557    const apply = this.applyFragmentData;
24558    const Ctor = fLoader || this.config.loader;
24559    return class CmcdFragmentLoader {
24560      constructor(config) {
24561        this.loader = void 0;
24562        this.loader = new Ctor(config);
24563      }
24564      get stats() {
24565        return this.loader.stats;
24566      }
24567      get context() {
24568        return this.loader.context;
24569      }
24570      destroy() {
24571        this.loader.destroy();
24572      }
24573      abort() {
24574        this.loader.abort();
24575      }
24576      load(context, config, callbacks) {
24577        apply(context);
24578        this.loader.load(context, config, callbacks);
24579      }
24580    };
24581  }
24582}
24583
24584const PATHWAY_PENALTY_DURATION_MS = 300000;
24585class ContentSteeringController {
24586  constructor(hls) {
24587    this.hls = void 0;
24588    this.log = void 0;
24589    this.loader = null;
24590    this.uri = null;
24591    this.pathwayId = '.';
24592    this.pathwayPriority = null;
24593    this.timeToLoad = 300;
24594    this.reloadTimer = -1;
24595    this.updated = 0;
24596    this.started = false;
24597    this.enabled = true;
24598    this.levels = null;
24599    this.audioTracks = null;
24600    this.subtitleTracks = null;
24601    this.penalizedPathways = {};
24602    this.hls = hls;
24603    this.log = logger.log.bind(logger, `[content-steering]:`);
24604    this.registerListeners();
24605  }
24606  registerListeners() {
24607    const hls = this.hls;
24608    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
24609    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
24610    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
24611    hls.on(Events.ERROR, this.onError, this);
24612  }
24613  unregisterListeners() {
24614    const hls = this.hls;
24615    if (!hls) {
24616      return;
24617    }
24618    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
24619    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
24620    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
24621    hls.off(Events.ERROR, this.onError, this);
24622  }
24623  startLoad() {
24624    this.started = true;
24625    this.clearTimeout();
24626    if (this.enabled && this.uri) {
24627      if (this.updated) {
24628        const ttl = this.timeToLoad * 1000 - (performance.now() - this.updated);
24629        if (ttl > 0) {
24630          this.scheduleRefresh(this.uri, ttl);
24631          return;
24632        }
24633      }
24634      this.loadSteeringManifest(this.uri);
24635    }
24636  }
24637  stopLoad() {
24638    this.started = false;
vendor: 4,184 bytes, lines 24639-24761
24639    if (this.loader) {
24640      this.loader.destroy();
24641      this.loader = null;
24642    }
24643    this.clearTimeout();
24644  }
24645  clearTimeout() {
24646    if (this.reloadTimer !== -1) {
24647      self.clearTimeout(this.reloadTimer);
24648      this.reloadTimer = -1;
24649    }
24650  }
24651  destroy() {
24652    this.unregisterListeners();
24653    this.stopLoad();
24654    // @ts-ignore
24655    this.hls = null;
24656    this.levels = this.audioTracks = this.subtitleTracks = null;
24657  }
24658  removeLevel(levelToRemove) {
24659    const levels = this.levels;
24660    if (levels) {
24661      this.levels = levels.filter(level => level !== levelToRemove);
24662    }
24663  }
24664  onManifestLoading() {
24665    this.stopLoad();
24666    this.enabled = true;
24667    this.timeToLoad = 300;
24668    this.updated = 0;
24669    this.uri = null;
24670    this.pathwayId = '.';
24671    this.levels = this.audioTracks = this.subtitleTracks = null;
24672  }
24673  onManifestLoaded(event, data) {
24674    const {
24675      contentSteering
24676    } = data;
24677    if (contentSteering === null) {
24678      return;
24679    }
24680    this.pathwayId = contentSteering.pathwayId;
24681    this.uri = contentSteering.uri;
24682    if (this.started) {
24683      this.startLoad();
24684    }
24685  }
24686  onManifestParsed(event, data) {
24687    this.audioTracks = data.audioTracks;
24688    this.subtitleTracks = data.subtitleTracks;
24689  }
24690  onError(event, data) {
24691    const {
24692      errorAction
24693    } = data;
24694    if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox && errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost) {
24695      const levels = this.levels;
24696      let pathwayPriority = this.pathwayPriority;
24697      let errorPathway = this.pathwayId;
24698      if (data.context) {
24699        const {
24700          groupId,
24701          pathwayId,
24702          type
24703        } = data.context;
24704        if (groupId && levels) {
24705          errorPathway = this.getPathwayForGroupId(groupId, type, errorPathway);
24706        } else if (pathwayId) {
24707          errorPathway = pathwayId;
24708        }
24709      }
24710      if (!(errorPathway in this.penalizedPathways)) {
24711        this.penalizedPathways[errorPathway] = performance.now();
24712      }
24713      if (!pathwayPriority && levels) {
24714        // If PATHWAY-PRIORITY was not provided, list pathways for error handling
24715        pathwayPriority = levels.reduce((pathways, level) => {
24716          if (pathways.indexOf(level.pathwayId) === -1) {
24717            pathways.push(level.pathwayId);
24718          }
24719          return pathways;
24720        }, []);
24721      }
24722      if (pathwayPriority && pathwayPriority.length > 1) {
24723        this.updatePathwayPriority(pathwayPriority);
24724        errorAction.resolved = this.pathwayId !== errorPathway;
24725      }
24726      if (!errorAction.resolved) {
24727        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)}`);
24728      }
24729    }
24730  }
24731  filterParsedLevels(levels) {
24732    // Filter levels to only include those that are in the initial pathway
24733    this.levels = levels;
24734    let pathwayLevels = this.getLevelsForPathway(this.pathwayId);
24735    if (pathwayLevels.length === 0) {
24736      const pathwayId = levels[0].pathwayId;
24737      this.log(`No levels found in Pathway ${this.pathwayId}. Setting initial Pathway to "${pathwayId}"`);
24738      pathwayLevels = this.getLevelsForPathway(pathwayId);
24739      this.pathwayId = pathwayId;
24740    }
24741    if (pathwayLevels.length !== levels.length) {
24742      this.log(`Found ${pathwayLevels.length}/${levels.length} levels in Pathway "${this.pathwayId}"`);
24743      return pathwayLevels;
24744    }
24745    return levels;
24746  }
24747  getLevelsForPathway(pathwayId) {
24748    if (this.levels === null) {
24749      return [];
24750    }
24751    return this.levels.filter(level => pathwayId === level.pathwayId);
24752  }
24753  updatePathwayPriority(pathwayPriority) {
24754    this.pathwayPriority = pathwayPriority;
24755    let levels;
24756
24757    // Evaluate if we should remove the pathway from the penalized list
24758    const penalizedPathways = this.penalizedPathways;
24759    const now = performance.now();
24760    Object.keys(penalizedPathways).forEach(pathwayId => {
24761      if (now - penalizedPathways[pathwayId] > PATHWAY_PENALTY_DURATION_MS) {
vendor: 4,399 bytes, lines 24762-24872
24762        delete penalizedPathways[pathwayId];
24763      }
24764    });
24765    for (let i = 0; i < pathwayPriority.length; i++) {
24766      const pathwayId = pathwayPriority[i];
24767      if (pathwayId in penalizedPathways) {
24768        continue;
24769      }
24770      if (pathwayId === this.pathwayId) {
24771        return;
24772      }
24773      const selectedIndex = this.hls.nextLoadLevel;
24774      const selectedLevel = this.hls.levels[selectedIndex];
24775      levels = this.getLevelsForPathway(pathwayId);
24776      if (levels.length > 0) {
24777        this.log(`Setting Pathway to "${pathwayId}"`);
24778        this.pathwayId = pathwayId;
24779        reassignFragmentLevelIndexes(levels);
24780        this.hls.trigger(Events.LEVELS_UPDATED, {
24781          levels
24782        });
24783        // Set LevelController's level to trigger LEVEL_SWITCHING which loads playlist if needed
24784        const levelAfterChange = this.hls.levels[selectedIndex];
24785        if (selectedLevel && levelAfterChange && this.levels) {
24786          if (levelAfterChange.attrs['STABLE-VARIANT-ID'] !== selectedLevel.attrs['STABLE-VARIANT-ID'] && levelAfterChange.bitrate !== selectedLevel.bitrate) {
24787            this.log(`Unstable Pathways change from bitrate ${selectedLevel.bitrate} to ${levelAfterChange.bitrate}`);
24788          }
24789          this.hls.nextLoadLevel = selectedIndex;
24790        }
24791        break;
24792      }
24793    }
24794  }
24795  getPathwayForGroupId(groupId, type, defaultPathway) {
24796    const levels = this.getLevelsForPathway(defaultPathway).concat(this.levels || []);
24797    for (let i = 0; i < levels.length; i++) {
24798      if (type === PlaylistContextType.AUDIO_TRACK && levels[i].hasAudioGroup(groupId) || type === PlaylistContextType.SUBTITLE_TRACK && levels[i].hasSubtitleGroup(groupId)) {
24799        return levels[i].pathwayId;
24800      }
24801    }
24802    return defaultPathway;
24803  }
24804  clonePathways(pathwayClones) {
24805    const levels = this.levels;
24806    if (!levels) {
24807      return;
24808    }
24809    const audioGroupCloneMap = {};
24810    const subtitleGroupCloneMap = {};
24811    pathwayClones.forEach(pathwayClone => {
24812      const {
24813        ID: cloneId,
24814        'BASE-ID': baseId,
24815        'URI-REPLACEMENT': uriReplacement
24816      } = pathwayClone;
24817      if (levels.some(level => level.pathwayId === cloneId)) {
24818        return;
24819      }
24820      const clonedVariants = this.getLevelsForPathway(baseId).map(baseLevel => {
24821        const attributes = new AttrList(baseLevel.attrs);
24822        attributes['PATHWAY-ID'] = cloneId;
24823        const clonedAudioGroupId = attributes.AUDIO && `${attributes.AUDIO}_clone_${cloneId}`;
24824        const clonedSubtitleGroupId = attributes.SUBTITLES && `${attributes.SUBTITLES}_clone_${cloneId}`;
24825        if (clonedAudioGroupId) {
24826          audioGroupCloneMap[attributes.AUDIO] = clonedAudioGroupId;
24827          attributes.AUDIO = clonedAudioGroupId;
24828        }
24829        if (clonedSubtitleGroupId) {
24830          subtitleGroupCloneMap[attributes.SUBTITLES] = clonedSubtitleGroupId;
24831          attributes.SUBTITLES = clonedSubtitleGroupId;
24832        }
24833        const url = performUriReplacement(baseLevel.uri, attributes['STABLE-VARIANT-ID'], 'PER-VARIANT-URIS', uriReplacement);
24834        const clonedLevel = new Level({
24835          attrs: attributes,
24836          audioCodec: baseLevel.audioCodec,
24837          bitrate: baseLevel.bitrate,
24838          height: baseLevel.height,
24839          name: baseLevel.name,
24840          url,
24841          videoCodec: baseLevel.videoCodec,
24842          width: baseLevel.width
24843        });
24844        if (baseLevel.audioGroups) {
24845          for (let i = 1; i < baseLevel.audioGroups.length; i++) {
24846            clonedLevel.addGroupId('audio', `${baseLevel.audioGroups[i]}_clone_${cloneId}`);
24847          }
24848        }
24849        if (baseLevel.subtitleGroups) {
24850          for (let i = 1; i < baseLevel.subtitleGroups.length; i++) {
24851            clonedLevel.addGroupId('text', `${baseLevel.subtitleGroups[i]}_clone_${cloneId}`);
24852          }
24853        }
24854        return clonedLevel;
24855      });
24856      levels.push(...clonedVariants);
24857      cloneRenditionGroups(this.audioTracks, audioGroupCloneMap, uriReplacement, cloneId);
24858      cloneRenditionGroups(this.subtitleTracks, subtitleGroupCloneMap, uriReplacement, cloneId);
24859    });
24860  }
24861  loadSteeringManifest(uri) {
24862    const config = this.hls.config;
24863    const Loader = config.loader;
24864    if (this.loader) {
24865      this.loader.destroy();
24866    }
24867    this.loader = new Loader(config);
24868    let url;
24869    try {
24870      url = new self.URL(uri);
24871    } catch (error) {
24872      this.enabled = false;
vendor: 5,045 bytes, lines 24873-25008
24873      this.log(`Failed to parse Steering Manifest URI: ${uri}`);
24874      return;
24875    }
24876    if (url.protocol !== 'data:') {
24877      const throughput = (this.hls.bandwidthEstimate || config.abrEwmaDefaultEstimate) | 0;
24878      url.searchParams.set('_HLS_pathway', this.pathwayId);
24879      url.searchParams.set('_HLS_throughput', '' + throughput);
24880    }
24881    const context = {
24882      responseType: 'json',
24883      url: url.href
24884    };
24885    const loadPolicy = config.steeringManifestLoadPolicy.default;
24886    const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
24887    const loaderConfig = {
24888      loadPolicy,
24889      timeout: loadPolicy.maxLoadTimeMs,
24890      maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
24891      retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
24892      maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0
24893    };
24894    const callbacks = {
24895      onSuccess: (response, stats, context, networkDetails) => {
24896        this.log(`Loaded steering manifest: "${url}"`);
24897        const steeringData = response.data;
24898        if (steeringData.VERSION !== 1) {
24899          this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
24900          return;
24901        }
24902        this.updated = performance.now();
24903        this.timeToLoad = steeringData.TTL;
24904        const {
24905          'RELOAD-URI': reloadUri,
24906          'PATHWAY-CLONES': pathwayClones,
24907          'PATHWAY-PRIORITY': pathwayPriority
24908        } = steeringData;
24909        if (reloadUri) {
24910          try {
24911            this.uri = new self.URL(reloadUri, url).href;
24912          } catch (error) {
24913            this.enabled = false;
24914            this.log(`Failed to parse Steering Manifest RELOAD-URI: ${reloadUri}`);
24915            return;
24916          }
24917        }
24918        this.scheduleRefresh(this.uri || context.url);
24919        if (pathwayClones) {
24920          this.clonePathways(pathwayClones);
24921        }
24922        const loadedSteeringData = {
24923          steeringManifest: steeringData,
24924          url: url.toString()
24925        };
24926        this.hls.trigger(Events.STEERING_MANIFEST_LOADED, loadedSteeringData);
24927        if (pathwayPriority) {
24928          this.updatePathwayPriority(pathwayPriority);
24929        }
24930      },
24931      onError: (error, context, networkDetails, stats) => {
24932        this.log(`Error loading steering manifest: ${error.code} ${error.text} (${context.url})`);
24933        this.stopLoad();
24934        if (error.code === 410) {
24935          this.enabled = false;
24936          this.log(`Steering manifest ${context.url} no longer available`);
24937          return;
24938        }
24939        let ttl = this.timeToLoad * 1000;
24940        if (error.code === 429) {
24941          const loader = this.loader;
24942          if (typeof (loader == null ? void 0 : loader.getResponseHeader) === 'function') {
24943            const retryAfter = loader.getResponseHeader('Retry-After');
24944            if (retryAfter) {
24945              ttl = parseFloat(retryAfter) * 1000;
24946            }
24947          }
24948          this.log(`Steering manifest ${context.url} rate limited`);
24949          return;
24950        }
24951        this.scheduleRefresh(this.uri || context.url, ttl);
24952      },
24953      onTimeout: (stats, context, networkDetails) => {
24954        this.log(`Timeout loading steering manifest (${context.url})`);
24955        this.scheduleRefresh(this.uri || context.url);
24956      }
24957    };
24958    this.log(`Requesting steering manifest: ${url}`);
24959    this.loader.load(context, loaderConfig, callbacks);
24960  }
24961  scheduleRefresh(uri, ttlMs = this.timeToLoad * 1000) {
24962    this.clearTimeout();
24963    this.reloadTimer = self.setTimeout(() => {
24964      var _this$hls;
24965      const media = (_this$hls = this.hls) == null ? void 0 : _this$hls.media;
24966      if (media && !media.ended) {
24967        this.loadSteeringManifest(uri);
24968        return;
24969      }
24970      this.scheduleRefresh(uri, this.timeToLoad * 1000);
24971    }, ttlMs);
24972  }
24973}
24974function cloneRenditionGroups(tracks, groupCloneMap, uriReplacement, cloneId) {
24975  if (!tracks) {
24976    return;
24977  }
24978  Object.keys(groupCloneMap).forEach(audioGroupId => {
24979    const clonedTracks = tracks.filter(track => track.groupId === audioGroupId).map(track => {
24980      const clonedTrack = _extends({}, track);
24981      clonedTrack.details = undefined;
24982      clonedTrack.attrs = new AttrList(clonedTrack.attrs);
24983      clonedTrack.url = clonedTrack.attrs.URI = performUriReplacement(track.url, track.attrs['STABLE-RENDITION-ID'], 'PER-RENDITION-URIS', uriReplacement);
24984      clonedTrack.groupId = clonedTrack.attrs['GROUP-ID'] = groupCloneMap[audioGroupId];
24985      clonedTrack.attrs['PATHWAY-ID'] = cloneId;
24986      return clonedTrack;
24987    });
24988    tracks.push(...clonedTracks);
24989  });
24990}
24991function performUriReplacement(uri, stableId, perOptionKey, uriReplacement) {
24992  const {
24993    HOST: host,
24994    PARAMS: params,
24995    [perOptionKey]: perOptionUris
24996  } = uriReplacement;
24997  let perVariantUri;
24998  if (stableId) {
24999    perVariantUri = perOptionUris == null ? void 0 : perOptionUris[stableId];
25000    if (perVariantUri) {
25001      uri = perVariantUri;
25002    }
25003  }
25004  const url = new self.URL(uri);
25005  if (host && !perVariantUri) {
25006    url.host = host;
25007  }
25008  if (params) {
vendor: 8,897 bytes, lines 25009-25279
25009    Object.keys(params).sort().forEach(key => {
25010      if (key) {
25011        url.searchParams.set(key, params[key]);
25012      }
25013    });
25014  }
25015  return url.href;
25016}
25017
25018const AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im;
25019class XhrLoader {
25020  constructor(config) {
25021    this.xhrSetup = void 0;
25022    this.requestTimeout = void 0;
25023    this.retryTimeout = void 0;
25024    this.retryDelay = void 0;
25025    this.config = null;
25026    this.callbacks = null;
25027    this.context = null;
25028    this.loader = null;
25029    this.stats = void 0;
25030    this.xhrSetup = config ? config.xhrSetup || null : null;
25031    this.stats = new LoadStats();
25032    this.retryDelay = 0;
25033  }
25034  destroy() {
25035    this.callbacks = null;
25036    this.abortInternal();
25037    this.loader = null;
25038    this.config = null;
25039    this.context = null;
25040    this.xhrSetup = null;
25041  }
25042  abortInternal() {
25043    const loader = this.loader;
25044    self.clearTimeout(this.requestTimeout);
25045    self.clearTimeout(this.retryTimeout);
25046    if (loader) {
25047      loader.onreadystatechange = null;
25048      loader.onprogress = null;
25049      if (loader.readyState !== 4) {
25050        this.stats.aborted = true;
25051        loader.abort();
25052      }
25053    }
25054  }
25055  abort() {
25056    var _this$callbacks;
25057    this.abortInternal();
25058    if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) {
25059      this.callbacks.onAbort(this.stats, this.context, this.loader);
25060    }
25061  }
25062  load(context, config, callbacks) {
25063    if (this.stats.loading.start) {
25064      throw new Error('Loader can only be used once.');
25065    }
25066    this.stats.loading.start = self.performance.now();
25067    this.context = context;
25068    this.config = config;
25069    this.callbacks = callbacks;
25070    this.loadInternal();
25071  }
25072  loadInternal() {
25073    const {
25074      config,
25075      context
25076    } = this;
25077    if (!config || !context) {
25078      return;
25079    }
25080    const xhr = this.loader = new self.XMLHttpRequest();
25081    const stats = this.stats;
25082    stats.loading.first = 0;
25083    stats.loaded = 0;
25084    stats.aborted = false;
25085    const xhrSetup = this.xhrSetup;
25086    if (xhrSetup) {
25087      Promise.resolve().then(() => {
25088        if (this.loader !== xhr || this.stats.aborted) return;
25089        return xhrSetup(xhr, context.url);
25090      }).catch(error => {
25091        if (this.loader !== xhr || this.stats.aborted) return;
25092        xhr.open('GET', context.url, true);
25093        return xhrSetup(xhr, context.url);
25094      }).then(() => {
25095        if (this.loader !== xhr || this.stats.aborted) return;
25096        this.openAndSendXhr(xhr, context, config);
25097      }).catch(error => {
25098        // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS
25099        this.callbacks.onError({
25100          code: xhr.status,
25101          text: error.message
25102        }, context, xhr, stats);
25103        return;
25104      });
25105    } else {
25106      this.openAndSendXhr(xhr, context, config);
25107    }
25108  }
25109  openAndSendXhr(xhr, context, config) {
25110    if (!xhr.readyState) {
25111      xhr.open('GET', context.url, true);
25112    }
25113    const headers = context.headers;
25114    const {
25115      maxTimeToFirstByteMs,
25116      maxLoadTimeMs
25117    } = config.loadPolicy;
25118    if (headers) {
25119      for (const header in headers) {
25120        xhr.setRequestHeader(header, headers[header]);
25121      }
25122    }
25123    if (context.rangeEnd) {
25124      xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1));
25125    }
25126    xhr.onreadystatechange = this.readystatechange.bind(this);
25127    xhr.onprogress = this.loadprogress.bind(this);
25128    xhr.responseType = context.responseType;
25129    // setup timeout before we perform request
25130    self.clearTimeout(this.requestTimeout);
25131    config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
25132    this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout);
25133    xhr.send();
25134  }
25135  readystatechange() {
25136    const {
25137      context,
25138      loader: xhr,
25139      stats
25140    } = this;
25141    if (!context || !xhr) {
25142      return;
25143    }
25144    const readyState = xhr.readyState;
25145    const config = this.config;
25146
25147    // don't proceed if xhr has been aborted
25148    if (stats.aborted) {
25149      return;
25150    }
25151
25152    // >= HEADERS_RECEIVED
25153    if (readyState >= 2) {
25154      if (stats.loading.first === 0) {
25155        stats.loading.first = Math.max(self.performance.now(), stats.loading.start);
25156        // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet
25157        if (config.timeout !== config.loadPolicy.maxLoadTimeMs) {
25158          self.clearTimeout(this.requestTimeout);
25159          config.timeout = config.loadPolicy.maxLoadTimeMs;
25160          this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start));
25161        }
25162      }
25163      if (readyState === 4) {
25164        self.clearTimeout(this.requestTimeout);
25165        xhr.onreadystatechange = null;
25166        xhr.onprogress = null;
25167        const status = xhr.status;
25168        // http status between 200 to 299 are all successful
25169        const useResponse = xhr.responseType !== 'text';
25170        if (status >= 200 && status < 300 && (useResponse && xhr.response || xhr.responseText !== null)) {
25171          stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
25172          const data = useResponse ? xhr.response : xhr.responseText;
25173          const len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
25174          stats.loaded = stats.total = len;
25175          stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first);
25176          if (!this.callbacks) {
25177            return;
25178          }
25179          const onProgress = this.callbacks.onProgress;
25180          if (onProgress) {
25181            onProgress(stats, context, data, xhr);
25182          }
25183          if (!this.callbacks) {
25184            return;
25185          }
25186          const response = {
25187            url: xhr.responseURL,
25188            data: data,
25189            code: status
25190          };
25191          this.callbacks.onSuccess(response, stats, context, xhr);
25192        } else {
25193          const retryConfig = config.loadPolicy.errorRetry;
25194          const retryCount = stats.retry;
25195          // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
25196          const response = {
25197            url: context.url,
25198            data: undefined,
25199            code: status
25200          };
25201          if (shouldRetry(retryConfig, retryCount, false, response)) {
25202            this.retry(retryConfig);
25203          } else {
25204            logger.error(`${status} while loading ${context.url}`);
25205            this.callbacks.onError({
25206              code: status,
25207              text: xhr.statusText
25208            }, context, xhr, stats);
25209          }
25210        }
25211      }
25212    }
25213  }
25214  loadtimeout() {
25215    if (!this.config) return;
25216    const retryConfig = this.config.loadPolicy.timeoutRetry;
25217    const retryCount = this.stats.retry;
25218    if (shouldRetry(retryConfig, retryCount, true)) {
25219      this.retry(retryConfig);
25220    } else {
25221      var _this$context;
25222      logger.warn(`timeout while loading ${(_this$context = this.context) == null ? void 0 : _this$context.url}`);
25223      const callbacks = this.callbacks;
25224      if (callbacks) {
25225        this.abortInternal();
25226        callbacks.onTimeout(this.stats, this.context, this.loader);
25227      }
25228    }
25229  }
25230  retry(retryConfig) {
25231    const {
25232      context,
25233      stats
25234    } = this;
25235    this.retryDelay = getRetryDelay(retryConfig, stats.retry);
25236    stats.retry++;
25237    logger.warn(`${status ? 'HTTP Status ' + status : 'Timeout'} while loading ${context == null ? void 0 : context.url}, retrying ${stats.retry}/${retryConfig.maxNumRetry} in ${this.retryDelay}ms`);
25238    // abort and reset internal state
25239    this.abortInternal();
25240    this.loader = null;
25241    // schedule retry
25242    self.clearTimeout(this.retryTimeout);
25243    this.retryTimeout = self.setTimeout(this.loadInternal.bind(this), this.retryDelay);
25244  }
25245  loadprogress(event) {
25246    const stats = this.stats;
25247    stats.loaded = event.loaded;
25248    if (event.lengthComputable) {
25249      stats.total = event.total;
25250    }
25251  }
25252  getCacheAge() {
25253    let result = null;
25254    if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) {
25255      const ageHeader = this.loader.getResponseHeader('age');
25256      result = ageHeader ? parseFloat(ageHeader) : null;
25257    }
25258    return result;
25259  }
25260  getResponseHeader(name) {
25261    if (this.loader && new RegExp(`^${name}:\\s*[\\d.]+\\s*$`, 'im').test(this.loader.getAllResponseHeaders())) {
25262      return this.loader.getResponseHeader(name);
25263    }
25264    return null;
25265  }
25266}
25267
25268function fetchSupported() {
25269  if (
25270  // @ts-ignore
25271  self.fetch && self.AbortController && self.ReadableStream && self.Request) {
25272    try {
25273      new self.ReadableStream({}); // eslint-disable-line no-new
25274      return true;
25275    } catch (e) {
25276      /* noop */
25277    }
25278  }
25279  return false;
25280}
25281const BYTERANGE = /(\d+)-(\d+)\/(\d+)/;
25282class FetchLoader {
25283  constructor(config /* HlsConfig */) {
25284    this.fetchSetup = void 0;
25285    this.requestTimeout = void 0;
25286    this.request = null;
25287    this.response = null;
25288    this.controller = void 0;
25289    this.context = null;
25290    this.config = null;
25291    this.callbacks = null;
25292    this.stats = void 0;
25293    this.loader = null;
25294    this.fetchSetup = config.fetchSetup || getRequest;
25295    this.controller = new self.AbortController();
25296    this.stats = new LoadStats();
25297  }
25298  destroy() {
25299    this.loader = this.callbacks = this.context = this.config = this.request = null;
25300    this.abortInternal();
25301    this.response = null;
25302    // @ts-ignore
25303    this.fetchSetup = this.controller = this.stats = null;
25304  }
25305  abortInternal() {
25306    if (this.controller && !this.stats.loading.end) {
25307      this.stats.aborted = true;
25308      this.controller.abort();
25309    }
25310  }
25311  abort() {
25312    var _this$callbacks;
25313    this.abortInternal();
25314    if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) {
25315      this.callbacks.onAbort(this.stats, this.context, this.response);
25316    }
25317  }
25318  load(context, config, callbacks) {
25319    const stats = this.stats;
25320    if (stats.loading.start) {
25321      throw new Error('Loader can only be used once.');
25322    }
25323    stats.loading.start = self.performance.now();
25324    const initParams = getRequestParameters(context, this.controller.signal);
25325    const onProgress = callbacks.onProgress;
25326    const isArrayBuffer = context.responseType === 'arraybuffer';
25327    const LENGTH = isArrayBuffer ? 'byteLength' : 'length';
25328    const {
25329      maxTimeToFirstByteMs,
25330      maxLoadTimeMs
25331    } = config.loadPolicy;
25332    this.context = context;
25333    this.config = config;
25334    this.callbacks = callbacks;
25335    this.request = this.fetchSetup(context, initParams);
25336    self.clearTimeout(this.requestTimeout);
25337    config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
25338    this.requestTimeout = self.setTimeout(() => {
25339      this.abortInternal();
25340      callbacks.onTimeout(stats, context, this.response);
25341    }, config.timeout);
25342    self.fetch(this.request).then(response => {
25343      this.response = this.loader = response;
25344      const first = Math.max(self.performance.now(), stats.loading.start);
25345      self.clearTimeout(this.requestTimeout);
25346      config.timeout = maxLoadTimeMs;
25347      this.requestTimeout = self.setTimeout(() => {
25348        this.abortInternal();
25349        callbacks.onTimeout(stats, context, this.response);
25350      }, maxLoadTimeMs - (first - stats.loading.start));
25351      if (!response.ok) {
25352        const {
25353          status,
25354          statusText
25355        } = response;
25356        throw new FetchError(statusText || 'fetch, bad network response', status, response);
25357      }
25358      stats.loading.first = first;
25359      stats.total = getContentLength(response.headers) || stats.total;
25360      if (onProgress && isFiniteNumber(config.highWaterMark)) {
25361        return this.loadProgressively(response, stats, context, config.highWaterMark, onProgress);
25362      }
25363      if (isArrayBuffer) {
25364        return response.arrayBuffer();
25365      }
25366      if (context.responseType === 'json') {
25367        return response.json();
25368      }
25369      return response.text();
25370    }).then(responseData => {
25371      const response = this.response;
25372      if (!response) {
25373        throw new Error('loader destroyed');
25374      }
25375      self.clearTimeout(this.requestTimeout);
25376      stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
25377      const total = responseData[LENGTH];
25378      if (total) {
25379        stats.loaded = stats.total = total;
25380      }
25381      const loaderResponse = {
25382        url: response.url,
25383        data: responseData,
25384        code: response.status
25385      };
25386      if (onProgress && !isFiniteNumber(config.highWaterMark)) {
25387        onProgress(stats, context, responseData, response);
25388      }
25389      callbacks.onSuccess(loaderResponse, stats, context, response);
25390    }).catch(error => {
25391      self.clearTimeout(this.requestTimeout);
25392      if (stats.aborted) {
25393        return;
25394      }
25395      // CORS errors result in an undefined code. Set it to 0 here to align with XHR's behavior
25396      // when destroying, 'error' itself can be undefined
25397      const code = !error ? 0 : error.code || 0;
25398      const text = !error ? null : error.message;
25399      callbacks.onError({
25400        code,
25401        text
25402      }, context, error ? error.details : null, stats);
25403    });
25404  }
25405  getCacheAge() {
25406    let result = null;
25407    if (this.response) {
25408      const ageHeader = this.response.headers.get('age');
25409      result = ageHeader ? parseFloat(ageHeader) : null;
25410    }
25411    return result;
25412  }
25413  getResponseHeader(name) {
25414    return this.response ? this.response.headers.get(name) : null;
25415  }
25416  loadProgressively(response, stats, context, highWaterMark = 0, onProgress) {
25417    const chunkCache = new ChunkCache();
25418    const reader = response.body.getReader();
25419    const pump = () => {
25420      return reader.read().then(data => {
25421        if (data.done) {
25422          if (chunkCache.dataLength) {
25423            onProgress(stats, context, chunkCache.flush(), response);
25424          }
25425          return Promise.resolve(new ArrayBuffer(0));
25426        }
25427        const chunk = data.value;
25428        const len = chunk.length;
25429        stats.loaded += len;
25430        if (len < highWaterMark || chunkCache.dataLength) {
25431          // The current chunk is too small to to be emitted or the cache already has data
25432          // Push it to the cache
25433          chunkCache.push(chunk);
25434          if (chunkCache.dataLength >= highWaterMark) {
25435            // flush in order to join the typed arrays
25436            onProgress(stats, context, chunkCache.flush(), response);
25437          }
25438        } else {
25439          // If there's nothing cached already, and the chache is large enough
25440          // just emit the progress event
25441          onProgress(stats, context, chunk, response);
25442        }
25443        return pump();
25444      }).catch(() => {
25445        /* aborted */
25446        return Promise.reject();
25447      });
25448    };
25449    return pump();
25450  }
25451}
25452function getRequestParameters(context, signal) {
25453  const initParams = {
25454    method: 'GET',
25455    mode: 'cors',
25456    credentials: 'same-origin',
25457    signal,
25458    headers: new self.Headers(_extends({}, context.headers))
25459  };
25460  if (context.rangeEnd) {
25461    initParams.headers.set('Range', 'bytes=' + context.rangeStart + '-' + String(context.rangeEnd - 1));
25462  }
25463  return initParams;
25464}
25465function getByteRangeLength(byteRangeHeader) {
25466  const result = BYTERANGE.exec(byteRangeHeader);
25467  if (result) {
25468    return parseInt(result[2]) - parseInt(result[1]) + 1;
25469  }
25470}
25471function getContentLength(headers) {
25472  const contentRange = headers.get('Content-Range');
25473  if (contentRange) {
25474    const byteRangeLength = getByteRangeLength(contentRange);
25475    if (isFiniteNumber(byteRangeLength)) {
25476      return byteRangeLength;
25477    }
25478  }
25479  const contentLength = headers.get('Content-Length');
25480  if (contentLength) {
25481    return parseInt(contentLength);
25482  }
25483}
vendor: 4,876 bytes, lines 25484-25642
25484function getRequest(context, initParams) {
25485  return new self.Request(context.url, initParams);
25486}
25487class FetchError extends Error {
25488  constructor(message, code, details) {
25489    super(message);
25490    this.code = void 0;
25491    this.details = void 0;
25492    this.code = code;
25493    this.details = details;
25494  }
25495}
25496
25497const WHITESPACE_CHAR = /\s/;
25498const Cues = {
25499  newCue(track, startTime, endTime, captionScreen) {
25500    const result = [];
25501    let row;
25502    // the type data states this is VTTCue, but it can potentially be a TextTrackCue on old browsers
25503    let cue;
25504    let indenting;
25505    let indent;
25506    let text;
25507    const Cue = self.VTTCue || self.TextTrackCue;
25508    for (let r = 0; r < captionScreen.rows.length; r++) {
25509      row = captionScreen.rows[r];
25510      indenting = true;
25511      indent = 0;
25512      text = '';
25513      if (!row.isEmpty()) {
25514        var _track$cues;
25515        for (let c = 0; c < row.chars.length; c++) {
25516          if (WHITESPACE_CHAR.test(row.chars[c].uchar) && indenting) {
25517            indent++;
25518          } else {
25519            text += row.chars[c].uchar;
25520            indenting = false;
25521          }
25522        }
25523        // To be used for cleaning-up orphaned roll-up captions
25524        row.cueStartTime = startTime;
25525
25526        // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE
25527        if (startTime === endTime) {
25528          endTime += 0.0001;
25529        }
25530        if (indent >= 16) {
25531          indent--;
25532        } else {
25533          indent++;
25534        }
25535        const cueText = fixLineBreaks(text.trim());
25536        const id = generateCueId(startTime, endTime, cueText);
25537
25538        // If this cue already exists in the track do not push it
25539        if (!(track != null && (_track$cues = track.cues) != null && _track$cues.getCueById(id))) {
25540          cue = new Cue(startTime, endTime, cueText);
25541          cue.id = id;
25542          cue.line = r + 1;
25543          cue.align = 'left';
25544          // Clamp the position between 10 and 80 percent (CEA-608 PAC indent code)
25545          // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
25546          // Firefox throws an exception and captions break with out of bounds 0-100 values
25547          cue.position = 10 + Math.min(80, Math.floor(indent * 8 / 32) * 10);
25548          result.push(cue);
25549        }
25550      }
25551    }
25552    if (track && result.length) {
25553      // Sort bottom cues in reverse order so that they render in line order when overlapping in Chrome
25554      result.sort((cueA, cueB) => {
25555        if (cueA.line === 'auto' || cueB.line === 'auto') {
25556          return 0;
25557        }
25558        if (cueA.line > 8 && cueB.line > 8) {
25559          return cueB.line - cueA.line;
25560        }
25561        return cueA.line - cueB.line;
25562      });
25563      result.forEach(cue => addCueToTrack(track, cue));
25564    }
25565    return result;
25566  }
25567};
25568
25569/**
25570 * @deprecated use fragLoadPolicy.default
25571 */
25572
25573/**
25574 * @deprecated use manifestLoadPolicy.default and playlistLoadPolicy.default
25575 */
25576
25577const defaultLoadPolicy = {
25578  maxTimeToFirstByteMs: 8000,
25579  maxLoadTimeMs: 20000,
25580  timeoutRetry: null,
25581  errorRetry: null
25582};
25583
25584/**
25585 * @ignore
25586 * If possible, keep hlsDefaultConfig shallow
25587 * It is cloned whenever a new Hls instance is created, by keeping the config
25588 * shallow the properties are cloned, and we don't end up manipulating the default
25589 */
25590const hlsDefaultConfig = _objectSpread2(_objectSpread2({
25591  autoStartLoad: true,
25592  // used by stream-controller
25593  startPosition: -1,
25594  // used by stream-controller
25595  defaultAudioCodec: undefined,
25596  // used by stream-controller
25597  debug: false,
25598  // used by logger
25599  capLevelOnFPSDrop: false,
25600  // used by fps-controller
25601  capLevelToPlayerSize: false,
25602  // used by cap-level-controller
25603  ignoreDevicePixelRatio: false,
25604  // used by cap-level-controller
25605  preferManagedMediaSource: true,
25606  initialLiveManifestSize: 1,
25607  // used by stream-controller
25608  maxBufferLength: 30,
25609  // used by stream-controller
25610  backBufferLength: Infinity,
25611  // used by buffer-controller
25612  frontBufferFlushThreshold: Infinity,
25613  maxBufferSize: 60 * 1000 * 1000,
25614  // used by stream-controller
25615  maxBufferHole: 0.1,
25616  // used by stream-controller
25617  highBufferWatchdogPeriod: 2,
25618  // used by stream-controller
25619  nudgeOffset: 0.1,
25620  // used by stream-controller
25621  nudgeMaxRetry: 3,
25622  // used by stream-controller
25623  maxFragLookUpTolerance: 0.25,
25624  // used by stream-controller
25625  liveSyncDurationCount: 3,
25626  // used by latency-controller
25627  liveMaxLatencyDurationCount: Infinity,
25628  // used by latency-controller
25629  liveSyncDuration: undefined,
25630  // used by latency-controller
25631  liveMaxLatencyDuration: undefined,
25632  // used by latency-controller
25633  maxLiveSyncPlaybackRate: 1,
25634  // used by latency-controller
25635  liveDurationInfinity: false,
25636  // used by buffer-controller
25637  /**
25638   * @deprecated use backBufferLength
25639   */
25640  liveBackBufferLength: null,
25641  // used by buffer-controller
25642  maxMaxBufferLength: 600,
25643  // used by stream-controller
25644  enableWorker: true,
25645  // used by transmuxer
25646  workerPath: null,
25647  // used by transmuxer
25648  enableSoftwareAES: true,
25649  // used by decrypter
25650  startLevel: undefined,
25651  // used by level-controller
25652  startFragPrefetch: false,
25653  // used by stream-controller
25654  fpsDroppedMonitoringPeriod: 5000,
25655  // used by fps-controller
25656  fpsDroppedMonitoringThreshold: 0.2,
25657  // used by fps-controller
25658  appendErrorMaxRetry: 3,
25659  // used by buffer-controller
25660  loader: XhrLoader,
25661  // loader: FetchLoader,
25662  fLoader: undefined,
25663  // used by fragment-loader
25664  pLoader: undefined,
25665  // used by playlist-loader
25666  xhrSetup: undefined,
25667  // used by xhr-loader
25668  licenseXhrSetup: undefined,
25669  // used by eme-controller
25670  licenseResponseCallback: undefined,
25671  // used by eme-controller
25672  abrController: AbrController,
25673  bufferController: BufferController,
25674  capLevelController: CapLevelController,
25675  errorController: ErrorController,
25676  fpsController: FPSController,
25677  stretchShortVideoTrack: false,
25678  // used by mp4-remuxer
25679  maxAudioFramesDrift: 1,
25680  // used by mp4-remuxer
25681  forceKeyFrameOnDiscontinuity: true,
25682  // used by ts-demuxer
25683  abrEwmaFastLive: 3,
25684  // used by abr-controller
25685  abrEwmaSlowLive: 9,
25686  // used by abr-controller
25687  abrEwmaFastVoD: 3,
25688  // used by abr-controller
25689  abrEwmaSlowVoD: 9,
25690  // used by abr-controller
25691  abrEwmaDefaultEstimate: 5e5,
25692  // 500 kbps  // used by abr-controller
25693  abrEwmaDefaultEstimateMax: 5e6,
25694  // 5 mbps
25695  abrBandWidthFactor: 0.95,
25696  // used by abr-controller
25697  abrBandWidthUpFactor: 0.7,
25698  // used by abr-controller
25699  abrMaxWithRealBitrate: false,
25700  // used by abr-controller
25701  maxStarvationDelay: 4,
25702  // used by abr-controller
25703  maxLoadingDelay: 4,
25704  // used by abr-controller
25705  minAutoBitrate: 0,
25706  // used by hls
25707  emeEnabled: false,
25708  // used by eme-controller
25709  widevineLicenseUrl: undefined,
25710  // used by eme-controller
25711  drmSystems: {},
25712  // used by eme-controller
25713  drmSystemOptions: {},
25714  // used by eme-controller
25715  requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess ,
25716  // used by eme-controller
25717  testBandwidth: true,
25718  progressive: false,
25719  lowLatencyMode: true,
25720  cmcd: undefined,
25721  enableDateRangeMetadataCues: true,
25722  enableEmsgMetadataCues: true,
25723  enableID3MetadataCues: true,
25724  useMediaCapabilities: true,
25725  certLoadPolicy: {
25726    default: defaultLoadPolicy
25727  },
25728  keyLoadPolicy: {
25729    default: {
25730      maxTimeToFirstByteMs: 8000,
25731      maxLoadTimeMs: 20000,
25732      timeoutRetry: {
25733        maxNumRetry: 1,
25734        retryDelayMs: 1000,
25735        maxRetryDelayMs: 20000,
25736        backoff: 'linear'
25737      },
25738      errorRetry: {
25739        maxNumRetry: 8,
25740        retryDelayMs: 1000,
25741        maxRetryDelayMs: 20000,
25742        backoff: 'linear'
25743      }
25744    }
25745  },
25746  manifestLoadPolicy: {
25747    default: {
25748      maxTimeToFirstByteMs: Infinity,
25749      maxLoadTimeMs: 20000,
25750      timeoutRetry: {
25751        maxNumRetry: 2,
25752        retryDelayMs: 0,
25753        maxRetryDelayMs: 0
25754      },
25755      errorRetry: {
25756        maxNumRetry: 1,
25757        retryDelayMs: 1000,
25758        maxRetryDelayMs: 8000
25759      }
25760    }
25761  },
25762  playlistLoadPolicy: {
25763    default: {
25764      maxTimeToFirstByteMs: 10000,
25765      maxLoadTimeMs: 20000,
25766      timeoutRetry: {
25767        maxNumRetry: 2,
25768        retryDelayMs: 0,
25769        maxRetryDelayMs: 0
25770      },
25771      errorRetry: {
25772        maxNumRetry: 2,
25773        retryDelayMs: 1000,
25774        maxRetryDelayMs: 8000
25775      }
25776    }
25777  },
25778  fragLoadPolicy: {
25779    default: {
25780      maxTimeToFirstByteMs: 10000,
25781      maxLoadTimeMs: 120000,
25782      timeoutRetry: {
25783        maxNumRetry: 4,
25784        retryDelayMs: 0,
25785        maxRetryDelayMs: 0
25786      },
25787      errorRetry: {
25788        maxNumRetry: 6,
25789        retryDelayMs: 1000,
25790        maxRetryDelayMs: 8000
25791      }
25792    }
25793  },
25794  steeringManifestLoadPolicy: {
25795    default: {
25796      maxTimeToFirstByteMs: 10000,
25797      maxLoadTimeMs: 20000,
25798      timeoutRetry: {
25799        maxNumRetry: 2,
25800        retryDelayMs: 0,
25801        maxRetryDelayMs: 0
25802      },
25803      errorRetry: {
25804        maxNumRetry: 1,
25805        retryDelayMs: 1000,
25806        maxRetryDelayMs: 8000
25807      }
25808    } 
25809  },
vendor: 4,542 bytes, lines 25810-25918
25810  // These default settings are deprecated in favor of the above policies
25811  // and are maintained for backwards compatibility
25812  manifestLoadingTimeOut: 10000,
25813  manifestLoadingMaxRetry: 1,
25814  manifestLoadingRetryDelay: 1000,
25815  manifestLoadingMaxRetryTimeout: 64000,
25816  levelLoadingTimeOut: 10000,
25817  levelLoadingMaxRetry: 4,
25818  levelLoadingRetryDelay: 1000,
25819  levelLoadingMaxRetryTimeout: 64000,
25820  fragLoadingTimeOut: 20000,
25821  fragLoadingMaxRetry: 6,
25822  fragLoadingRetryDelay: 1000,
25823  fragLoadingMaxRetryTimeout: 64000
25824}, timelineConfig()), {}, {
25825  subtitleStreamController: SubtitleStreamController ,
25826  subtitleTrackController: SubtitleTrackController ,
25827  timelineController: TimelineController ,
25828  audioStreamController: AudioStreamController ,
25829  audioTrackController: AudioTrackController ,
25830  emeController: EMEController ,
25831  cmcdController: CMCDController ,
25832  contentSteeringController: ContentSteeringController 
25833});
25834function timelineConfig() {
25835  return {
25836    cueHandler: Cues,
25837    // used by timeline-controller
25838    enableWebVTT: true,
25839    // used by timeline-controller
25840    enableIMSC1: true,
25841    // used by timeline-controller
25842    enableCEA708Captions: true,
25843    // used by timeline-controller
25844    captionsTextTrack1Label: 'English',
25845    // used by timeline-controller
25846    captionsTextTrack1LanguageCode: 'en',
25847    // used by timeline-controller
25848    captionsTextTrack2Label: 'Spanish',
25849    // used by timeline-controller
25850    captionsTextTrack2LanguageCode: 'es',
25851    // used by timeline-controller
25852    captionsTextTrack3Label: 'Unknown CC',
25853    // used by timeline-controller
25854    captionsTextTrack3LanguageCode: '',
25855    // used by timeline-controller
25856    captionsTextTrack4Label: 'Unknown CC',
25857    // used by timeline-controller
25858    captionsTextTrack4LanguageCode: '',
25859    // used by timeline-controller
25860    renderTextTracksNatively: true
25861  };
25862}
25863
25864/**
25865 * @ignore
25866 */
25867function mergeConfig(defaultConfig, userConfig) {
25868  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
25869    throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
25870  }
25871  if (userConfig.liveMaxLatencyDurationCount !== undefined && (userConfig.liveSyncDurationCount === undefined || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) {
25872    throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"');
25873  }
25874  if (userConfig.liveMaxLatencyDuration !== undefined && (userConfig.liveSyncDuration === undefined || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) {
25875    throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"');
25876  }
25877  const defaultsCopy = deepCpy(defaultConfig);
25878
25879  // Backwards compatibility with deprecated config values
25880  const deprecatedSettingTypes = ['manifest', 'level', 'frag'];
25881  const deprecatedSettings = ['TimeOut', 'MaxRetry', 'RetryDelay', 'MaxRetryTimeout'];
25882  deprecatedSettingTypes.forEach(type => {
25883    const policyName = `${type === 'level' ? 'playlist' : type}LoadPolicy`;
25884    const policyNotSet = userConfig[policyName] === undefined;
25885    const report = [];
25886    deprecatedSettings.forEach(setting => {
25887      const deprecatedSetting = `${type}Loading${setting}`;
25888      const value = userConfig[deprecatedSetting];
25889      if (value !== undefined && policyNotSet) {
25890        report.push(deprecatedSetting);
25891        const settings = defaultsCopy[policyName].default;
25892        userConfig[policyName] = {
25893          default: settings
25894        };
25895        switch (setting) {
25896          case 'TimeOut':
25897            settings.maxLoadTimeMs = value;
25898            settings.maxTimeToFirstByteMs = value;
25899            break;
25900          case 'MaxRetry':
25901            settings.errorRetry.maxNumRetry = value;
25902            settings.timeoutRetry.maxNumRetry = value;
25903            break;
25904          case 'RetryDelay':
25905            settings.errorRetry.retryDelayMs = value;
25906            settings.timeoutRetry.retryDelayMs = value;
25907            break;
25908          case 'MaxRetryTimeout':
25909            settings.errorRetry.maxRetryDelayMs = value;
25910            settings.timeoutRetry.maxRetryDelayMs = value;
25911            break;
25912        }
25913      }
25914    });
25915    if (report.length) {
25916      logger.warn(`hls.js config: "${report.join('", "')}" setting(s) are deprecated, use "${policyName}": ${JSON.stringify(userConfig[policyName])}`);
25917    }
25918  });
25919  return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig);
25920}
25921function deepCpy(obj) {
25922  if (obj && typeof obj === 'object') {
25923    if (Array.isArray(obj)) {
25924      return obj.map(deepCpy);
25925    }
25926    return Object.keys(obj).reduce((result, key) => {
25927      result[key] = deepCpy(obj[key]);
25928      return result;
25929    }, {});
25930  }
25931  return obj;
25932}
25933
25934/**
25935 * @ignore
25936 */
25937function enableStreamingMode(config) {
25938  const currentLoader = config.loader;
25939  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
25940    // If a developer has configured their own loader, respect that choice
25941    logger.log('[config]: Custom loader detected, cannot enable progressive streaming');
25942    config.progressive = false;
25943  } else {
25944    const canStreamProgressively = fetchSupported();
25945    if (canStreamProgressively) {
25946      config.loader = FetchLoader;
25947      config.progressive = true;
25948      config.enableSoftwareAES = true;
25949      logger.log('[config]: Progressive streaming enabled, using FetchLoader');
25950    }
25951  }
25952}
25953
25954let chromeOrFirefox;
25955class LevelController extends BasePlaylistController {
25956  constructor(hls, contentSteeringController) {
25957    super(hls, '[level-controller]');
25958    this._levels = [];
25959    this._firstLevel = -1;
25960    this._maxAutoLevel = -1;
25961    this._startLevel = void 0;
25962    this.currentLevel = null;
25963    this.currentLevelIndex = -1;
25964    this.manualLevelIndex = -1;
25965    this.steering = void 0;
25966    this.onParsedComplete = void 0;
25967    this.steering = contentSteeringController;
25968    this._registerListeners();
25969  }
25970  _registerListeners() {
25971    const {
25972      hls
25973    } = this;
25974    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
25975    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
25976    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
25977    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
25978    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
25979    hls.on(Events.ERROR, this.onError, this);
25980  }
25981  _unregisterListeners() {
25982    const {
25983      hls
25984    } = this;
25985    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
25986    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
25987    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
25988    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
25989    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
25990    hls.off(Events.ERROR, this.onError, this);
25991  }
25992  destroy() {
25993    this._unregisterListeners();
25994    this.steering = null;
25995    this.resetLevels();
25996    super.destroy();
25997  }
25998  stopLoad() {
25999    const levels = this._levels;
26000
26001    // clean up live level details to force reload them, and reset load errors
26002    levels.forEach(level => {
26003      level.loadError = 0;
26004      level.fragmentError = 0;
26005    });
26006    super.stopLoad();
26007  }
26008  resetLevels() {
26009    this._startLevel = undefined;
26010    this.manualLevelIndex = -1;
26011    this.currentLevelIndex = -1;
26012    this.currentLevel = null;
26013    this._levels = [];
26014    this._maxAutoLevel = -1;
26015  }
26016  onManifestLoading(event, data) {
26017    this.resetLevels();
26018  }
26019  onManifestLoaded(event, data) {
26020    const preferManagedMediaSource = this.hls.config.preferManagedMediaSource;
26021    const levels = [];
26022    const redundantSet = {};
26023    const generatePathwaySet = {};
26024    let resolutionFound = false;
26025    let videoCodecFound = false;
26026    let audioCodecFound = false;
26027    data.levels.forEach(levelParsed => {
26028      var _audioCodec, _videoCodec;
26029      const attributes = levelParsed.attrs;
26030
26031      // erase audio codec info if browser does not support mp4a.40.34.
26032      // demuxer will autodetect codec and fallback to mpeg/audio
26033      let {
26034        audioCodec,
26035        videoCodec
26036      } = levelParsed;
26037      if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
26038        chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
26039        if (chromeOrFirefox) {
26040          levelParsed.audioCodec = audioCodec = undefined;
26041        }
26042      }
26043      if (audioCodec) {
26044        levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
26045      }
26046      if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
26047        videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
26048      }
26049
26050      // only keep levels with supported audio/video codecs
26051      const {
26052        width,
26053        height,
26054        unknownCodecs
26055      } = levelParsed;
26056      resolutionFound || (resolutionFound = !!(width && height));
26057      videoCodecFound || (videoCodecFound = !!videoCodec);
26058      audioCodecFound || (audioCodecFound = !!audioCodec);
26059      if (unknownCodecs != null && unknownCodecs.length || audioCodec && !areCodecsMediaSourceSupp
26059orted(audioCodec, 'audio', preferManagedMediaSource) || videoCodec && !areCodecsMediaSourceSupported(videoCodec, 'video', preferManagedMediaSource)) {
26060        return;
26061      }
26062      const {
26063        CODECS,
26064        'FRAME-RATE': FRAMERATE,
26065        'HDCP-LEVEL': HDCP,
26066        'PATHWAY-ID': PATHWAY,
26067        RESOLUTION,
26068        'VIDEO-RANGE': VIDEO_RANGE
26069      } = attributes;
26070      const contentSteeringPrefix = `${PATHWAY || '.'}-`;
26071      const levelKey = `${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}-${VIDEO_RANGE}-${HDCP}`;
26072      if (!redundantSet[levelKey]) {
26073        const level = new Level(levelParsed);
26074        redundantSet[levelKey] = level;
26075        generatePathwaySet[levelKey] = 1;
26076        levels.push(level);
26077      } else if (redundantSet[levelKey].uri !== levelParsed.url && !levelParsed.attrs['PATHWAY-ID']) {
26078        // Assign Pathway IDs to Redundant Streams (default Pathways is ".". Redundant Streams "..", "...", and so on.)
26079        // Content Steering controller to handles Pathway fallback on error
26080        const pathwayCount = generatePathwaySet[levelKey] += 1;
26081        levelParsed.attrs['PATHWAY-ID'] = new Array(pathwayCount + 1).join('.');
26082        const level = new Level(levelParsed);
26083        redundantSet[levelKey] = level;
26084        levels.push(level);
26085      } else {
26086        redundantSet[levelKey].addGroupId('audio', attributes.AUDIO);
26087        redundantSet[levelKey].addGroupId('text', attributes.SUBTITLES);
26088      }
26089    });
26090    this.filterAndSortMediaOptions(levels, data, resolutionFound, videoCodecFound, audioCodecFound);
26091  }
26092  filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound) {
26093    let audioTracks = [];
26094    let subtitleTracks = [];
26095    let levels = filteredLevels;
26096
26097    // remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled
26098    if ((resolutionFound || videoCodecFound) && audioCodecFound) {
26099      levels = levels.filter(({
26100        videoCodec,
26101        videoRange,
26102        width,
26103        height
26104      }) => (!!videoCodec || !!(width && height)) && isVideoRange(videoRange));
26105    }
26106    if (levels.length === 0) {
26107      // Dispatch error after MANIFEST_LOADED is done propagating
26108      Promise.resolve().then(() => {
26109        if (this.hls) {
26110          if (data.levels.length) {
26111            this.warn(`One or more CODECS in variant not supported: ${JSON.stringify(data.levels[0].attrs)}`);
26112          }
26113          const error = new Error('no level with compatible codecs found in manifest');
26114          this.hls.trigger(Events.ERROR, {
26115            type: ErrorTypes.MEDIA_ERROR,
26116            details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR,
26117            fatal: true,
26118            url: data.url,
26119            error,
26120            reason: error.message
26121          });
26122        }
26123      });
26124      return;
26125    }
26126    if (data.audioTracks) {
26127      const {
26128        preferManagedMediaSource
26129      } = this.hls.config;
26130      audioTracks = data.audioTracks.filter(track => !track.audioCodec || areCodecsMediaSourceSupported(track.audioCodec, 'audio', preferManagedMediaSource));
26131      // Assign ids after filtering as array indices by group-id
26132      assignTrackIdsByGroup(audioTracks);
26133    }
26134    if (data.subtitles) {
26135      subtitleTracks = data.subtitles;
26136      assignTrackIdsByGroup(subtitleTracks);
26137    }
26138    // start bitrate is the first bitrate of the manifest
26139    const unsortedLevels = levels.slice(0);
26140    // sort levels from lowest to highest
26141    levels.sort((a, b) => {
26142      if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) {
26143        return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '') ? 1 : -1;
26144      }
26145      // sort on height before bitrate for cap-level-controller
26146      if (resolutionFound && a.height !== b.height) {
26147        return a.height - b.height;
26148      }
26149      if (a.frameRate !== b.frameRate) {
26150        return a.frameRate - b.frameRate;
26151      }
26152      if (a.videoRange !== b.videoRange) {
26153        return VideoRangeValues.indexOf(a.videoRange) - VideoRangeValues.indexOf(b.videoRange);
26154      }
26155      if (a.videoCodec !== b.videoCodec) {
26156        const valueA = videoCodecPreferenceValue(a.videoCodec);
26157        const valueB = videoCodecPreferenceValue(b.videoCodec);
26158        if (valueA !== valueB) {
26159          return valueB - valueA;
26160        }
26161      }
vendor: 10,167 bytes, lines 26162-26446
26162      if (a.uri === b.uri && a.codecSet !== b.codecSet) {
26163        const valueA = codecsSetSelectionPreferenceValue(a.codecSet);
26164        const valueB = codecsSetSelectionPreferenceValue(b.codecSet);
26165        if (valueA !== valueB) {
26166          return valueB - valueA;
26167        }
26168      }
26169      if (a.averageBitrate !== b.averageBitrate) {
26170        return a.averageBitrate - b.averageBitrate;
26171      }
26172      return 0;
26173    });
26174    let firstLevelInPlaylist = unsortedLevels[0];
26175    if (this.steering) {
26176      levels = this.steering.filterParsedLevels(levels);
26177      if (levels.length !== unsortedLevels.length) {
26178        for (let i = 0; i < unsortedLevels.length; i++) {
26179          if (unsortedLevels[i].pathwayId === levels[0].pathwayId) {
26180            firstLevelInPlaylist = unsortedLevels[i];
26181            break;
26182          }
26183        }
26184      }
26185    }
26186    this._levels = levels;
26187
26188    // find index of first level in sorted levels
26189    for (let i = 0; i < levels.length; i++) {
26190      if (levels[i] === firstLevelInPlaylist) {
26191        var _this$hls$userConfig;
26192        this._firstLevel = i;
26193        const firstLevelBitrate = firstLevelInPlaylist.bitrate;
26194        const bandwidthEstimate = this.hls.bandwidthEstimate;
26195        this.log(`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`);
26196        // Update default bwe to first variant bitrate as long it has not been configured or set
26197        if (((_this$hls$userConfig = this.hls.userConfig) == null ? void 0 : _this$hls$userConfig.abrEwmaDefaultEstimate) === undefined) {
26198          const startingBwEstimate = Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax);
26199          if (startingBwEstimate > bandwidthEstimate && bandwidthEstimate === hlsDefaultConfig.abrEwmaDefaultEstimate) {
26200            this.hls.bandwidthEstimate = startingBwEstimate;
26201          }
26202        }
26203        break;
26204      }
26205    }
26206
26207    // Audio is only alternate if manifest include a URI along with the audio group tag,
26208    // and this is not an audio-only stream where levels contain audio-only
26209    const audioOnly = audioCodecFound && !videoCodecFound;
26210    const edata = {
26211      levels,
26212      audioTracks,
26213      subtitleTracks,
26214      sessionData: data.sessionData,
26215      sessionKeys: data.sessionKeys,
26216      firstLevel: this._firstLevel,
26217      stats: data.stats,
26218      audio: audioCodecFound,
26219      video: videoCodecFound,
26220      altAudio: !audioOnly && audioTracks.some(t => !!t.url)
26221    };
26222    this.hls.trigger(Events.MANIFEST_PARSED, edata);
26223
26224    // Initiate loading after all controllers have received MANIFEST_PARSED
26225    if (this.hls.config.autoStartLoad || this.hls.forceStartLoad) {
26226      this.hls.startLoad(this.hls.config.startPosition);
26227    }
26228  }
26229  get levels() {
26230    if (this._levels.length === 0) {
26231      return null;
26232    }
26233    return this._levels;
26234  }
26235  get level() {
26236    return this.currentLevelIndex;
26237  }
26238  set level(newLevel) {
26239    const levels = this._levels;
26240    if (levels.length === 0) {
26241      return;
26242    }
26243    // check if level idx is valid
26244    if (newLevel < 0 || newLevel >= levels.length) {
26245      // invalid level id given, trigger error
26246      const error = new Error('invalid level idx');
26247      const fatal = newLevel < 0;
26248      this.hls.trigger(Events.ERROR, {
26249        type: ErrorTypes.OTHER_ERROR,
26250        details: ErrorDetails.LEVEL_SWITCH_ERROR,
26251        level: newLevel,
26252        fatal,
26253        error,
26254        reason: error.message
26255      });
26256      if (fatal) {
26257        return;
26258      }
26259      newLevel = Math.min(newLevel, levels.length - 1);
26260    }
26261    const lastLevelIndex = this.currentLevelIndex;
26262    const lastLevel = this.currentLevel;
26263    const lastPathwayId = lastLevel ? lastLevel.attrs['PATHWAY-ID'] : undefined;
26264    const level = levels[newLevel];
26265    const pathwayId = level.attrs['PATHWAY-ID'];
26266    this.currentLevelIndex = newLevel;
26267    this.currentLevel = level;
26268    if (lastLevelIndex === newLevel && level.details && lastLevel && lastPathwayId === pathwayId) {
26269      return;
26270    }
26271    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 : ''}`);
26272    const levelSwitchingData = {
26273      level: newLevel,
26274      attrs: level.attrs,
26275      details: level.details,
26276      bitrate: level.bitrate,
26277      averageBitrate: level.averageBitrate,
26278      maxBitrate: level.maxBitrate,
26279      realBitrate: level.realBitrate,
26280      width: level.width,
26281      height: level.height,
26282      codecSet: level.codecSet,
26283      audioCodec: level.audioCodec,
26284      videoCodec: level.videoCodec,
26285      audioGroups: level.audioGroups,
26286      subtitleGroups: level.subtitleGroups,
26287      loaded: level.loaded,
26288      loadError: level.loadError,
26289      fragmentError: level.fragmentError,
26290      name: level.name,
26291      id: level.id,
26292      uri: level.uri,
26293      url: level.url,
26294      urlId: 0,
26295      audioGroupIds: level.audioGroupIds,
26296      textGroupIds: level.textGroupIds
26297    };
26298    this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData);
26299    // check if we need to load playlist for this level
26300    const levelDetails = level.details;
26301    if (!levelDetails || levelDetails.live) {
26302      // level not retrieved yet, or live playlist we need to (re)load it
26303      const hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details, levelDetails);
26304      this.loadPlaylist(hlsUrlParameters);
26305    }
26306  }
26307  get manualLevel() {
26308    return this.manualLevelIndex;
26309  }
26310  set manualLevel(newLevel) {
26311    this.manualLevelIndex = newLevel;
26312    if (this._startLevel === undefined) {
26313      this._startLevel = newLevel;
26314    }
26315    if (newLevel !== -1) {
26316      this.level = newLevel;
26317    }
26318  }
26319  get firstLevel() {
26320    return this._firstLevel;
26321  }
26322  set firstLevel(newLevel) {
26323    this._firstLevel = newLevel;
26324  }
26325  get startLevel() {
26326    // Setting hls.startLevel (this._startLevel) overrides config.startLevel
26327    if (this._startLevel === undefined) {
26328      const configStartLevel = this.hls.config.startLevel;
26329      if (configStartLevel !== undefined) {
26330        return configStartLevel;
26331      }
26332      return this.hls.firstAutoLevel;
26333    }
26334    return this._startLevel;
26335  }
26336  set startLevel(newLevel) {
26337    this._startLevel = newLevel;
26338  }
26339  onError(event, data) {
26340    if (data.fatal || !data.context) {
26341      return;
26342    }
26343    if (data.context.type === PlaylistContextType.LEVEL && data.context.level === this.level) {
26344      this.checkRetry(data);
26345    }
26346  }
26347
26348  // reset errors on the successful load of a fragment
26349  onFragBuffered(event, {
26350    frag
26351  }) {
26352    if (frag !== undefined && frag.type === PlaylistLevelType.MAIN) {
26353      const el = frag.elementaryStreams;
26354      if (!Object.keys(el).some(type => !!el[type])) {
26355        return;
26356      }
26357      const level = this._levels[frag.level];
26358      if (level != null && level.loadError) {
26359        this.log(`Resetting level error count of ${level.loadError} on frag buffered`);
26360        level.loadError = 0;
26361      }
26362    }
26363  }
26364  onLevelLoaded(event, data) {
26365    var _data$deliveryDirecti2;
26366    const {
26367      level,
26368      details
26369    } = data;
26370    const curLevel = this._levels[level];
26371    if (!curLevel) {
26372      var _data$deliveryDirecti;
26373      this.warn(`Invalid level index ${level}`);
26374      if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) {
26375        details.deltaUpdateFailed = true;
26376      }
26377      return;
26378    }
26379
26380    // only process level loaded events matching with expected level
26381    if (level === this.currentLevelIndex) {
26382      // reset level load error counter on successful level loaded only if there is no issues with fragments
26383      if (curLevel.fragmentError === 0) {
26384        curLevel.loadError = 0;
26385      }
26386      this.playlistLoaded(level, data, curLevel.details);
26387    } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
26388      // received a delta playlist update that cannot be merged
26389      details.deltaUpdateFailed = true;
26390    }
26391  }
26392  loadPlaylist(hlsUrlParameters) {
26393    super.loadPlaylist();
26394    const currentLevelIndex = this.currentLevelIndex;
26395    const currentLevel = this.currentLevel;
26396    if (currentLevel && this.shouldLoadPlaylist(currentLevel)) {
26397      let url = currentLevel.uri;
26398      if (hlsUrlParameters) {
26399        try {
26400          url = hlsUrlParameters.addDirectives(url);
26401        } catch (error) {
26402          this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);
26403        }
26404      }
26405      const pathwayId = currentLevel.attrs['PATHWAY-ID'];
26406      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}`);
26407
26408      // console.log('Current audio track group ID:', this.hls.audioTracks[this.hls.audioTrack].groupId);
26409      // console.log('New video quality level audio group id:', levelObject.attrs.AUDIO, level);
26410      this.clearTimer();
26411      this.hls.trigger(Events.LEVEL_LOADING, {
26412        url,
26413        level: currentLevelIndex,
26414        pathwayId: currentLevel.attrs['PATHWAY-ID'],
26415        id: 0,
26416        // Deprecated Level urlId
26417        deliveryDirectives: hlsUrlParameters || null
26418      });
26419    }
26420  }
26421  get nextLoadLevel() {
26422    if (this.manualLevelIndex !== -1) {
26423      return this.manualLevelIndex;
26424    } else {
26425      return this.hls.nextAutoLevel;
26426    }
26427  }
26428  set nextLoadLevel(nextLevel) {
26429    this.level = nextLevel;
26430    if (this.manualLevelIndex === -1) {
26431      this.hls.nextAutoLevel = nextLevel;
26432    }
26433  }
26434  removeLevel(levelIndex) {
26435    var _this$currentLevel;
26436    const levels = this._levels.filter((level, index) => {
26437      if (index !== levelIndex) {
26438        return true;
26439      }
26440      if (this.steering) {
26441        this.steering.removeLevel(level);
26442      }
26443      if (level === this.currentLevel) {
26444        this.currentLevel = null;
26445        this.currentLevelIndex = -1;
26446        if (level.details) {
26447          level.details.fragments.forEach(f => f.level = -1);
26448        }
26449      }
26450      return false;
26451    });
26452    reassignFragmentLevelIndexes(levels);
26453    this._levels = levels;
26454    if (this.currentLevelIndex > -1 && (_this$currentLevel = this.currentLevel) != null && _this$currentLevel.details) {
26455      this.currentLevelIndex = this.currentLevel.details.fragments[0].level;
26456    }
26457    this.hls.trigger(Events.LEVELS_UPDATED, {
26458      levels
26459    });
26460  }
26461  onLevelsUpdated(event, {
26462    levels
26463  }) {
26464    this._levels = levels;
26465  }
26466  checkMaxAutoUpdated() {
26467    const {
26468      autoLevelCapping,
26469      maxAutoLevel,
26470      maxHdcpLevel
26471    } = this.hls;
26472    if (this._maxAutoLevel !== maxAutoLevel) {
26473      this._maxAutoLevel = maxAutoLevel;
26474      this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, {
26475        autoLevelCapping,
26476        levels: this.levels,
26477        maxAutoLevel,
26478        minAutoLevel: this.hls.minAutoLevel,
26479        maxHdcpLevel
26480      });
26481    }
26482  }
26483}
26484function assignTrackIdsByGroup(tracks) {
26485  const groups = {};
26486  tracks.forEach(track => {
26487    const groupId = track.groupId || '';
26488    track.id = groups[groupId] = groups[groupId] || 0;
26489    groups[groupId]++;
26490  });
26491}
26492
26493class KeyLoader {
26494  constructor(config) {
26495    this.config = void 0;
26496    this.keyUriToKeyInfo = {};
26497    this.emeController = null;
26498    this.config = config;
26499  }
26500  abort(type) {
26501    for (const uri in this.keyUriToKeyInfo) {
26502      const loader = this.keyUriToKeyInfo[uri].loader;
26503      if (loader) {
26504        var _loader$context;
26505        if (type && type !== ((_loader$context = loader.context) == null ? void 0 : _loader$context.frag.type)) {
26506          return;
26507        }
26508        loader.abort();
26509      }
26510    }
26511  }
26512  detach() {
26513    for (const uri in this.keyUriToKeyInfo) {
26514      const keyInfo = this.keyUriToKeyInfo[uri];
26515      // Remove cached EME keys on detach
26516      if (keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption) {
26517        delete this.keyUriToKeyInfo[uri];
26518      }
26519    }
26520  }
26521  destroy() {
26522    this.detach();
26523    for (const uri in this.keyUriToKeyInfo) {
26524      const loader = this.keyUriToKeyInfo[uri].loader;
26525      if (loader) {
26526        loader.destroy();
26527      }
26528    }
26529    this.keyUriToKeyInfo = {};
26530  }
26531  createKeyLoadError(frag, details = ErrorDetails.KEY_LOAD_ERROR, error, networkDetails, response) {
26532    return new LoadError({
26533      type: ErrorTypes.NETWORK_ERROR,
26534      details,
26535      fatal: false,
26536      frag,
26537      response,
26538      error,
26539      networkDetails
26540    });
26541  }
26542  loadClear(loadingFrag, encryptedFragments) {
26543    if (this.emeController && this.config.emeEnabled) {
26544      // access key-system with nearest key on start (loaidng frag is unencrypted)
26545      const {
26546        sn,
26547        cc
26548      } = loadingFrag;
26549      for (let i = 0; i < encryptedFragments.length; i++) {
26550        const frag = encryptedFragments[i];
26551        if (cc <= frag.cc && (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn)) {
26552          this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
26553            frag.setKeyFormat(keySystemFormat);
26554          });
26555          break;
26556        }
26557      }
26558    }
26559  }
26560  load(frag) {
26561    if (!frag.decryptdata && frag.encrypted && this.emeController) {
26562      // Multiple keys, but none selected, resolve in eme-controller
26563      return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
26564        return this.loadInternal(frag, keySystemFormat);
26565      });
26566    }
26567    return this.loadInternal(frag);
26568  }
26569  loadInternal(frag, keySystemFormat) {
26570    var _keyInfo, _keyInfo2;
26571    if (keySystemFormat) {
26572      frag.setKeyFormat(keySystemFormat);
26573    }
26574    const decryptdata = frag.decryptdata;
26575    if (!decryptdata) {
26576      const error = new Error(keySystemFormat ? `Expected frag.decryptdata to be defined after setting format ${keySystemFormat}` : 'Missing decryption data on fragment in onKeyLoading');
26577      return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error));
26578    }
26579    const uri = decryptdata.uri;
26580    if (!uri) {
26581      return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Invalid key URI: "${uri}"`)));
26582    }
26583    let keyInfo = this.keyUriToKeyInfo[uri];
26584    if ((_keyInfo = keyInfo) != null && _keyInfo.decryptdata.key) {
26585      decryptdata.key = keyInfo.decryptdata.key;
26586      return Promise.resolve({
26587        frag,
26588        keyInfo
26589      });
26590    }
26591    // Return key load promise as long as it does not have a mediakey session with an unusable key status
26592    if ((_keyInfo2 = keyInfo) != null && _keyInfo2.keyLoadPromise) {
26593      var _keyInfo$mediaKeySess;
26594      switch ((_keyInfo$mediaKeySess = keyInfo.mediaKeySessionContext) == null ? void 0 : _keyInfo$mediaKeySess.keyStatus) {
26595        case undefined:
26596        case 'status-pending':
26597        case 'usable':
26598        case 'usable-in-future':
26599          return keyInfo.keyLoadPromise.then(keyLoadedData => {
26600            // Return the correct fragment with updated decryptdata key and loaded keyInfo
26601            decryptdata.key = keyLoadedData.keyInfo.decryptdata.key;
26602            return {
26603              frag,
26604              keyInfo
26605            };
26606          });
26607      }
26608      // If we have a key session and status and it is not pending or usable, continue
26609      // This will go back to the eme-controller for expired keys to get a new keyLoadPromise
26610    }
26611
26612    // Load the key or return the loading promise
26613    keyInfo = this.keyUriToKeyInfo[uri] = {
26614      decryptdata,
26615      keyLoadPromise: null,
26616      loader: null,
26617      mediaKeySessionContext: null
26618    };
26619    switch (decryptdata.method) {
26620      case 'ISO-23001-7':
26621      case 'SAMPLE-AES':
26622      case 'SAMPLE-AES-CENC':
26623      case 'SAMPLE-AES-CTR':
26624        if (decryptdata.keyFormat === 'identity') {
26625          // loadKeyHTTP handles http(s) and data URLs
26626          return this.loadKeyHTTP(keyInfo, frag);
26627        }
26628        return this.loadKeyEME(keyInfo, frag);
26629      case 'AES-128':
26630        return this.loadKeyHTTP(keyInfo, frag);
26631      default:
26632        return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
26633    }
26634  }
26635  loadKeyEME(keyInfo, frag) {
26636    const keyLoadedData = {
26637      frag,
26638      keyInfo
26639    };
26640    if (this.emeController && this.config.emeEnabled) {
26641      const keySessionContextPromise = this.emeController.loadKey(keyLoadedData);
26642      if (keySessionContextPromise) {
26643        return (keyInfo.keyLoadPromise = keySessionContextPromise.then(keySessionContext => {
26644          keyInfo.mediaKeySessionContext = keySessionContext;
26645          return keyLoadedData;
26646        })).catch(error => {
26647          // Remove promise for license renewal or retry
26648          keyInfo.keyLoadPromise = null;
26649          throw error;
26650        });
26651      }
26652    }
26653    return Promise.resolve(keyLoadedData);
26654  }
26655  loadKeyHTTP(keyInfo, frag) {
26656    const config = this.config;
26657    const Loader = config.loader;
26658    const keyLoader = new Loader(config);
26659    frag.keyLoader = keyInfo.loader = keyLoader;
26660    return keyInfo.keyLoadPromise = new Promise((resolve, reject) => {
26661      const loaderContext = {
26662        keyInfo,
26663        frag,
26664        responseType: 'arraybuffer',
26665        url: keyInfo.decryptdata.uri
26666      };
26667
26668      // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times,
26669      // key-loader will trigger an error and rely on stream-controller to handle retry logic.
26670      // this will also align retry logic with fragment-loader
26671      const loadPolicy = config.keyLoadPolicy.default;
26672      const loaderConfig = {
26673        loadPolicy,
26674        timeout: loadPolicy.maxLoadTimeMs,
26675        maxRetry: 0,
26676        retryDelay: 0,
26677        maxRetryDelay: 0
26678      };
26679      const loaderCallbacks = {
26680        onSuccess: (response, stats, context, networkDetails) => {
26681          const {
26682            frag,
26683            keyInfo,
26684            url: uri
26685          } = context;
26686          if (!frag.decryptdata || keyInfo !== this.keyUriToKeyInfo[uri]) {
26687            return reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error('after key load, decryptdata unset or changed'), networkDetails));
26688          }
26689          keyInfo.decryptdata.key = frag.decryptdata.key = new Uint8Array(response.data);
26690
26691          // detach fragment key loader on load success
26692          frag.keyLoader = null;
26693          keyInfo.loader = null;
26694          resolve({
26695            frag,
26696            keyInfo
26697          });
26698        },
26699        onError: (response, context, networkDetails, stats) => {
26700          this.resetLoader(context);
26701          reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`HTTP Error ${response.code} loading key ${response.text}
26701`), networkDetails, _objectSpread2({
26702            url: loaderContext.url,
26703            data: undefined
26704          }, response)));
26705        },
26706        onTimeout: (stats, context, networkDetails) => {
26707          this.resetLoader(context);
26708          reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error('key loading timed out'), networkDetails));
26709        },
26710        onAbort: (stats, context, networkDetails) => {
26711          this.resetLoader(context);
26712          reject(this.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error('key loading aborted'), networkDetails));
26713        }
26714      };
26715      keyLoader.load(loaderContext, loaderConfig, loaderCallbacks);
26716    });
26717  }
26718  resetLoader(context) {
26719    const {
26720      frag,
26721      keyInfo,
26722      url: uri
26723    } = context;
26724    const loader = keyInfo.loader;
26725    if (frag.keyLoader === loader) {
26726      frag.keyLoader = null;
26727      keyInfo.loader = null;
26728    }
26729    delete this.keyUriToKeyInfo[uri];
26730    if (loader) {
26731      loader.destroy();
26732    }
26733  }
26734}
26735
26736function getSourceBuffer() {
26737  return self.SourceBuffer || self.WebKitSourceBuffer;
26738}
26739function isMSESupported() {
26740  const mediaSource = getMediaSource();
26741  if (!mediaSource) {
26742    return false;
26743  }
26744
26745  // if SourceBuffer is exposed ensure its API is valid
26746  // Older browsers do not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible
26747  const sourceBuffer = getSourceBuffer();
26748  return !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === 'function' && typeof sourceBuffer.prototype.remove === 'function';
26749}
26750function isSupported() {
26751  if (!isMSESupported()) {
26752    return false;
26753  }
26754  const mediaSource = getMediaSource();
26755  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'))));
26756}
26757function changeTypeSupported() {
26758  var _sourceBuffer$prototy;
26759  const sourceBuffer = getSourceBuffer();
26760  return typeof (sourceBuffer == null ? void 0 : (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === 'function';
26761}
26762
26763const STALL_MINIMUM_DURATION_MS = 250;
26764const MAX_START_GAP_JUMP = 2.0;
26765const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
26766const SKIP_BUFFER_RANGE_START = 0.05;
26767class GapController {
26768  constructor(config, media, fragmentTracker, hls) {
26769    this.config = void 0;
26770    this.media = null;
26771    this.fragmentTracker = void 0;
26772    this.hls = void 0;
26773    this.nudgeRetry = 0;
26774    this.stallReported = false;
26775    this.stalled = null;
26776    this.moved = false;
26777    this.seeking = false;
26778    this.config = config;
26779    this.media = media;
26780    this.fragmentTracker = fragmentTracker;
26781    this.hls = hls;
26782  }
26783  destroy() {
26784    this.media = null;
26785    // @ts-ignore
26786    this.hls = this.fragmentTracker = null;
26787  }
26788
26789  /**
26790   * Checks if the playhead is stuck within a gap, and if so, attempts to free it.
26791   * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range).
26792   *
26793   * @param lastCurrentTime - Previously read playhead position
26794   */
26795  poll(lastCurrentTime, activeFrag) {
26796    const {
26797      config,
26798      media,
26799      stalled
26800    } = this;
26801    if (media === null) {
26802      return;
26803    }
26804    const {
26805      currentTime,
26806      seeking
26807    } = media;
26808    const seeked = this.seeking && !seeking;
26809    const beginSeek = !this.seeking && seeking;
26810    this.seeking = seeking;
26811
26812    // The playhead is moving, no-op
26813    if (currentTime !== lastCurrentTime) {
26814      this.moved = true;
26815      if (!seeking) {
26816        this.nudgeRetry = 0;
26817      }
26818      if (stalled !== null) {
26819        // The playhead is now moving, but was previously stalled
26820        if (this.stallReported) {
26821          const _stalledDuration = self.performance.now() - stalled;
26822          logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
26823          this.stallReported = false;
26824        }
26825        this.stalled = null;
26826      }
26827      return;
26828    }
26829
26830    // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
26831    if (beginSeek || seeked) {
26832      this.stalled = null;
26833      return;
26834    }
26835
26836    // The playhead should not be moving
26837    if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) {
26838      this.nudgeRetry = 0;
26839      return;
26840    }
26841    const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
26842    const nextStart = bufferInfo.nextStart || 0;
26843    if (seeking) {
26844      // Waiting for seeking in a buffered range to complete
26845      const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
26846      // Next buffered range is too far ahead to jump to while still seeking
26847      const noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime);
26848      if (hasEnoughBuffer || noBufferGap) {
26849        return;
26850      }
26851      // Reset moved state when seeking to a point in or before a gap
26852      this.moved = false;
26853    }
26854
26855    // Skip start gaps if we haven't played, but the last poll detected the start of a stall
26856    // The addition poll gives the browser a chance to jump the gap for us
26857    if (!this.moved && this.stalled !== null) {
26858      var _level$details;
26859      // There is no playable buffer (seeked, waiting for buffer)
26860      const isBuffered = bufferInfo.len > 0;
26861      if (!isBuffered && !nextStart) {
26862        return;
26863      }
26864      // Jump start gaps within jump threshold
26865      const startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;
26866
26867      // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
26868      // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
26869      // that begins over 1 target duration after the video start position.
26870      const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
26871      const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
26872      const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
26873      const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
26874      if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
26875        if (!media.paused) {
26876          this._trySkipBufferHole(partialOrGap);
26877        }
26878        return;
26879      }
26880    }
26881
26882    // Start tracking stall time
26883    const tnow = self.performance.now();
26884    if (stalled === null) {
26885      this.stalled = tnow;
26886      return;
26887    }
26888    const stalledDuration = tnow - stalled;
26889    if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
26890      // Report stalling after trying to fix
26891      this._reportStall(bufferInfo);
26892      if (!this.media) {
26893        return;
26894      }
26895    }
26896    const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
26897    this._tryFixBufferStall(bufferedWithHoles, stalledDuration);
26898  }
26899
26900  /**
26901   * Detects and attempts to fix known buffer stalling issues.
26902   * @param bufferInfo - The properties of the current buffer.
26903   * @param stalledDurationMs - The amount of time Hls.js has been stalling for.
26904   * @private
26905   */
26906  _tryFixBufferStall(bufferInfo, stalledDurationMs) {
26907    const {
26908      config,
26909      fragmentTracker,
26910      media
26911    } = this;
26912    if (media === null) {
26913      return;
26914    }
26915    const currentTime = media.currentTime;
26916    const partial = fragmentTracker.getPartialFragment(currentTime);
26917    if (partial) {
26918      // Try to skip over the buffer hole caused by a partial fragment
26919      // This method isn't limited by the size of the gap between buffered ranges
26920      const targetTime = this._trySkipBufferHole(partial);
26921      // we return here in this case, meaning
26922      // the branch below only executes when we haven't seeked to a new position
26923      if (targetTime || !this.media) {
26924        return;
26925      }
26926    }
26927
26928    // if we haven't had to skip over a buffer hole of a partial fragment
26929    // we may just have to "nudge" the playlist as the browser decoding/rendering engine
26930    // needs to cross some sort of threshold covering all source-buffers content
26931    // to start playing properly.
26932    if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
26933      logger.warn('Trying to nudge playhead over buffer-hole');
26934      // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
26935      // We only try to jump the hole if it's under the configured size
26936      // Reset stalled so to rearm watchdog timer
26937      this.stalled = null;
26938      this._tryNudgeBuffer();
26939    }
26940  }
26941
26942  /**
26943   * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
26944   * @param bufferLen - The playhead distance from the end of the current buffer segment.
26945   * @private
26946   */
26947  _reportStall(bufferInfo) {
26948    const {
26949      hls,
26950      media,
26951      stallReported
26952    } = this;
26953    if (!stallReported && media) {
26954      // Report stalled error once
26955      this.stallReported = true;
26956      const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
26957      logger.warn(error.message);
26958      hls.trigger(Events.ERROR, {
26959        type: ErrorTypes.MEDIA_ERROR,
26960        details: ErrorDetails.BUFFER_STALLED_ERROR,
26961        fatal: false,
26962        error,
26963        buffer: bufferInfo.len
26964      });
26965    }
26966  }
26967
26968  /**
26969   * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
26970   * @param partial - The partial fragment found at the current time (where playback is stalling).
26971   * @private
26972   */
26973  _trySkipBufferHole(partial) {
26974    const {
26975      config,
26976      hls,
26977      media
26978    } = this;
26979    if (media === null) {
26980      return 0;
26981    }
26982
26983    // Check if currentTime is between unbuffered regions of partial fragments
26984    const currentTime = media.currentTime;
26985    const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
26986    const startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
26987    if (startTime) {
26988      const bufferStarved = bufferInfo.len <= config.maxBufferHole;
26989      const waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
26990      const gapLength = startTime - currentTime;
26991      if (gapLength > 0 && (bufferStarved || waiting)) {
26992        // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
26993        if (gapLength > config.maxBufferHole) {
26994          const {
26995            fragmentTracker
26996          } = this;
26997          let startGap = false;
26998          if (currentTime === 0) {
26999            const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
27000            if (startFrag && startTime < startFrag.end) {
27001              startGap = true;
27002            }
27003          }
27004          if (!startGap) {
27005            const startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN);
27006            if (startProvisioned) {
27007              let moreToLoad = false;
27008              let pos = startProvisioned.end;
27009              while (pos < startTime) {
27010                const provisioned = fragmentTracker.getPartialFragment(pos);
27011                if (provisioned) {
27012                  pos += provisioned.duration;
27013                } else {
27014                  moreToLoad = true;
27015                  break;
27016                }
27017              }
27018              if (moreToLoad) {
27019                return 0;
27020              }
27021            }
27022          }
27023        }
27024        const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
27025        logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
27026        this.moved = true;
27027        this.stalled = null;
27028        media.currentTime = targetTime;
27029        if (partial && !partial.gap) {
27030          const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`);
27031          hls.trigger(Events.ERROR, {
27032            type: ErrorTypes.MEDIA_ERROR,
27033            details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
27034            fatal: false,
27035            error,
27036            reason: error.message,
27037            frag: partial
27038          });
27039        }
27040        return targetTime;
27041      }
27042    }
27043    return 0;
27044  }
27045
27046  /**
27047   * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
27048   * @private
27049   */
27050  _tryNudgeBuffer() {
27051    const {
27052      config,
27053      hls,
27054      media,
27055      nudgeRetry
27056    } = this;
27057    if (media === null) {
27058      return;
27059    }
27060    const currentTime = media.currentTime;
27061    this.nudgeRetry++;
27062    if (nudgeRetry < config.nudgeMaxRetry) {
27063      const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
27064      // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
27065      const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
27066      logger.warn(error.message);
27067      media.currentTime = targetTime;
27068      hls.trigger(Events.ERROR, {
27069        type: ErrorTypes.MEDIA_ERROR,
27070        details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
27071        error,
27072        fatal: false
27073      });
27074    } else {
27075      const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
27076      logger.error(error.message);
27077      hls.trigger(Events.ERROR, {
27078        type: ErrorTypes.MEDIA_ERROR,
27079        details: ErrorDetails.BUFFER_STALLED_ERROR,
27080        error,
27081        fatal: true
27082      });
27083    }
27084  }
27085}
27086
27087const TICK_INTERVAL = 100; // how often to tick in ms
27088
27089class StreamController extends BaseStreamController {
27090  constructor(hls, fragmentTracker, keyLoader) {
27091    super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
27092    this.audioCodecSwap = false;
vendor: 4,333 bytes, lines 27093-27202
27093    this.gapController = null;
27094    this.level = -1;
27095    this._forceStartLoad = false;
27096    this.altAudio = false;
27097    this.audioOnly = false;
27098    this.fragPlaying = null;
27099    this.onvplaying = null;
27100    this.onvseeked = null;
27101    this.fragLastKbps = 0;
27102    this.couldBacktrack = false;
27103    this.backtrackFragment = null;
27104    this.audioCodecSwitch = false;
27105    this.videoBuffer = null;
27106    this._registerListeners();
27107  }
27108  _registerListeners() {
27109    const {
27110      hls
27111    } = this;
27112    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
27113    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
27114    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
27115    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
27116    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
27117    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27118    hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
27119    hls.on(Events.ERROR, this.onError, this);
27120    hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
27121    hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
27122    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
27123    hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
27124    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
27125    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
27126  }
27127  _unregisterListeners() {
27128    const {
27129      hls
27130    } = this;
27131    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
27132    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
27133    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
27134    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
27135    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27136    hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
27137    hls.off(Events.ERROR, this.onError, this);
27138    hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
27139    hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
27140    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
27141    hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
27142    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
27143    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
27144  }
27145  onHandlerDestroying() {
27146    this._unregisterListeners();
27147    super.onHandlerDestroying();
27148  }
27149  startLoad(startPosition) {
27150    if (this.levels) {
27151      const {
27152        lastCurrentTime,
27153        hls
27154      } = this;
27155      this.stopLoad();
27156      this.setInterval(TICK_INTERVAL);
27157      this.level = -1;
27158      if (!this.startFragRequested) {
27159        // determine load level
27160        let startLevel = hls.startLevel;
27161        if (startLevel === -1) {
27162          if (hls.config.testBandwidth && this.levels.length > 1) {
27163            // -1 : guess start Level by doing a bitrate test by loading first fragment of lowest quality level
27164            startLevel = 0;
27165            this.bitrateTest = true;
27166          } else {
27167            startLevel = hls.firstAutoLevel;
27168          }
27169        }
27170        // set new level to playlist loader : this will trigger start level load
27171        // hls.nextLoadLevel remains until it is set to a new value or until a new frag is successfully loaded
27172        hls.nextLoadLevel = startLevel;
27173        this.level = hls.loadLevel;
27174        this.loadedmetadata = false;
27175      }
27176      // if startPosition undefined but lastCurrentTime set, set startPosition to last currentTime
27177      if (lastCurrentTime > 0 && startPosition === -1) {
27178        this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);
27179        startPosition = lastCurrentTime;
27180      }
27181      this.state = State.IDLE;
27182      this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition;
27183      this.tick();
27184    } else {
27185      this._forceStartLoad = true;
27186      this.state = State.STOPPED;
27187    }
27188  }
27189  stopLoad() {
27190    this._forceStartLoad = false;
27191    super.stopLoad();
27192  }
27193  doTick() {
27194    switch (this.state) {
27195      case State.WAITING_LEVEL:
27196        {
27197          const {
27198            levels,
27199            level
27200          } = this;
27201          const currentLevel = levels == null ? void 0 : levels[level];
27202          const details = currentLevel == null ? void 0 : currentLevel.details;
27203          if (details && (!details.live || this.levelLastLoaded === currentLevel)) {
27204            if (this.waitForCdnTuneIn(details)) {
27205              break;
27206            }
27207            this.state = State.IDLE;
27208            break;
27209          } else if (this.hls.nextLoadLevel !== this.level) {
27210            this.state = State.IDLE;
27211            break;
27212          }
27213          break;
27214        }
27215      case State.FRAG_LOADING_WAITING_RETRY:
27216        {
27217          var _this$media;
27218          const now = self.performance.now();
27219          const retryDate = this.retryDate;
27220          // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading
27221          if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) {
27222            const {
27223              levels,
27224              level
27225            } = this;
27226            const currentLevel = levels == null ? void 0 : levels[level];
27227            this.resetStartWhenNotLoaded(currentLevel || null);
27228            this.state = State.IDLE;
27229          }
27230        }
27231        break;
27232    }
27233    if (this.state === State.IDLE) {
27234      this.doTickIdle();
27235    }
27236    this.onTickEnd();
27237  }
27238  onTickEnd() {
27239    super.onTickEnd();
27240    this.checkBuffer();
27241    this.checkFragmentChanged();
27242  }
27243  doTickIdle() {
27244    const {
27245      hls,
27246      levelLastLoaded,
27247      levels,
27248      media
27249    } = this;
27250
27251    // if start level not parsed yet OR
27252    // if video not attached AND start fragment already requested OR start frag prefetch not enabled
27253    // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment
27254    if (levelLastLoaded === null || !media && (this.startFragRequested || !hls.config.startFragPrefetch)) {
27255      return;
27256    }
27257
27258    // If the "main" level is audio-only but we are loading an alternate track in the same group, do not load anything
27259    if (this.altAudio && this.audioOnly) {
27260      return;
27261    }
27262    const level = hls.nextLoadLevel;
27263    if (!(levels != null && levels[level])) {
27264      return;
27265    }
27266    const levelInfo = levels[level];
27267
27268    // if buffer length is less than maxBufLen try to load a new fragment
27269
27270    const bufferInfo = this.getMainFwdBufferInfo();
27271    if (bufferInfo === null) {
27272      return;
27273    }
27274    const lastDetails = this.getLevelDetails();
27275    if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) {
27276      const data = {};
27277      if (this.altAudio) {
27278        data.type = 'video';
27279      }
27280      this.hls.trigger(Events.BUFFER_EOS, data);
27281      this.state = State.ENDED;
27282      return;
27283    }
27284
27285    // set next load level : this will trigger a playlist load if needed
27286    if (hls.loadLevel !== level && hls.manualLevel === -1) {
27287      this.log(`Adapting to level ${level} from level ${this.level}`);
27288    }
27289    this.level = hls.nextLoadLevel = level;
27290    const levelDetails = levelInfo.details;
27291    // if level info not retrieved yet, switch state and wait for level retrieval
27292    // if live playlist, ensure that new playlist has been refreshed to avoid loading/try to load
27293    // a useless and outdated fragment (that might even introduce load error if it is already out of the live playlist)
27294    if (!levelDetails || this.state === State.WAITING_LEVEL || levelDetails.live && this.levelLastLoaded !== levelInfo) {
27295      this.level = level;
27296      this.state = State.WAITING_LEVEL;
27297      return;
27298    }
27299    const bufferLen = bufferInfo.len;
27300
27301    // 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
27302    const maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate);
27303
27304    // Stay idle if we are still with buffer margins
27305    if (bufferLen >= maxBufLen) {
27306      return;
27307    }
27308    if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) {
27309      this.backtrackFragment = null;
27310    }
27311    const targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end;
27312    let frag = this.getNextFragment(targetBufferTime, levelDetails);
27313    // Avoid backtracking by loading an earlier segment in streams with segments that do not start with a key frame (flagged by `couldBacktrack`)
27314    if (this.couldBacktrack && !this.fragPrevious && frag && frag.sn !== 'initSegment' && this.fragmentTracker.getState(frag) !== FragmentState.OK) {
27315      var _this$backtrackFragme;
27316      const backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn;
27317      const fragIdx = backtrackSn - levelDetails.startSN;
27318      const backtrackFrag = levelDetails.fragments[fragIdx - 1];
27319      if (backtrackFrag && frag.cc === backtrackFrag.cc) {
27320        frag = backtrackFrag;
27321        this.fragmentTracker.removeFragment(backtrackFrag);
27322      }
27323    } else if (this.backtrackFragment && bufferInfo.len) {
27324      this.backtrackFragment = null;
27325    }
27326    // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
27327    if (frag && this.isLoopLoading(frag, targetBufferTime)) {
27328      const gapStart = frag.gap;
27329      if (!gapStart) {
27330        // Cleanup the fragment tracker before trying to find the next unbuffered fragment
27331        const type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO;
27332        const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
27333        if (mediaBuffer) {
27334          this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
27335        }
27336      }
27337      frag = this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
27338    }
27339    if (!frag) {
27340      return;
27341    }
27342    if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
27343      frag = frag.initSegment;
27344    }
27345    this.loadFragment(frag, levelInfo, targetBufferTime);
27346  }
27347  loadFragment(frag, level, targetBufferTime) {
27348    // Check if fragment is not loaded
27349    const fragState = this.fragmentTracker.getState(frag);
27350    this.fragCurrent = frag;
27351    if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
27352      if (frag.sn === 'initSegment') {
27353        this._loadInitSegment(frag, level);
27354      } else if (this.bitrateTest) {
27355        this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`);
27356        this._loadBitrateTestFrag(frag, level);
27357      } else {
27358        this.startFragRequested = true;
27359        super.loadFragment(frag, level, targetBufferTime);
27360      }
27361    } else {
27362      this.clearTrackerIfNeeded(frag);
27363    }
27364  }
27365  getBufferedFrag(position) {
27366    return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN);
27367  }
27368  followingBufferedFrag(frag) {
27369    if (frag) {
27370      // try to get range of next fragment (500ms after this range)
27371      return this.getBufferedFrag(frag.end + 0.5);
27372    }
27373    return null;
27374  }
27375
27376  /*
27377    on immediate level switch :
27378     - pause playback if playing
27379     - cancel any pending load request
27380     - and trigger a buffer flush
27381  */
27382  immediateLevelSwitch() {
27383    this.abortCurrentFrag();
27384    this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
27385  }
27386
27387  /**
27388   * try to switch ASAP without breaking video playback:
27389   * in order to ensure smooth but quick level switching,
27390   * we need to find the next flushable buffer range
27391   * we should take into account new segment fetch time
27392   */
27393  nextLevelSwitch() {
27394    const {
27395      levels,
27396      media
27397    } = this;
27398    // ensure that media is defined and that metadata are available (to retrieve currentTime)
27399    if (media != null && media.readyState) {
27400      let fetchdelay;
27401      const fragPlayingCurrent = this.getAppendedFrag(media.currentTime);
27402      if (fragPlayingCurrent && fragPlayingCurrent.start > 1) {
27403        // flush buffer preceding current fragment (flush until current fragment start offset)
27404        // minus 1s to avoid video freezing, that could happen if we flush ke
27404yframe of current video ...
27405        this.flushMainBuffer(0, fragPlayingCurrent.start - 1);
27406      }
27407      const levelDetails = this.getLevelDetails();
27408      if (levelDetails != null && levelDetails.live) {
27409        const bufferInfo = this.getMainFwdBufferInfo();
27410        // Do not flush in live stream with low buffer
27411        if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) {
27412          return;
27413        }
27414      }
27415      if (!media.paused && levels) {
27416        // add a safety delay of 1s
27417        const nextLevelId = this.hls.nextLoadLevel;
27418        const nextLevel = levels[nextLevelId];
27419        const fragLastKbps = this.fragLastKbps;
27420        if (fragLastKbps && this.fragCurrent) {
27421          fetchdelay = this.fragCurrent.duration * nextLevel.maxBitrate / (1000 * fragLastKbps) + 1;
27422        } else {
27423          fetchdelay = 0;
27424        }
27425      } else {
27426        fetchdelay = 0;
27427      }
27428      // this.log('fetchdelay:'+fetchdelay);
27429      // find buffer range that will be reached once new fragment will be fetched
27430      const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay);
27431      if (bufferedFrag) {
27432        // we can flush buffer range following this one without stalling playback
27433        const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag);
27434        if (nextBufferedFrag) {
27435          // if we are here, we can also cancel any loading/demuxing in progress, as they are useless
27436          this.abortCurrentFrag();
27437          // start flush position is in next buffered frag. Leave some padding for non-independent segments and smoother playback.
27438          const maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start;
27439          const fragDuration = nextBufferedFrag.duration;
27440          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)));
27441          this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY);
27442        }
27443      }
27444    }
27445  }
27446  abortCurrentFrag() {
27447    const fragCurrent = this.fragCurrent;
27448    this.fragCurrent = null;
27449    this.backtrackFragment = null;
27450    if (fragCurrent) {
27451      fragCurrent.abortRequests();
27452      this.fragmentTracker.removeFragment(fragCurrent);
27453    }
27454    switch (this.state) {
27455      case State.KEY_LOADING:
27456      case State.FRAG_LOADING:
27457      case State.FRAG_LOADING_WAITING_RETRY:
27458      case State.PARSING:
27459      case State.PARSED:
27460        this.state = State.IDLE;
27461        break;
27462    }
27463    this.nextLoadPosition = this.getLoadPosition();
27464  }
27465  flushMainBuffer(startOffset, endOffset) {
27466    super.flushMainBuffer(startOffset, endOffset, this.altAudio ? 'video' : null);
27467  }
27468  onMediaAttached(event, data) {
27469    super.onMediaAttached(event, data);
27470    const media = data.media;
27471    this.onvplaying = this.onMediaPlaying.bind(this);
27472    this.onvseeked = this.onMediaSeeked.bind(this);
27473    media.addEventListener('playing', this.onvplaying);
27474    media.addEventListener('seeked', this.onvseeked);
27475    this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
27476  }
27477  onMediaDetaching() {
27478    const {
27479      media
27480    } = this;
27481    if (media && this.onvplaying && this.onvseeked) {
27482      media.removeEventListener('playing', this.onvplaying);
27483      media.removeEventListener('seeked', this.onvseeked);
27484      this.onvplaying = this.onvseeked = null;
27485      this.videoBuffer = null;
27486    }
27487    this.fragPlaying = null;
27488    if (this.gapController) {
27489      this.gapController.destroy();
27490      this.gapController = null;
27491    }
27492    super.onMediaDetaching();
27493  }
27494  onMediaPlaying() {
27495    // tick to speed up FRAG_CHANGED triggering
27496    this.tick();
27497  }
27498  onMediaSeeked() {
27499    const media = this.media;
27500    const currentTime = media ? media.currentTime : null;
27501    if (isFiniteNumber(currentTime)) {
27502      this.log(`Media seeked to ${currentTime.toFixed(3)}`);
27503    }
27504
27505    // If seeked was issued before buffer was appended do not tick immediately
27506    const bufferInfo = this.getMainFwdBufferInfo();
27507    if (bufferInfo === null || bufferInfo.len === 0) {
27508      this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
27509      return;
27510    }
27511
27512    // tick to speed up FRAG_CHANGED triggering
27513    this.tick();
27514  }
27515  onManifestLoading() {
27516    // reset buffer on manifest loading
27517    this.log('Trigger BUFFER_RESET');
27518    this.hls.trigger(Events.BUFFER_RESET, undefined);
27519    this.fragmentTracker.removeAllFragments();
27520    this.couldBacktrack = false;
vendor: 5,039 bytes, lines 27521-27644
27521    this.startPosition = this.lastCurrentTime = this.fragLastKbps = 0;
27522    this.levels = this.fragPlaying = this.backtrackFragment = this.levelLastLoaded = null;
27523    this.altAudio = this.audioOnly = this.startFragRequested = false;
27524  }
27525  onManifestParsed(event, data) {
27526    // detect if we have different kind of audio codecs used amongst playlists
27527    let aac = false;
27528    let heaac = false;
27529    data.levels.forEach(level => {
27530      const codec = level.audioCodec;
27531      if (codec) {
27532        aac = aac || codec.indexOf('mp4a.40.2') !== -1;
27533        heaac = heaac || codec.indexOf('mp4a.40.5') !== -1;
27534      }
27535    });
27536    this.audioCodecSwitch = aac && heaac && !changeTypeSupported();
27537    if (this.audioCodecSwitch) {
27538      this.log('Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC');
27539    }
27540    this.levels = data.levels;
27541    this.startFragRequested = false;
27542  }
27543  onLevelLoading(event, data) {
27544    const {
27545      levels
27546    } = this;
27547    if (!levels || this.state !== State.IDLE) {
27548      return;
27549    }
27550    const level = levels[data.level];
27551    if (!level.details || level.details.live && this.levelLastLoaded !== level || this.waitForCdnTuneIn(level.details)) {
27552      this.state = State.WAITING_LEVEL;
27553    }
27554  }
27555  onLevelLoaded(event, data) {
27556    var _curLevel$details;
27557    const {
27558      levels
27559    } = this;
27560    const newLevelId = data.level;
27561    const newDetails = data.details;
27562    const duration = newDetails.totalduration;
27563    if (!levels) {
27564      this.warn(`Levels were reset while loading level ${newLevelId}`);
27565      return;
27566    }
27567    this.log(`Level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''}, cc [${newDetails.startCC}, ${newDetails.endCC}] duration:${duration}`);
27568    const curLevel = levels[newLevelId];
27569    const fragCurrent = this.fragCurrent;
27570    if (fragCurrent && (this.state === State.FRAG_LOADING || this.state === State.FRAG_LOADING_WAITING_RETRY)) {
27571      if (fragCurrent.level !== data.level && fragCurrent.loader) {
27572        this.abortCurrentFrag();
27573      }
27574    }
27575    let sliding = 0;
27576    if (newDetails.live || (_curLevel$details = curLevel.details) != null && _curLevel$details.live) {
27577      var _this$levelLastLoaded;
27578      this.checkLiveUpdate(newDetails);
27579      if (newDetails.deltaUpdateFailed) {
27580        return;
27581      }
27582      sliding = this.alignPlaylists(newDetails, curLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
27583    }
27584    // override level info
27585    curLevel.details = newDetails;
27586    this.levelLastLoaded = curLevel;
27587    this.hls.trigger(Events.LEVEL_UPDATED, {
27588      details: newDetails,
27589      level: newLevelId
27590    });
27591
27592    // only switch back to IDLE state if we were waiting for level to start downloading a new fragment
27593    if (this.state === State.WAITING_LEVEL) {
27594      if (this.waitForCdnTuneIn(newDetails)) {
27595        // Wait for Low-Latency CDN Tune-in
27596        return;
27597      }
27598      this.state = State.IDLE;
27599    }
27600    if (!this.startFragRequested) {
27601      this.setStartPosition(newDetails, sliding);
27602    } else if (newDetails.live) {
27603      this.synchronizeToLiveEdge(newDetails);
27604    }
27605
27606    // trigger handler right now
27607    this.tick();
27608  }
27609  _handleFragmentLoadProgress(data) {
27610    var _frag$initSegment;
27611    const {
27612      frag,
27613      part,
27614      payload
27615    } = data;
27616    const {
27617      levels
27618    } = this;
27619    if (!levels) {
27620      this.warn(`Levels were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);
27621      return;
27622    }
27623    const currentLevel = levels[frag.level];
27624    const details = currentLevel.details;
27625    if (!details) {
27626      this.warn(`Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`);
27627      this.fragmentTracker.removeFragment(frag);
27628      return;
27629    }
27630    const videoCodec = currentLevel.videoCodec;
27631
27632    // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)
27633    const accurateTimeOffset = details.PTSKnown || !details.live;
27634    const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data;
27635    const audioCodec = this._getAudioCodec(currentLevel);
27636
27637    // transmux the MPEG-TS data to ISO-BMFF segments
27638    // this.log(`Transmuxing ${frag.sn} of [${details.startSN} ,${details.endSN}],level ${frag.level}, cc ${frag.cc}`);
27639    const transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
27640    const partIndex = part ? part.index : -1;
27641    const partial = partIndex !== -1;
27642    const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
27643    const initPTS = this.initPTS[frag.cc];
27644    transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOff
27644set, chunkMeta, initPTS);
27645  }
27646  onAudioTrackSwitching(event, data) {
27647    // if any URL found on new audio track, it is an alternate audio track
27648    const fromAltAudio = this.altAudio;
27649    const altAudio = !!data.url;
27650    // if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered
27651    // don't do anything if we switch to alt audio: audio stream controller is handling it.
27652    // we will just have to change buffer scheduling on audioTrackSwitched
27653    if (!altAudio) {
27654      if (this.mediaBuffer !== this.media) {
27655        this.log('Switching on main audio, use media.buffered to schedule main fragment loading');
27656        this.mediaBuffer = this.media;
27657        const fragCurrent = this.fragCurrent;
27658        // we need to refill audio buffer from main: cancel any frag loading to speed up audio switch
27659        if (fragCurrent) {
27660          this.log('Switching to main audio track, cancel main fragment load');
27661          fragCurrent.abortRequests();
27662          this.fragmentTracker.removeFragment(fragCurrent);
27663        }
27664        // destroy transmuxer to force init segment generation (following audio switch)
27665        this.resetTransmuxer();
27666        // switch to IDLE state to load new fragment
27667        this.resetLoadingState();
27668      } else if (this.audioOnly) {
27669        // Reset audio transmuxer so when switching back to main audio we're not still appending where we left off
27670        this.resetTransmuxer();
27671      }
27672      const hls = this.hls;
27673      // If switching from alt to main audio, flush all audio and trigger track switched
27674      if (fromAltAudio) {
27675        hls.trigger(Events.BUFFER_FLUSHING, {
27676          startOffset: 0,
27677          endOffset: Number.POSITIVE_INFINITY,
27678          type: null
27679        });
27680        this.fragmentTracker.removeAllFragments();
27681      }
27682      hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
27683    }
27684  }
27685  onAudioTrackSwitched(event, data) {
27686    const trackId = data.id;
27687    const altAudio = !!this.hls.audioTracks[trackId].url;
27688    if (altAudio) {
27689      const videoBuffer = this.videoBuffer;
27690      // if we switched on alternate audio, ensure that main fragment scheduling is synced with video sourcebuffer buffered
27691      if (videoBuffer && this.mediaBuffer !== videoBuffer) {
27692        this.log('Switching on alternate audio, use video.buffered to schedule main fragment loading');
27693        this.mediaBuffer = videoBuffer;
27694      }
27695    }
27696    this.altAudio = altAudio;
27697    this.tick();
27698  }
27699  onBufferCreated(event, data) {
27700    const tracks = data.tracks;
27701    let mediaTrack;
27702    let name;
27703    let alternate = false;
27704    for (const type in tracks) {
27705      const track = tracks[type];
27706      if (track.id === 'main') {
27707        name = type;
27708        mediaTrack = track;
27709        // keep video source buffer reference
27710        if (type === 'video') {
27711          const videoTrack = tracks[type];
27712          if (videoTrack) {
27713            this.videoBuffer = videoTrack.buffer;
27714          }
27715        }
27716      } else {
27717        alternate = true;
27718      }
27719    }
27720    if (alternate && mediaTrack) {
27721      this.log(`Alternate track found, use ${name}.buffered to schedule main fragment loading`);
27722      this.mediaBuffer = mediaTrack.buffer;
27723    } else {
27724      this.mediaBuffer = this.media;
27725    }
27726  }
27727  onFragBuffered(event, data) {
27728    const {
27729      frag,
27730      part
27731    } = data;
27732    if (frag && frag.type !== PlaylistLevelType.MAIN) {
27733      return;
27734    }
27735    if (this.fragContextChanged(frag)) {
27736      // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion
27737      // Avoid setting state back to IDLE, since that will interfere with a level switch
27738      this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index : ''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}`);
27739      if (this.state === State.PARSED) {
27740        this.state = State.IDLE;
27741      }
27742      return;
27743    }
27744    const stats = part ? part.stats : frag.stats;
27745    this.fragLastKbps = Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first));
27746    if (frag.sn !== 'initSegment') {
27747      this.fragPrevious = frag;
27748    }
27749    this.fragBufferedComplete(frag, part);
27750  }
27751  onError(event, data) {
27752    var _data$context;
27753    if (data.fatal) {
27754      this.state = State.ERROR;
27755      return;
27756    }
27757    switch (data.details) {
27758      case ErrorDetails.FRAG_GAP:
27759      case ErrorDetails.FRAG_PARSING_ERROR:
27760      case ErrorDetails.FRAG_DECRYPT_ERROR:
27761      case ErrorDetails.FRAG_LOAD_ERROR:
27762      case ErrorDetails.FRAG_LOAD_TIMEOUT:
27763      case ErrorDetails.KEY_LOAD_ERROR:
27764      case ErrorDetails.KEY_LOAD_TIMEOUT:
27765        this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data);
27766        break;
27767      case ErrorDetails.LEVEL_LOAD_ERROR:
27768      case ErrorDetails.LEVEL_LOAD_TIMEOUT:
27769      case ErrorDetails.LEVEL_PARSING_ERROR:
27770        // in case of non fatal error while loading level, if level controller is not retrying to load level, switch back to IDLE
27771        if (!data.levelRetry && this.state === State.WAITING_LEVEL && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.LEVEL) {
27772          this.state = State.IDLE;
27773        }
27774        break;
27775      case ErrorDetails.BUFFER_APPEND_ERROR:
27776      case ErrorDetails.BUFFER_FULL_ERROR:
27777        if (!data.parent || data.parent !== 'main') {
27778          return;
27779        }
27780        if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) {
27781          this.resetLoadingState();
27782          return;
27783        }
27784        if (this.reduceLengthAndFlushBuffer(data)) {
27785          this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
27786        }
27787        break;
27788      case ErrorDetails.INTERNAL_EXCEPTION:
27789        this.recoverWorkerError(data);
27790        break;
27791    }
27792  }
27793
27794  // Checks the health of the buffer and attempts to resolve playback stalls.
27795  checkBuffer() {
27796    const {
27797      media,
27798      gapController
27799    } = this;
27800    if (!media || !gapController || !media.readyState) {
27801      // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0)
27802      return;
27803    }
27804    if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
27805      // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
27806      const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
27807      gapController.poll(this.lastCurrentTime, activeFrag);
27808    }
27809    this.lastCurrentTime = media.currentTime;
27810  }
27811  onFragLoadEmergencyAborted() {
27812    this.state = State.IDLE;
27813    // if loadedmetadata is not set, it means that we are emergency switch down on first frag
27814    // in that case, reset startFragRequested flag
27815    if (!this.loadedmetadata) {
27816      this.startFragRequested = false;
27817      this.nextLoadPosition = this.startPosition;
27818    }
27819    this.tickImmediate();
27820  }
27821  onBufferFlushed(event, {
27822    type
27823  }) {
27824    if (type !== ElementaryStreamTypes.AUDIO || this.audioOnly && !this.altAudio) {
27825      const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
27826      this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
27827      this.tick();
27828    }
27829  }
27830  onLevelsUpdated(event, data) {
27831    if (this.level > -1 && this.fragCurrent) {
27832      this.level = this.fragCurrent.level;
27833    }
27834    this.levels = data.levels;
27835  }
27836  swapAudioCodec() {
27837    this.audioCodecSwap = !this.audioCodecSwap;
27838  }
27839
27840  /**
27841   * Seeks to the set startPosition if not equal to the mediaElement's current time.
27842   */
27843  seekToStartPos() {
27844    const {
27845      media
27846    } = this;
27847    if (!media) {
27848      return;
27849    }
27850    const currentTime = media.currentTime;
27851    let startPosition = this.startPosition;
27852    // only adjust currentTime if different from startPosition or if startPosition not buffered
27853    // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
27854    if (startPosition >= 0 && currentTime < startPosition) {
27855      if (media.seeking) {
27856        this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`);
27857        return;
27858      }
27859      const buffered = BufferHelper.getBuffered(media);
27860      const bufferStart = buffered.length ? buffered.start(0) : 0;
27861      const delta = bufferStart - startPosition;
27862      if (delta > 0 && (delta < this.config.maxBufferHole || delta < this.config.maxFragLookUpTolerance)) {
27863        this.log(`adjusting start position by ${delta} to match buffer start`);
27864        startPosition += delta;
27865        this.startPosition = startPosition;
27866      }
27867      this.log(`seek to target start position ${startPosition} from current time ${currentTime}`);
27868      media.currentTime = startPosition;
27869    }
27870  }
27871  _getAudioCodec(currentLevel) {
27872    let audioCodec = this.config.defaultAudioCodec || currentLevel.audioCodec;
27873    if (this.audioCodecSwap && audioCodec) {
27874      this.log('Swapping audio codec');
27875      if (audioCodec.indexOf('mp4a.40.5') !== -1) {
27876        audioCodec = 'mp4a.40.2';
27877      } else {
27878        audioCodec = 'mp4a.40.5';
27879      }
27880    }
27881    return audioCodec;
27882  }
27883  _loadBitrateTestFrag(frag, level) {
27884    frag.bitrateTest = true;
27885    this._doFragLoad(frag, level).then(data => {
27886      const {
27887        hls
27888      } = this;
27889      if (!data || this.fragContextChanged(frag)) {
27890        return;
27891      }
27892      level.fragmentError = 0;
27893      this.state = State.IDLE;
27894      this.startFragRequested = false;
27895      this.bitrateTest = false;
27896      const stats = frag.stats;
27897      // Bitrate tests fragments are neither parsed nor buffered
27898      stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now();
27899      hls.trigger(Events.FRAG_LOADED, data);
27900      frag.bitrateTest = false;
27901    });
27902  }
27903  _handleTransmuxComplete(transmuxResult) {
27904    var _id3$samples;
27905    const id = 'main';
27906    const {
27907      hls
27908    } = this;
27909    const {
27910      remuxResult,
27911      chunkMeta
27912    } = transmuxResult;
27913    const context = this.getCurrentContext(chunkMeta);
27914    if (!context) {
27915      this.resetWhenMissingContext(chunkMeta);
27916      return;
27917    }
27918    const {
27919      frag,
27920      part,
27921      level
27922    } = context;
27923    const {
27924      video,
27925      text,
27926      id3,
27927      initSegment
27928    } = remuxResult;
27929    const {
27930      details
27931    } = level;
27932    // The audio-stream-controller handles audio buffering if Hls.js is playing an alternate audio track
27933    const audio = this.altAudio ? undefined : remuxResult.audio;
27934
27935    // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level.
27936    // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed.
27937    if (this.fragContextChanged(frag)) {
27938      this.fragmentTracker.removeFragment(frag);
27939      return;
27940    }
27941    this.state = State.PARSING;
27942    if (initSegment) {
27943      if (initSegment != null && initSegment.tracks) {
27944        const mapFragment = frag.initSegment || frag;
27945        this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta);
27946        hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
27947          frag: mapFragment,
27948          id,
27949          tracks: initSegment.tracks
27950        });
27951      }
27952
27953      // This would be nice if Number.isFinite acted as a typeguard, but it doesn't. See: https://github.com/Microsoft/TypeScript/issues/10038
27954      const initPTS = initSegment.initPTS;
27955      const timescale = initSegment.timescale;
27956      if (isFiniteNumber(initPTS)) {
27957        this.initPTS[frag.cc] = {
27958          baseTime: initPTS,
27959          timescale
27960        };
27961        hls.trigger(Events.INIT_PTS_FOUND, {
27962          frag,
27963          id,
27964          initPTS,
27965          timescale
27966        });
27967      }
27968    }
27969
27970    // Avoid buffering if backtracking this fragment
27971    if (video && details && frag.sn !== 'initSegment') {
27972      const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
27973      const isFirstFragment = frag.sn === details.startSN;
27974      const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
27975      if (remuxResult.independent !== false) {
27976        const {
27977          startPTS,
27978          endPTS,
27979          startDTS,
27980          endDTS
27981        } = video;
27982        if (part) {
27983          part.elementaryStreams[video.type] = {
27984            startPTS,
27985            endPTS,
27986            startDTS,
27987            endDTS
27988          };
27989        } else {
27990          if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) {
27991            this.couldBacktrack = true;
27992          }
27993          if (video.dropped && video.independent) {
27994            // Backtrack if dropped frames create a gap after currentTime
27995
27996            const bufferInfo = this.getMainFwdBufferInfo();
27997            const targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole;
27998            const startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS;
27999            if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) {
28000              this.backtrack(frag);
28001              return;
28002            } else if (isFirstInDiscontinuity) {
28003              // Mark segment with a gap to avoid loop loading
28004              frag.gap = true;
28005            }
28006            // Set video stream start to fragment start so that truncated samples do not distort the timeline, and mark it partial
28007            frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true);
28008          } else if (isFirstFragment && startPTS > MAX_START_GAP_JUMP) {
28009            // Mark segment with a gap to skip large start gap
28010            frag.gap = true;
28011          }
28012        }
28013        frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS);
28014        if (this.backtrackFragment) {
28015          this.backtrackFragment = frag;
28016        }
28017        this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment || isFirstInDiscontinuity);
28018      } else if (isFirstFragment || isFirstInDiscontinuity) {
28019        // Mark segment with a gap to avoid loop loading
28020        frag.gap = true;
28021      } else {
28022        this.backtrack(frag);
28023        return;
28024      }
28025    }
28026    if (audio) {
28027      const {
28028        startPTS,
28029        endPTS,
28030        startDTS,
28031        endDTS
28032      } = audio;
28033      if (part) {
28034        part.elementaryStreams[ElementaryStreamTypes.AUDIO] = {
28035          startPTS,
28036          endPTS,
28037          startDTS,
28038          endDTS
28039        };
28040      }
28041      frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);
28042      this.bufferFragmentData(audio, frag, part, chunkMeta);
28043    }
28044    if (details && id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) {
28045      const emittedID3 = {
28046        id,
28047        frag,
28048        details,
28049        samples: id3.samples
28050      };
28051      hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);
28052    }
28053    if (details && text) {
28054      const emittedText = {
28055        id,
28056        frag,
28057        details,
28058        samples: text.samples
28059      };
28060      hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);
28061    }
28062  }
28063  _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) {
28064    if (this.state !== State.PARSING) {
28065      return;
28066    }
28067    this.audioOnly = !!tracks.audio && !tracks.video;
28068
28069    // if audio track is expected to come from audio stream controller, discard any coming from main
28070    if (this.altAudio && !this.audioOnly) {
28071      delete tracks.audio;
28072    }
28073    // include levelCodec in audio and video tracks
28074    const {
28075      audio,
28076      video,
28077      audiovideo
28078    } = tracks;
28079    if (audio) {
28080      let audioCodec = currentLevel.audioCodec;
28081      const ua = navigator.userAgent.toLowerCase();
28082      if (this.audioCodecSwitch) {
28083        if (audioCodec) {
28084          if (audioCodec.indexOf('mp4a.40.5') !== -1) {
28085            audioCodec = 'mp4a.40.2';
28086          } else {
28087            audioCodec = 'mp4a.40.5';
28088          }
28089        }
28090        // In the case that AAC and HE-AAC audio codecs are signalled 
28090in manifest,
28091        // force HE-AAC, as it seems that most browsers prefers it.
28092        // don't force HE-AAC if mono stream, or in Firefox
28093        const audioMetadata = audio.metadata;
28094        if (audioMetadata && 'channelCount' in audioMetadata && (audioMetadata.channelCount || 1) !== 1 && ua.indexOf('firefox') === -1) {
28095          audioCodec = 'mp4a.40.5';
28096        }
28097      }
28098      // HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise
28099      if (audioCodec && audioCodec.indexOf('mp4a.40.5') !== -1 && ua.indexOf('android') !== -1 && audio.container !== 'audio/mpeg') {
28100        // Exclude mpeg audio
28101        audioCodec = 'mp4a.40.2';
28102        this.log(`Android: force audio codec to ${audioCodec}`);
28103      }
28104      if (currentLevel.audioCodec && currentLevel.audioCodec !== audioCodec) {
28105        this.log(`Swapping manifest audio codec "${currentLevel.audioCodec}" for "${audioCodec}"`);
28106      }
28107      audio.levelCodec = audioCodec;
28108      audio.id = 'main';
28109      this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ''}/${currentLevel.audioCodec || ''}/${audio.codec}]`);
28110    }
28111    if (video) {
28112      video.levelCodec = currentLevel.videoCodec;
28113      video.id = 'main';
28114      this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ''}/${video.codec}]`);
28115    }
28116    if (audiovideo) {
28117      this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`);
28118    }
28119    this.hls.trigger(Events.BUFFER_CODECS, tracks);
28120    // loop through tracks that are going to be provided to bufferController
28121    Object.keys(tracks).forEach(trackName => {
28122      const track = tracks[trackName];
28123      const initSegment = track.initSegment;
28124      if (initSegment != null && initSegment.byteLength) {
28125        this.hls.trigger(Events.BUFFER_APPENDING, {
28126          type: trackName,
28127          data: initSegment,
28128          frag,
28129          part: null,
28130          chunkMeta,
28131          parent: frag.type
28132        });
28133      }
28134    });
28135    // trigger handler right now
28136    this.tickImmediate();
28137  }
28138  getMainFwdBufferInfo() {
28139    return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
28140  }
28141  backtrack(frag) {
28142    this.couldBacktrack = true;
28143    // Causes findFragments to backtrack through fragments to find the keyframe
28144    this.backtrackFragment = frag;
28145    this.resetTransmuxer();
28146    this.flushBufferGap(frag);
28147    this.fragmentTracker.removeFragment(frag);
28148    this.fragPrevious = null;
28149    this.nextLoadPosition = frag.start;
28150    this.state = State.IDLE;
28151  }
28152  checkFragmentChanged() {
28153    const video = this.media;
28154    let fragPlayingCurrent = null;
28155    if (video && video.readyState > 1 && video.seeking === false) {
28156      const currentTime = video.currentTime;
28157      /* if video element is in seeked state, currentTime can only increase.
28158        (assuming that playback rate is positive ...)
28159        As sometimes currentTime jumps back to zero after a
28160        media decode error, check this, to avoid seeking back to
28161        wrong position after a media decode error
28162      */
28163
28164      if (BufferHelper.isBuffered(video, currentTime)) {
28165        fragPlayingCurrent = this.getAppendedFrag(currentTime);
28166      } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) {
28167        /* ensure that FRAG_CHANGED event is triggered at startup,
28168          when first video frame is displayed and playback is paused.
28169          add a tolerance of 100ms, in case current position is not buffered,
28170          check if current pos+100ms is buffered and use that buffer range
28171          for FRAG_CHANGED event reporting */
28172        fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1);
28173      }
28174      if (fragPlayingCurrent) {
28175        this.backtrackFragment = null;
28176        const fragPlaying = this.fragPlaying;
28177        const fragCurrentLevel = fragPlayingCurrent.level;
28178        if (!fragPlaying || fragPlayingCurrent.sn !== fragPlaying.sn || fragPlaying.level !== fragCurrentLevel) {
28179          this.fragPlaying = fragPlayingCurrent;
28180          this.hls.trigger(Events.FRAG_CHANGED, {
28181            frag: fragPlayingCurrent
28182          });
28183          if (!fragPlaying || fragPlaying.level !== fragCurrentLevel) {
28184            this.hls.trigger(Events.LEVEL_SWITCHED, {
28185              level: fragCurrentLevel
28186            });
28187          }
28188        }
28189      }
28190    }
28191  }
28192  get nextLevel() {
28193    const frag = this.nextBufferedFrag;
28194    if (frag) {
28195      return frag.level;
28196    }
28197    return -1;
28198  }
28199  get currentFrag() {
28200    const media = this.media;
28201    if (media) {
28202      return this.fragPlaying || this.getAppendedFrag(media.currentTime);
28203    }
28204    return null;
28205  }
28206  get currentProgramDateTime() {
28207    const media = this.media;
28208    if (media) {
28209      const currentTime = media.currentTime;
28210      const frag = this.currentFrag;
28211      if (frag && isFiniteNumber(currentTime) && isFiniteNumber(frag.programDateTime)) {
28212        const epocMs = frag.programDateTime + (currentTime - frag.start) * 1000;
28213        return new Date(epocMs);
28214      }
28215    }
28216    return null;
28217  }
28218  get currentLevel() {
28219    const frag = this.currentFrag;
28220    if (frag) {
28221      return frag.level;
28222    }
28223    return -1;
28224  }
28225  get nextBufferedFrag() {
28226    const frag = this.currentFrag;
28227    if (frag) {
28228      return this.followingBufferedFrag(frag);
28229    }
28230    return null;
28231  }
28232  get forceStartLoad() {
28233    return this._forceStartLoad;
28234  }
28235}
28236
28237/**
28238 * The `Hls` class is the core of the HLS.js library used to instantiate player instances.
28239 * @public
28240 */
28241class Hls {
28242  /**
28243   * Get the video-dev/hls.js package version.
28244   */
28245  static get version() {
28246    return "1.5.13";
28247  }
28248
28249  /**
28250   * Check if the required MediaSource Extensions are available.
28251   */
28252  static isMSESupported() {
28253    return isMSESupported();
28254  }
28255
28256  /**
28257   * Check if MediaSource Extensions are available and isTypeSupp
28257orted checks pass for any baseline codecs.
28258   */
28259  static isSupported() {
28260    return isSupported();
28261  }
28262
28263  /**
28264   * Get the MediaSource global used for MSE playback (ManagedMediaSource, MediaSource, or WebKitMediaSource).
28265   */
28266  static getMediaSource() {
28267    return getMediaSource();
28268  }
28269  static get Events() {
28270    return Events;
28271  }
28272  static get ErrorTypes() {
28273    return ErrorTypes;
28274  }
28275  static get ErrorDetails() {
28276    return ErrorDetails;
28277  }
28278
28279  /**
28280   * Get the default configuration applied to new instances.
28281   */
28282  static get DefaultConfig() {
28283    if (!Hls.defaultConfig) {
28284      return hlsDefaultConfig;
28285    }
28286    return Hls.defaultConfig;
28287  }
28288
28289  /**
28290   * Replace the default configuration applied to new instances.
28291   */
28292  static set DefaultConfig(defaultConfig) {
28293    Hls.defaultConfig = defaultConfig;
28294  }
28295
28296  /**
28297   * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`.
28298   * @param userConfig - Configuration options applied over `Hls.DefaultConfig`
28299   */
28300  constructor(userConfig = {}) {
28301    /**
28302     * The runtime configuration used by the player. At instantiation this is combination of `hls.userConfig` merged over `Hls.DefaultConfig`.
28303     */
28304    this.config = void 0;
28305    /**
28306     * The configuration object provided on player instantiation.
28307     */
28308    this.userConfig = void 0;
28309    this.coreComponents = void 0;
28310    this.networkControllers = void 0;
28311    this.started = false;
28312    this._emitter = new EventEmitter();
28313    this._autoLevelCapping = -1;
28314    this._maxHdcpLevel = null;
28315    this.abrController = void 0;
28316    this.bufferController = void 0;
28317    this.capLevelController = void 0;
28318    this.latencyController = void 0;
28319    this.levelController = void 0;
28320    this.streamController = void 0;
28321    this.audioTrackController = void 0;
28322    this.subtitleTrackController = void 0;
28323    this.emeController = void 0;
28324    this.cmcdController = void 0;
28325    this._media = null;
28326    this.url = null;
28327    this.triggeringException = void 0;
28328    enableLogs(userConfig.debug || false, 'Hls instance');
28329    const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
28330    this.userConfig = userConfig;
28331    if (config.progressive) {
28332      enableStreamingMode(config);
28333    }
28334
28335    // core controllers and network loaders
28336    const {
28337      abrController: ConfigAbrController,
28338      bufferController: ConfigBufferController,
28339      capLevelController: ConfigCapLevelController,
28340      errorController: ConfigErrorController,
28341      fpsController: ConfigFpsController
28342    } = config;
28343    const errorController = new ConfigErrorController(this);
28344    const abrController = this.abrController = new ConfigAbrController(this);
28345    const bufferController = this.bufferController = new ConfigBufferController(this);
28346    const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
28347    const fpsController = new ConfigFpsController(this);
28348    const playListLoader = new PlaylistLoader(this);
28349    const id3TrackController = new ID3TrackController(this);
28350    const ConfigContentSteeringController = config.contentSteeringController;
28351    // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
28352    const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
28353    const levelController = this.levelController = new LevelController(this, contentSteering);
28354    // FragmentTracker must be defined before StreamController because the order of event handling is important
28355    const fragmentTracker = new FragmentTracker(this);
28356    const keyLoader = new KeyLoader(this.config);
28357    const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
28358
28359    // Cap level controller uses streamController to flush the buffer
28360    capLevelController.setStreamController(streamController);
28361    // fpsController uses streamController to switch when frames are being dropped
28362    fpsController.setStreamController(streamController);
28363    const networkControllers = [playListLoader, levelController, streamController];
28364    if (contentSteering) {
28365      networkControllers.splice(1, 0, contentSteering);
28366    }
28367    this.networkControllers = networkControllers;
28368    const coreComponents = [abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker];
28369    this.audioTrackController = this.createController(config.audioTrackController, networkControllers);
28370    const AudioStreamControllerClass = config.audioStreamController;
28371    if (AudioStreamControllerClass) {
28372      networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
28373    }
28374    // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important
28375    this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
28376    const SubtitleStreamControllerClass = config.subtitleStreamController;
28377    if (SubtitleStreamControllerClass) {
28378      networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
28379    }
28380    this.createController(config.timelineController, coreComponents);
28381    keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents);
28382    this.cmcdController = this.createController(config.cmcdController, coreComponents);
28383    this.latencyController = this.createController(LatencyController, coreComponents);
28384    this.coreComponents = coreComponents;
28385
28386    // Error controller handles errors before and after all other controllers
28387    // This listener will be invoked after all other controllers error listeners
28388    networkControllers.push(errorController);
28389    const onErrorOut = errorController.onErrorOut;
28390    if (typeof onErrorOut === 'function') {
28391      this.on(Events.ERROR, onErrorOut, errorController);
28392    }
28393  }
28394  createController(ControllerClass, components) {
28395    if (ControllerClass) {
28396      const controllerInstance = new ControllerClass(this);
28397      if (components) {
28398        components.push(controllerInstance);
28399      }
28400      return controllerInstance;
28401    }
28402    return null;
28403  }
28404
28405  // Delegate the EventEmitter through the public API of Hls.js
28406  on(event, listener, context = this) {
28407    this._emitter.on(event, listener, context);
28408  }
28409  once(event, listener, context = this) {
28410    this._emitter.once(event, listener, context);
28411  }
28412  removeAllListeners(event) {
28413    this._emitter.removeAllListeners(event);
28414  }
28415  off(event, listener, context = this, once) {
28416    this._emitter.off(event, listener, context, once);
28417  }
28418  listeners(event) {
28419    return this._emitter.listeners(event);
28420  }
28421  emit(event, name, eventObject) {
28422    return this._emitter.emit(event, name, eventObject);
28423  }
28424  trigger(event, eventObject) {
28425    if (this.config.debug) {
28426      return this.emit(event, event, eventObject);
28427    } else {
28428      try {
28429        return this.emit(event, event, eventObject);
28430      } catch (error) {
28431        logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
28432        // Prevent recursion in error event handlers that throw #5497
28433        if (!this.triggeringException) {
28434          this.triggeringException = true;
28435          const fatal = event === Events.ERROR;
28436          this.trigger(Events.ERROR, {
28437            type: ErrorTypes.OTHER_ERROR,
28438            details: ErrorDetails.INTERNAL_EXCEPTION,
28439            fatal,
28440            event,
28441            error
28442          });
28443          this.triggeringException = false;
28444        }
28445      }
28446    }
28447    return false;
28448  }
28449  listenerCount(event) {
28450    return this._emitter.listenerCount(event);
28451  }
28452
28453  /**
28454   * Dispose of the instance
28455   */
28456  destroy() {
28457    logger.log('destroy');
28458    this.trigger(Events.DESTROYING, undefined);
28459    this.detachMedia();
28460    this.removeAllListeners();
28461    this._autoLevelCapping = -1;
28462    this.url = null;
28463    this.networkControllers.forEach(component => component.destroy());
28464    this.networkControllers.length = 0;
28465    this.coreComponents.forEach(component => component.destroy());
28466    this.coreComponents.length = 0;
28467    // Remove any references that could be held in config options or callbacks
28468    const config = this.config;
28469    config.xhrSetup = config.fetchSetup = undefined;
28470    // @ts-ignore
28471    this.userConfig = null;
28472  }
28473
28474  /**
28475   * Attaches Hls.js to a media element
28476   */
28477  attachMedia(media) {
28478    logger.log('attachMedia');
28479    this._media = media;
28480    this.trigger(Events.MEDIA_ATTACHING, {
28481      media: media
28482    });
28483  }
28484
28485  /**
28486   * Detach Hls.js from the media
28487   */
28488  detachMedia() {
28489    logger.log('detachMedia');
28490    this.trigger(Events.MEDIA_DETACHING, undefined);
28491    this._media = null;
28492  }
28493
28494  /**
28495   * Set the source URL. Can be relative or absolute.
28496   */
28497  loadSource(url) {
28498    this.stopLoad();
28499    const media = this.media;
28500    const loadedSource = this.url;
28501    const loadingSource = this.url = urlToolkitExports.buildAbsoluteURL(self.location.href, url, {
28502      alwaysNormalize: true
28503    });
28504    this._autoLevelCapping = -1;
28505    this._maxHdcpLevel = null;
28506    logger.log(`loadSource:${loadingSource}`);
28507    if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
28508      this.detachMedia();
28509      this.attachMedia(media);
28510    }
28511    // when attaching to a source URL, trigger a playlist load
28512    this.trigger(Events.MANIFEST_LOADING, {
28513      url: url
28514    });
28515  }
28516
28517  /**
28518   * Start loading data from the stream source.
28519   * Depending on default config, client starts loading automatically when a source is set.
28520   *
28521   * @param startPosition - Set the start position to stream from.
28522   * Defaults to -1 (None: starts from earliest point)
28523   */
28524  startLoad(startPosition = -1) {
28525    logger.log(`startLoad(${startPosition})`);
28526    this.started = true;
28527    this.networkControllers.forEach(controller => {
28528      controller.startLoad(startPosition);
28529    });
28530  }
28531
28532  /**
28533   * Stop loading of any stream data.
28534   */
28535  stopLoad() {
28536    logger.log('stopLoad');
28537    this.started = false;
28538    this.networkControllers.forEach(controller => {
28539      controller.stopLoad();
28540    });
28541  }
28542
28543  /**
28544   * Resumes stream controller segment loading if previously started.
28545   */
28546  resumeBuffering() {
28547    if (this.started) {
28548      this.networkControllers.forEach(controller => {
28549        if ('fragmentLoader' in controller) {
28550          controller.startLoad(-1);
28551        }
28552      });
28553    }
28554  }
28555
28556  /**
28557   * Stops stream controller segment loading without changing 'started' state like stopLoad().
28558   * This allows for media buffering to be paused without interupting playlist loading.
28559   */
28560  pauseBuffering() {
28561    this.networkControllers.forEach(controller => {
28562      if ('fragmentLoader' in controller) {
28563        controller.stopLoad();
28564      }
28565    });
28566  }
28567
28568  /**
28569   * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
28570   */
28571  swapAudioCodec() {
28572    logger.log('swapAudioCodec');
28573    this.streamController.swapAudioCodec();
28574  }
28575
28576  /**
28577   * When the media-element fails, this allows to detach and then re-attach it
28578   * as one call (convenience method).
28579   *
28580   * Automatic recovery of media-errors by this process is configurable.
28581   */
28582  recoverMediaError() {
28583    logger.log('recoverMediaError');
28584    const media = this._media;
28585    this.detachMedia();
28586    if (media) {
28587      this.attachMedia(media);
28588    }
28589  }
28590  removeLevel(levelIndex) {
28591    this.levelController.removeLevel(levelIndex);
28592  }
28593
28594  /**
28595   * @returns an array of levels (variants) sorted by HDCP-LEVEL, RESOLUTION (height), FRAME-RATE, CODECS, VIDEO-RANGE, and BANDWIDTH
28596   */
28597  get levels() {
28598    const levels = this.levelController.levels;
28599    return levels ? levels : [];
28600  }
28601
28602  /**
28603   * Index of quality level (variant) currently played
28604   */
28605  get currentLevel() {
28606    return this.streamController.currentLevel;
28607  }
28608
28609  /**
28610   * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
28611   */
28612  set currentLevel(newLevel) {
28613    logger.log(`set currentLevel:${newLevel}`);
28614    this.levelController.manualLevel = newLevel;
28615    this.streamController.immediateLevelSwitch();
28616  }
28617
28618  /**
28619   * Index of next quality level loaded as scheduled by stream controller.
28620   */
28621  get nextLevel() {
28622    return this.streamController.nextLevel;
28623  }
28624
28625  /**
28626   * Set quality level index for next loaded data.
28627   * This will switch the video quality asap, without interrupting playback.
28628   * May abort current loading of data, and flush parts of buffer (outside currently played fragment region).
28629   * @param newLevel - Pass -1 for automatic level selection
28630   */
28631  set nextLevel(newLevel) {
28632    logger.log(`set nextLevel:${newLevel}`);
28633    this.levelController.manualLevel = newLevel;
28634    this.streamController.nextLevelSwitch();
28635  }
28636
28637  /**
28638   * Return the quality level of the currently or last (of none is loaded currently) segment
28639   */
28640  get loadLevel() {
28641    return this.levelController.level;
28642  }
28643
28644  /**
28645   * Set quality level index for next loaded data in a conservative way.
28646   * This will switch the quality without flushing, but interrupt current loading.
28647   * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer.
28648   * @param newLevel - Pass -1 for automatic level selection
28649   */
28650  set loadLevel(newLevel) {
28651    logger.log(`set loadLevel:${newLevel}`);
28652    this.levelController.manualLevel = newLevel;
28653  }
28654
28655  /**
28656   * get next quality level loaded
28657   */
28658  get nextLoadLevel() {
28659    return this.levelController.nextLoadLevel;
28660  }
28661
28662  /**
28663   * Set quality level of next loaded segment in a fully "non-destructive" way.
28664   * Same as `loadLevel` but will wait for next switch (until current loading is done).
28665   */
28666  set nextLoadLevel(level) {
28667    this.levelController.nextLoadLevel = level;
28668  }
28669
28670  /**
28671   * Return "first level": like a default level, if not set,
28672   * falls back to index of first level referenced in manifest
28673   */
28674  get firstLevel() {
28675    return Math.max(this.levelController.firstLevel, this.minAutoLevel);
28676  }
28677
28678  /**
28679   * Sets "first-level", see getter.
28680   */
28681  set firstLevel(newLevel) {
28682    logger.log(`set firstLevel:${newLevel}`);
28683    this.levelController.firstLevel = newLevel;
28684  }
28685
28686  /**
28687   * Return the desired start level for the first fragment that will be loaded.
28688   * The default value of -1 indicates automatic start level selection.
28689   * Setting hls.nextAutoLevel without setting a startLevel will result in
28690   * the nextAutoLevel value being used for one fragment load.
28691   */
28692  get startLevel() {
28693    const startLevel = this.levelController.startLevel;
28694    if (startLevel === -1 && this.abrController.forcedAutoLevel > -1) {
28695      return this.abrController.forcedAutoLevel;
28696    }
28697    return startLevel;
28698  }
28699
28700  /**
28701   * set  start level (level of first fragment that will be played back)
28702   * if not overrided by user, first level appearing in manifest will be used as start level
28703   * if -1 : automatic start level selection, playback will start from level matching download bandwidth
28704   * (determined from download of first segment)
28705   */
28706  set startLevel(newLevel) {
28707    logger.log(`set startLevel:${newLevel}`);
28708    // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
28709    if (newLevel !== -1) {
28710      newLevel = Math.max(newLevel, this.minAutoLevel);
28711    }
28712    this.levelController.startLevel = newLevel;
28713  }
28714
28715  /**
28716   * Whether level capping is enabled.
28717   * Default value is set via `config.capLevelToPlayerSize`.
28718   */
28719  get capLevelToPlayerSize() {
28720    return this.config.capLevelToPlayerSize;
28721  }
28722
28723  /**
28724   * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called.
28725   */
28726  set capLevelToPlayerSize(shouldStartCapping) {
28727    const newCapLevelToPlayerSize = !!shouldStartCapping;
28728    if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) {
28729      if (newCapLevelToPlayerSize) {
28730        this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size.
28731      } else {
28732        this.capLevelController.stopCapping();
28733        this.autoLevelCapping = -1;
28734        this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap.
28735      }
28736      this.config.capLevelToPlayerSize = newCapLevelToPlayerSize;
28737    }
28738  }
28739
28740  /**
28741   * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
28742   */
28743  get autoLevelCapping() {
28744    return this._autoLevelCapping;
28745  }
28746
28747  /**
28748   * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned.
28749   */
28750  get bandwidthEstimate() {
28751    const {
28752      bwEstimator
28753    } = this.abrController;
28754    if (!bwEstimator) {
28755      return NaN;
28756    }
28757    return bwEstimator.getEstimate();
28758  }
28759  set bandwidthEstimate(abrEwmaDefaultEstimate) {
28760    this.abrController.resetEstimator(abrEwmaDefaultEstimate);
28761  }
28762
28763  /**
28764   * get time to first byte estimate
28765   * @type {number}
28766   */
28767  get ttfbEstimate() {
28768    const {
28769      bwEstimator
28770    } = this.abrController;
28771    if (!bwEstimator) {
28772      return NaN;
28773    }
28774    return bwEstimator.getEstimateTTFB();
28775  }
28776
28777  /**
28778   * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
28779   */
28780  set autoLevelCapping(newLevel) {
28781    if (this._autoLevelCapping !== newLevel) {
28782      logger.log(`set autoLevelCapping:${newLevel}`);
28783      this._autoLevelCapping = newLevel;
28784      this.levelController.checkMaxAutoUpdated();
28785    }
28786  }
28787  get maxHdcpLevel() {
28788    return this._maxHdcpLevel;
28789  }
28790  set maxHdcpLevel(value) {
28791    if (isHdcpLevel(value) && this._maxHdcpLevel !== value) {
28792      this._maxHdcpLevel = value;
28793      this.levelController.checkMaxAutoUpdated();
28794    }
28795  }
28796
28797  /**
28798   * True when automatic level selection enabled
28799   */
28800  get autoLevelEnabled() {
28801    return this.levelController.manualLevel === -1;
28802  }
28803
28804  /**
28805   * Level set manually (if any)
28806   */
28807  get manualLevel() {
28808    return this.levelController.manualLevel;
28809  }
28810
28811  /**
28812   * min level selectable in auto mode according to config.minAutoBitrate
28813   */
28814  get minAutoLevel() {
28815    const {
28816      levels,
28817      config: {
28818        minAutoBitrate
28819      }
28820    } = this;
28821    if (!levels) return 0;
28822    const len = levels.length;
28823    for (let i = 0; i < len; i++) {
28824      if (levels[i].maxBitrate >= minAutoBitrate) {
28825        return i;
28826      }
28827    }
28828    return 0;
28829  }
28830
28831  /**
28832   * max level selectable in auto mode according to autoLevelCapping
28833   */
28834  get maxAutoLevel() {
28835    const {
28836      levels,
28837      autoLevelCapping,
28838      maxHdcpLevel
28839    } = this;
28840    let maxAutoLevel;
28841    if (autoLevelCapping === -1 && levels != null && levels.length) {
28842      maxAutoLevel = levels.length - 1;
28843    } else {
28844      maxAutoLevel = autoLevelCapping;
28845    }
28846    if (maxHdcpLevel) {
28847      for (let i = maxAutoLevel; i--;) {
28848        const hdcpLevel = levels[i].attrs['HDCP-LEVEL'];
28849        if (hdcpLevel && hdcpLevel <= maxHdcpLevel) {
28850          return i;
28851        }
28852      }
28853    }
28854    return maxAutoLevel;
28855  }
28856  get firstAutoLevel() {
28857    return this.abrController.firstAutoLevel;
28858  }
28859
28860  /**
28861   * next automatically selected quality level
28862   */
28863  get nextAutoLevel() {
28864    return this.abrController.nextAutoLevel;
28865  }
28866
28867  /**
28868   * this setter is used to force next auto level.
28869   * this is useful to force a switch down in auto mode:
28870   * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example)
28871   * forced value is valid for one fragment. upon successful frag loading at forced level,
28872   * this value will be resetted to -1 by ABR controller.
28873   */
28874  set nextAutoLevel(nextLevel) {
28875    this.abrController.nextAutoLevel = nextLevel;
28876  }
28877
28878  /**
28879   * get the datetime value relative to media.currentTime for the active level Program Date Time if present
28880   */
28881  get playingDate() {
28882    return this.streamController.currentProgramDateTime;
28883  }
28884  get mainForwardBufferInfo() {
28885    return this.streamController.getMainFwdBufferInfo();
28886  }
28887
28888  /**
28889   * Find and select the best matching audio track, making a level switch when a Group change is necessary.
28890   * Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found.
28891   */
28892  setAudioOption(audioOption) {
28893    var _this$audioTrackContr;
28894    return (_this$audioTrackContr = this.audioTrackController) == null ? void 0 : _this$audioTrackContr.setAudioOption(audioOption);
28895  }
28896  /**
28897   * Find and select the best matching subtitle track, making a level switch when a Group change is necessary.
28898   * Updates `hls.config.subtitlePreference`. Returns the selected track, or null when no matching track is found.
28899   */
28900  setSubtitleOption(subtitleOption) {
28901    var _this$subtitleTrackCo;
28902    (_this$subtitleTrackCo = this.subtitleTrackController) == null ? void 0 : _this$subtitleTrackCo.setSubtitleOption(subtitleOption);
28903    return null;
28904  }
28905
28906  /**
28907   * Get the complete list of audio tracks across all media groups
28908   */
28909  get allAudioTracks() {
28910    const audioTrackController = this.audioTrackController;
28911    return audioTrackController ? audioTrackController.allAudioTracks : [];
28912  }
28913
28914  /**
28915   * Get the list of selectable audio tracks
28916   */
28917  get audioTracks() {
28918    const audioTrackController = this.audioTrackController;
28919    return audioTrackController ? audioTrackController.audioTracks : [];
28920  }
28921
28922  /**
28923   * index of the selected audio track (index in audio track lists)
28924   */
28925  get audioTrack() {
28926    const audioTrackController = this.audioTrackController;
28927    return audioTrackController ? audioTrackController.audioTrack : -1;
28928  }
28929
28930  /**
28931   * selects an audio track, based on its index in audio track lists
28932   */
28933  set audioTrack(audioTrackId) {
28934    const audioTrackController = this.audioTrackController;
28935    if (audioTrackController) {
28936      audioTrackController.audioTrack = audioTrackId;
28937    }
28938  }
28939
28940  /**
28941   * get the complete list of subtitle tracks across all media groups
28942   */
28943  get allSubtitleTracks() {
28944    const subtitleTrackController = this.subtitleTrackController;
28945    return subtitleTrackController ? subtitleTrackController.allSubtitleTracks : [];
28946  }
28947
28948  /**
28949   * get alternate subtitle tracks list from playlist
28950   */
28951  get subtitleTracks() {
28952    const subtitleTrackController = this.subtitleTrackController;
28953    return subtitleTrackController ? subtitleTrackController.subtitleTracks : [];
28954  }
28955
28956  /**
28957   * index of the selected subtitle track (index in subtitle track lists)
28958   */
28959  get subtitleTrack() {
28960    const subtitleTrackController = this.subtitleTrackController;
28961    return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1;
28962  }
28963  get media() {
28964    return this._media;
28965  }
28966
28967  /**
28968   * select an subtitle track, based on its index in subtitle track lists
28969   */
28970  set subtitleTrack(subtitleTrackId) {
28971    const subtitleTrackController = this.subtitleTrackController;
28972    if (subtitleTrackController) {
28973      subtitleTrackController.subtitleTrack = subtitleTrackId;
28974    }
28975  }
28976
28977  /**
28978   * Whether subtitle display is enabled or not
28979   */
28980  get subtitleDisplay() {
28981    const subtitleTrackController = this.subtitleTrackController;
28982    return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false;
28983  }
28984
28985  /**
28986   * Enable/disable subtitle display rendering
28987   */
28988  set subtitleDisplay(value) {
28989    const subtitleTrackController = this.subtitleTrackController;
28990    if (subtitleTrackController) {
28991      subtitleTrackController.subtitleDisplay = value;
28992    }
28993  }
28994
28995  /**
28996   * get mode for Low-Latency HLS loading
28997   */
28998  get lowLatencyMode() {
28999    return this.config.lowLatencyMode;
29000  }
29001
29002  /**
29003   * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK.
29004   */
29005  set lowLatencyMode(mode) {
29006    this.config.lowLatencyMode = mode;
29007  }
29008
29009  /**
29010   * Position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```)
29011   * @returns null prior to loading live Playlist
29012   */
29013  get liveSyncPosition() {
29014    return this.latencyController.liveSyncPosition;
29015  }
29016
29017  /**
29018   * Estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced)
29019   * @returns 0 before first playlist is loaded
29020   */
29021  get latency() {
29022    return this.latencyController.latency;
29023  }
29024
29025  /**
29026   * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition```
29027   * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration```
29028   * @returns 0 before first playlist is loaded
29029   */
29030  get maxLatency() {
29031    return this.latencyController.maxLatency;
29032  }
29033
29034  /**
29035   * target distance from the edge as calculated by the latency controller
29036   */
29037  get targetLatency() {
29038    return this.latencyController.targetLatency;
29039  }
29040
29041  /**
29042   * the rate at which the edge of the current live playlist is advancing or 1 if there is none
29043   */
29044  get drift() {
29045    return this.latencyController.drift;
29046  }
29047
29048  /**
29049   * set to true when startLoad is called before MANIFEST_PARSED event
29050   */
29051  get forceStartLoad() {
29052    return this.streamController.forceStartLoad;
29053  }
29054}
29055Hls.defaultConfig = void 0;
29056
29057var root_5 = from_html(`<time id="trim-duration" class="svelte-1ffmt2w"> </time>`);
29058var root_4$1 = from_html(`<div class="component-wrapper svelte-1ffmt2w"><div class="waveform-container svelte-1ffmt2w"><div id="waveform" class="svelte-1ffmt2w"></div></div> <div class="timestamps svelte-1ffmt2w"><time id="time" class="svelte-1ffmt2w">0:00</time> <div><!> <time id="duration" class="svelte-1ffmt2w">0:00</time></div></div> <div class="subtitle-display svelte-1ffmt2w" data-testid="subtitle-display"></div> <!></div>`);
29059var root$1 = from_html(`<audio controls preload="metadata"></audio> <!>`, 1);
29060
29061function AudioPlayer($$anchor, $$props) {
29062	push$1($$props, true);
29063
29064	let value = prop($$props, 'value', 3, null),
29065		subtitles = prop($$props, 'subtitles', 3, null),
29066		dispatch_blob = prop($$props, 'dispatch_blob', 3, () => Promise.resolve()),
29067		interactive = prop($$props, 'interactive', 3, false),
29068		editable = prop($$props, 'editable', 3, true),
29069		trim_region_settings = prop($$props, 'trim_region_settings', 19, () => ({})),
29070		mode = prop($$props, 'mode', 15),
29071		handle_reset_value = prop($$props, 'handle_reset_value', 3, () => {}),
29072		playback_position = prop($$props, 'playback_position', 15);
29073
29074	let url = user_derived(() => value()?.url);
29075	let old_playback_position = state(0);
29076	let container;
29077	let waveform;
29078	let waveform_ready = state(false);
29079	let waveform_component_wrapper;
29080	let playing = state(false);
29081	let subtitle_container;
29082	let timeRef;
29083	let durationRef;
29084	let audio_duration = state(0);
29085	let trimDuration = state(0);
29086	let show_volume_slider = state(false);
29087	let audio_player;
29088	let stream_active = false;
29089	let subtitles_toggle = state(true);
29090	let subtitle_event_handlers = [];
29091	let use_waveform = user_derived(() => $$props.waveform_options.show_recording_waveform && !value()?.is_stream);
29092
29093	user_effect(() => {
29094		if (get(waveform_ready) && get(old_playback_position) !== playback_position() && get(audio_duration)) {
29095			waveform?.seekTo(playback_position() / get(audio_duration));
29096			set(old_playback_position, playback_position(), true);
29097		}
29098	});
29099
29100	const create_waveform = () => {
29101		waveform = u.create({ container, ...$$props.waveform_settings });
29102
29103		if (subtitles() && waveform) {
29104			if (get(subtitles_toggle)) {
29105				add_subtitles_to_waveform(waveform, subtitles());
29106			} else {
29107				hide_subtitles();
29108			}
29109		}
29110
29111		waveform?.on("init", () => {
29112			set(waveform_ready, true);
29113		});
29114
29115		waveform?.on("decode", (duration) => {
29116			set(audio_duration, duration, true);
29117			durationRef && (durationRef.textContent = format_time(duration));
29118		});
29119
29120		let firstTimeUpdate = true;
29121
29122		waveform?.on("timeupdate", (currentTime) => {
29123			timeRef && (timeRef.textContent = format_time(currentTime));
29124
29125			if (firstTimeUpdate) {
29126				firstTimeUpdate = false;
29127
29128				return;
29129			}
29130
29131			playback_position(currentTime);
29132			set(old_playback_position, currentTime, true);
29133		});
29134
29135		waveform?.on("interaction", () => {
29136			const currentTime = waveform?.getCurrentTime() || 0;
29137
29138			timeRef && (timeRef.textContent = format_time(currentTime));
29139			playback_position(currentTime);
29140			set(old_playback_position, currentTime, true);
29141		});
29142
29143		waveform?.on("ready", () => {
29144			if (!$$props.waveform_settings.autoplay) {
29145				waveform?.stop();
29146			} else {
29147				waveform?.play();
29148			}
29149		});
29150
29151		waveform?.on("finish", () => {
29152			if ($$props.loop) {
29153				waveform?.play();
29154			} else {
29155				set(playing, false);
29156				$$props.onstop?.();
29157			}
29158		});
29159
29160		waveform?.on("pause", () => {
29161			set(playing, false);
29162			$$props.onpause?.();
29163		});
29164
29165		waveform?.on("play", () => {
29166			set(playing, true);
29167			$$props.onplay?.();
29168		});
29169
29170		waveform?.on("load", () => {
29171			$$props.onload?.();
29172		});
29173	};
29174
29175	user_effect(() => {
29176		if (get(url) && get(waveform_ready)) {
29177			untrack(() => {
29178				if (value()?.url && waveform) {
29179					waveform.load(value().url).catch((e) => {
29180						if (e.name !== "AbortError") {
29181							console.error("Waveform load error:", e);
29182						}
29183					});
29184				}
29185			});
29186		}
29187	});
29188
29189	const handle_trim_audio = async (start, end) => {
29190		mode("");
29191
29192		const decodedData = waveform?.getDecodedData();
29193
29194		if (decodedData) {
29195			const trimmedBlob = await process_audio(decodedData, start, end, $$props.waveform_settings.sampleRate);
29196
29197			await dispatch_blob()([trimmedBlob], "change");
29198		}
29199
29200		$$props.onedit?.();
29201	};
29202
29203	async function load_audio(data) {
29204		stream_active = false;
29205
29206		if ($$props.waveform_options.show_recording_waveform) {
29207			waveform?.load(data);
29208		} else if (audio_player) {
29209			audio_player.src = data;
29210		}
29211	}
29212
29213	user_effect(() => {
29214		if (subtitles() && waveform) {
29215			if (get(subtitles_toggle)) {
29216				add_subtitles_to_waveform(waveform, subtitles());
29217			} else {
29218				hide_subtitles();
29219			}
29220		}
29221	});
29222
29223	function load_stream(value) {
29224		if (!value || !value.is_stream || !value.url) return;
29225
29226		if (Hls.isSupported() && !stream_active) {
29227			// Set config to start playback after 1 second of data received
29228			const hls = new Hls({
29229				maxBufferLength: 1,
29230				maxMaxBufferLength: 1,
29231				lowLatencyMode: true
29232			});
29233
29234			hls.loadSource(value.url);
29235			hls.attachMedia(audio_player);
29236
29237			hls.on(Hls.Events.MANIFEST_PARSED, function () {
29238				if ($$props.waveform_settings.autoplay) audio_player.play();
29239			});
29240
29241			hls.on(Hls.Events.ERROR, function (event, data) {
29242				console.error("HLS error:", event, data);
29243
29244				if (data.fatal) {
29245					switch (data.type) {
29246						case Hls.ErrorTypes.NETWORK_ERROR:
29247							console.error("Fatal network error encountered, trying to recover");
29248							hls.startLoad();
29249							break;
29250
29251						case Hls.ErrorTypes.MEDIA_ERROR:
29252							console.error("Fatal media error encountered, trying to recover");
29253							hls.recoverMediaError();
29254							break;
29255
29256						default:
29257							console.error("Fatal error, cannot recover");
29258							hls.destroy();
29259							break;
29260					}
29261				}
29262			});
29263
29264			stream_active = true;
29265		} else if (!stream_active) {
29266			audio_player.src = value.url;
29267
29268			if ($$props.waveform_settings.autoplay) audio_player.play();
29269
29270			stream_active = true;
29271		}
29272	}
29273
29274	user_effect(() => {
29275		if (audio_player && get(url) && get(waveform_ready) && get(url)) {
29276			load_audio(get(url));
29277		}
29278	});
29279
29280	user_effect(() => {
29281		if (audio_player && value()?.is_stream) {
29282			load_stream(value());
29283		}
29284	});
29285
29286	onMount(() => {
29287		create_waveform();
29288
29289		const handleKeydown = (e) => {
29290			if (!waveform || get(show_volume_slider)) return;
29291
29292			const is_focused_in_waveform = waveform_component_wrapper && waveform_component_wrapper.contains(document.activeElement);
29293
29294			if (!is_focused_in_waveform) return;
29295
29296			if (e.key === "ArrowRight" && mode() !== "edit") {
29297				skip_audio(waveform, 0.1);
29298			} else if (e.key === "ArrowLeft" && mode() !== "edit") {
29299				skip_audio(waveform, -0.1);
29300			}
29301		};
29302
29303		window.addEventListener("keydown", handleKeydown);
29304
29305		return () => {
29306			waveform?.destroy();
29307			window.removeEventListener("keydown", handleKeydown);
29308		};
29309	});
29310
29311	async function add_subtitles_to_waveform(wavesurfer, subtitle_data) {
29312		clear_subtitles();
29313
29314		try {
29315			let subtitles;
29316
29317			if (Array.isArray(subtitle_data)) {
29318				subtitles = subtitle_data;
29319			} else {
29320				const response = await fetch(subtitle_data);
29321				const subtitle_content = await response.text();
29322
29323				subtitles = parse_subtitles(subtitle_content);
29324			}
29325
29326			if (subtitles.length > 0) {
29327				let current_subtitle = "";
29328
29329				if (subtitle_container) {
29330					subtitle_container.style.display = "";
29331
29332					const audioProcessHandler = (time) => {
29333						const subtitle = subtitles.find((s) => time >= s.start && time <= s.end);
29334
29335						if (subtitle && subtitle.text !== current_subtitle) {
29336							current_subtitle = subtitle.text;
29337							subtitle_container.textContent = current_subtitle;
29338						} else if (!subtitle && current_subtitle !== "") {
29339							current_subtitle = "";
29340							subtitle_container.textContent = "";
29341						}
29342					};
29343
29344					wavesurfer.on("audioprocess", audioProcessHandler);
29345
29346					subtitle_event_handlers.push(() => {
29347						wavesurfer.un("audioprocess", audioProcessHandler);
29348					});
29349				}
29350			}
29351		} catch(error) {}
29352	}
29353
29354	function hide_subtitles() {
29355		if (subtitle_container) {
29356			subtitle_container.style.display = "none";
29357		}
29358	}
29359
29360	function clear_subtitles() {
29361		if (subtitle_container) {
29362			subtitle_container.textContent = "";
29363		}
29364
29365		subtitle_event_handlers.forEach((handler) => handler());
29366		subtitle_event_handlers = [];
29367	}
29368
29369	function parse_subtitles(subtitle_content) {
29370		const lines = subtitle_content.split("\n");
29371		const subtitles = [];
29372
29373		for (let i = 0; i < lines.length; i++) {
29374			const line = lines[i].trim();
29375
29376			if (line.includes(" --> ")) {
29377				const [start_time, end_time] = line.split(" --> ");
29378				const start = parse_time_to_seconds(start_time);
29379				const end = parse_time_to_seconds(end_time);
29380				let text = "";
29381
29382				for (let j = i + 1; j < lines.length && lines[j].trim() !== ""; j++) {
29383					if (text) text += " ";
29384
29385					text += lines[j].trim();
29386				}
29387
29388				if (text) {
29389					subtitles.push({ start, end, text });
29390				}
29391			}
29392		}
29393
29394		return subtitles;
29395	}
29396
29397	function parse_time_to_seconds(time_str) {
29398		const parts = time_str.split(":");
29399
29400		if (parts.length === 3) {
29401			const hours = parseInt(parts[0]);
29402			const minutes = parseInt(parts[1]);
29403			const seconds = parseFloat(parts[2]);
29404
29405			return hours * 3600 + minutes * 60 + seconds;
29406		}
29407
29408		return 0;
29409	}
29410
29411	var fragment = root$1();
29412	var audio = first_child(fragment);
29413	let classes;
29414
29415	bind_this(audio, ($$value) => audio_player = $$value, () => audio_player);
29416
29417	var node = sibling(audio, 2);
29418
29419	{
29420		var consequent = ($$anchor) => {
29421			Empty($$anchor, {
29422				size: 'small',
29423				children: ($$anchor, $$slotProps) => {
29424					Music($$anchor);
29425				},
29426				$$slots: { default: true }
29427			});
29428		};
29429
29430		var alternate = ($$anchor) => {
29431			var fragment_3 = comment();
29432			var node_1 = first_child(fragment_3);
29433
29434			{
29435				var consequent_2 = ($$anchor) => {
29436					var div = root_4$1();
29437					var div_1 = child(div);
29438					var div_2 = child(div_1);
29439					let styles;
29440
29441					bind_this(div_2, ($$value) => container = $$value, () => container);
29442					reset(div_1);
29443
29444					var div_3 = sibling(div_1, 2);
29445					var time_1 = child(div_3);
29446
29447					bind_this(time_1, ($$value) => timeRef = $$value, () => timeRef);
29448
29449					var div_4 = sibling(time_1, 2);
29450					var node_2 = child(div_4);
29451
29452					{
29453						var consequent_1 = ($$anchor) => {
29454							var time_2 = root_5();
29455							var text_1 = child(time_2, true);
29456
29457							reset(time_2);
29458							template_effect(($0) => set_text(text_1, $0), [() => format_time(get(trimDuration))]);
29459							append($$anchor, time_2);
29460						};
29461
29462						if_block(node_2, ($$render) => {
29463							if (mode() === "edit" && get(trimDuration) > 0) $$render(consequent_1);
29464						});
29465					}
29466
29467					var time_3 = sibling(node_2, 2);
29468
29469					bind_this(time_3, ($$value) => durationRef = $$value, () => durationRef);
29470					reset(div_4);
29471					reset(div_3);
29472
29473					var div_5 = sibling(div_3, 2);
29474
29475					bind_this(div_5, ($$value) => subtitle_container = $$value, () => subtitle_container);
29476
29477					var node_3 = sibling(div_5, 2);
29478
29479					{
29480						let $0 = user_derived(() => subtitles() !== null);
29481
29482						WaveformControls(node_3, {
29483							get container() {
29484								return container;
29485							},
29486
29487							get waveform() {
29488								return waveform;
29489							},
29490
29491							get playing() {
29492								return get(playing);
29493							},
29494
29495							get audio_duration() {
29496								return get(audio_duration);
29497							},
29498
29499							get i18n() {
29500								return $$props.i18n;
29501							},
29502
29503							get interactive() {
29504								return interactive();
29505							},
29506							handle_trim_audio,
29507							get show_redo() {
29508								return interactive();
29509							},
29510
29511							get handle_reset_value() {
29512								return handle_reset_value();
29513							},
29514
29515							get waveform_options() {
29516								return $$props.waveform_options;
29517							},
29518
29519							get trim_region_settings() {
29520								return trim_region_settings();
29521							},
29522
29523							get editable() {
29524								return editable();
29525							},
29526
29527							get show_subtitles() {
29528								return get($0);
29529							},
29530
29531							get mode() {
29532								return mode();
29533							},
29534
29535							set mode($$value) {
29536								mode($$value);
29537							},
29538
29539							get trimDuration() {
29540								return get(trimDuration);
29541							},
29542
29543							set trimDuration($$value) {
29544								set(trimDuration, $$value, true);
29545							},
29546
29547							get show_volume_slider() {
29548								return get(show_volume_slider);
29549							},
29550
29551							set show_volume_slider($$value) {
29552								set(show_volume_slider, $$value, true);
29553							},
29554
29555							get subtitles_toggle() {
29556								return get(subtitles_toggle);
29557							},
29558
29559							set subtitles_toggle($$value) {
29560								set(subtitles_toggle, $$value, true);
29561							}
29562						});
29563					}
29564
29565					reset(div);
29566					bind_this(div, ($$value) => waveform_component_wrapper = $$value, () => waveform_component_wrapper);
29567
29568					template_effect(() => {
29569						set_attribute(div, 'data-testid', $$props.label ? "waveform-" + $$props.label : "unlabelled-audio");
29570						styles = set_style(div_2, '', styles, { height: container ? null : "58px" });
29571					});
29572
29573					append($$anchor, div);
29574				};
29575
29576				if_block(
29577					node_1,
29578					($$render) => {
29579						if (get(use_waveform)) $$render(consequent_2);
29580					},
29581					true
29582				);
29583			}
29584
29585			append($$anchor, fragment_3);
29586		};
29587
29588		if_block(node, ($$render) => {
29589			if (value() === null) $$render(consequent); else $$render(alternate, false);
29590		});
29591	}
29592
29593	template_effect(() => {
29594		classes = set_class(audio, 1, 'standard-player svelte-1ffmt2w', null, classes, { hidden: get(use_waveform) });
29595		audio.autoplay = $$props.waveform_settings.autoplay;
29596	});
29597
29598	event('load', audio, function (...$$args) {
29599		$$props.onload?.apply(this, $$args);
29600	});
29601
29602	event('ended', audio, () => $$props.onstop?.());
29603	event('play', audio, () => $$props.onplay?.());
29604	append($$anchor, fragment);
29605	pop();
29606}
29607
29608var root_4 = from_html(`<!> <!>`, 1);
29609var root_3 = from_html(`<!> <!>`, 1);
29610var root = from_html(`<!> <!>`, 1);
29611
29612function StaticAudio($$anchor, $$props) {
29613	push$1($$props, true);
29614
29615	let value = prop($$props, 'value', 3, null),
29616		subtitles = prop($$props, 'subtitles', 3, null),
29617		show_label = prop($$props, 'show_label', 3, true),
29618		buttons = prop($$props, 'buttons', 19, () => []),
29619		on_custom_button_click = prop($$props, 'on_custom_button_click', 3, null),
29620		waveform_settings = prop($$props, 'waveform_settings', 19, () => ({})),
29621		waveform_options = prop($$props, 'waveform_options', 19, () => ({ show_recording_waveform: true })),
29622		editable = prop($$props, 'editable', 3, true),
29623		display_icon_button_wrapper_top_corner = prop($$props, 'display_icon_button_wrapper_top_corner', 3, false),
29624		minimal = prop($$props, 'minimal', 3, false),
29625		playback_position = prop($$props, 'playback_position', 15);
29626
29627	user_effect(() => {
29628		if (value()) {
29629			$$props.onchange?.(value());
29630		}
29631	});
29632
29633	var fragment = root();
29634	var node = first_child(fragment);
29635
29636	{
29637		let $0 = user_derived(() => $$props.label || $$props.i18n("audio.audio"));
29638
29639		BlockLabel(node, {
29640			get show_label() {
29641				return show_label();
29642			},
29643
29644			get Icon() {
29645				return Music;
29646			},
29647			float: false,
29648			get label() {
29649				return get($0);
29650			}
29651		});
29652	}
29653
29654	var node_1 = sibling(node, 2);
29655
29656	{
29657		var consequent_3 = ($$anchor) => {
29658			var fragment_1 = comment();
29659			var node_2 = first_child(fragment_1);
29660
29661			{
29662				var consequent = ($$anchor) => {
29663					{
29664						let $0 = user_derived(() => $$props.label || $$props.i18n("audio.audio"));
29665
29666						MinimalAudioPlayer($$anchor, {
29667							get value() {
29668								return value();
29669							},
29670
29671							get label() {
29672								return get($0);
29673							},
29674
29675							get loop() {
29676								return $$props.loop;
29677							}
29678						});
29679					}
29680				};
29681
29682				var alternate = ($$anchor) => {
29683					var fragment_3 = root_3();
29684					var node_3 = first_child(fragment_3);
29685
29686					IconButtonWrapper(node_3, {
29687						get display_top_corner() {
29688							return display_icon_button_wrapper_top_corner();
29689						},
29690
29691						get buttons() {
29692							return buttons();
29693						},
29694
29695						get on_custom_button_click() {
29696							return on_custom_button_click();
29697						},
29698
29699						children: ($$anchor, $$slotProps) => {
29700							var fragment_4 = root_4();
29701							var node_4 = first_child(fragment_4);
29702
29703							{
29704								var consequent_1 = ($$anchor) => {
29705									{
29706										let $0 = user_derived(() => value().is_stream
29707											? value().url?.replace("playlist.m3u8", "playlist-file")
29708											: value().url);
29709
29710										let $1 = user_derived(() => value().orig_name || value().path);
29711
29712										DownloadLink($$anchor, {
29713											get href() {
29714												return get($0);
29715											},
29716
29717											get download() {
29718												return get($1);
29719											},
29720
29721											children: ($$anchor, $$slotProps) => {
29722												{
29723													let $0 = user_derived(() => $$props.i18n("common.download"));
29724
29725													IconButton($$anchor, {
29726														get Icon() {
29727															return Download;
29728														},
29729
29730														get label() {
29731															return get($0);
29732														}
29733													});
29734												}
29735											},
29736											$$slots: { default: true }
29737										});
29738									}
29739								};
29740
29741								if_block(node_4, ($$render) => {
29742									if (buttons().some((btn) => typeof btn === "string" && btn === "download")) $$render(consequent_1);
29743								});
29744							}
29745
29746							var node_5 = sibling(node_4, 2);
29747
29748							{
29749								var consequent_2 = ($$anchor) => {
29750									ShareButton($$anchor, {
29751										get i18n() {
29752											return $$props.i18n;
29753										},
29754
29755										get onerror() {
29756											return $$props.onerror;
29757										},
29758
29759										get onshare() {
29760											return $$props.onshare;
29761										},
29762
29763										formatter: async (fileData) => {
29764											if (!fileData || !fileData.url) return "";
29765
29766											let url = await uploadToHuggingFace(fileData.url);
29767
29768											return `<audio controls src="${url}"></audio>`;
29769										},
29770
29771										get value() {
29772											return value();
29773										}
29774									});
29775								};
29776
29777								if_block(node_5, ($$render) => {
29778									if (buttons().some((btn) => typeof btn === "string" && btn === "share")) $$render(consequent_2);
29779								});
29780							}
29781
29782							append($$anchor, fragment_4);
29783						},
29784						$$slots: { default: true }
29785					});
29786
29787					var node_6 = sibling(node_3, 2);
29788
29789					{
29790						let $0 = user_derived(() => Array.isArray(subtitles()) ? subtitles() : subtitles()?.url);
29791
29792						AudioPlayer(node_6, {
29793							get value() {
29794								return value();
29795							},
29796
29797							get subtitles() {
29798								return get($0);
29799							},
29800
29801							get label() {
29802								return $$props.label;
29803							},
29804
29805							get i18n() {
29806								return $$props.i18n;
29807							},
29808
29809							get waveform_settings() {
29810								return waveform_settings();
29811							},
29812
29813							get waveform_options() {
29814								return waveform_options();
29815							},
29816
29817							get editable() {
29818								return editable();
29819							},
29820
29821							get loop() {
29822								return $$props.loop;
29823							},
29824
29825							get onpause() {
29826								return $$props.onpause;
29827							},
29828
29829							get onplay() {
29830								return $$props.onplay;
29831							},
29832
29833							get onstop() {
29834								return $$props.onstop;
29835							},
29836							onload: () => {},
29837							get playback_position() {
29838								return playback_position();
29839							},
29840
29841							set playback_position($$value) {
29842								playback_position($$value);
29843							}
29844						});
29845					}
29846
29847					append($$anchor, fragment_3);
29848				};
29849
29850				if_block(node_2, ($$render) => {
29851					if (minimal()) $$render(consequent); else $$render(alternate, false);
29852				});
29853			}
29854
29855			append($$anchor, fragment_1);
29856		};
29857
29858		var alternate_1 = ($$anchor) => {
29859			Empty($$anchor, {
29860				size: 'small',
29861				children: ($$anchor, $$slotProps) => {
29862					Music($$anchor);
29863				},
29864				$$slots: { default: true }
29865			});
29866		};
29867
29868		if_block(node_1, ($$render) => {
29869			if (value() !== null) $$render(consequent_3); else $$render(alternate_1, false);
29870		});
29871	}
29872
29873	append($$anchor, fragment);
29874	pop();
29875}
29876
29877const StaticAudio$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
29878	__proto__: null,
29879	default: StaticAudio
29880}, Symbol.toStringTag, { value: 'Module' }));
29881
29882export { AudioPlayer as A, StaticAudio as S, WaveformControls as W, StaticAudio$1 as a };
29883//# sourceMappingURL=StaticAudio-CWETG3mI.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.