PageSourceSearch

https://pro-demo.safehs.ai/_next/static/chunks/e217e3ef-4a7896cbb909a77c.js

js safehs.ai collected 2026-10-05 14:17:19 UTC 1,307,364 bytes, 36,721 lines download raw bytes

1"use strict";
2(self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([[6306],{
3
4/***/ 28276:
5/***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) {
6
7/* harmony export */ __webpack_require__.d(__webpack_exports__, {
8/* harmony export */   ZP: function() { return /* binding */ Hls; }
9/* harmony export */ });
10/* unused harmony exports AbrController, AttrList, AudioStreamController, AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, Cues, DateRange, EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, FetchLoader, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, M3U8Parser, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, SubtitleTrackController, TimelineController, XhrLoader, fetchSupported, getMediaSource, isMSESupported, isSupported, requestMediaKeySystemAccess */
vendor: 15,348 bytes, lines 10-207
10
11// https://caniuse.com/mdn-javascript_builtins_number_isfinite
12const isFiniteNumber = Number.isFinite || function (value) {
13  return typeof value === 'number' && isFinite(value);
14};
15
16// https://caniuse.com/mdn-javascript_builtins_number_issafeinteger
17const isSafeInteger = Number.isSafeInteger || function (value) {
18  return typeof value === 'number' && Math.abs(value) <= MAX_SAFE_INTEGER;
19};
20const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
21
22let ErrorTypes = /*#__PURE__*/function (ErrorTypes) {
23  // Identifier for a network error (loading error / timeout ...)
24  ErrorTypes["NETWORK_ERROR"] = "networkError";
25  // Identifier for a media Error (video/parsing/mediasource error)
26  ErrorTypes["MEDIA_ERROR"] = "mediaError";
27  // EME (encrypted media extensions) errors
28  ErrorTypes["KEY_SYSTEM_ERROR"] = "keySystemError";
29  // Identifier for a mux Error (demuxing/remuxing)
30  ErrorTypes["MUX_ERROR"] = "muxError";
31  // Identifier for all other errors
32  ErrorTypes["OTHER_ERROR"] = "otherError";
33  return ErrorTypes;
34}({});
35let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
36  ErrorDetails["KEY_SYSTEM_NO_KEYS"] = "keySystemNoKeys";
37  ErrorDetails["KEY_SYSTEM_NO_ACCESS"] = "keySystemNoAccess";
38  ErrorDetails["KEY_SYSTEM_NO_SESSION"] = "keySystemNoSession";
39  ErrorDetails["KEY_SYSTEM_NO_CONFIGURED_LICENSE"] = "keySystemNoConfiguredLicense";
40  ErrorDetails["KEY_SYSTEM_LICENSE_REQUEST_FAILED"] = "keySystemLicenseRequestFailed";
41  ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED"] = "keySystemServerCertificateRequestFailed";
42  ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED"] = "keySystemServerCertificateUpdateFailed";
43  ErrorDetails["KEY_SYSTEM_SESSION_UPDATE_FAILED"] = "keySystemSessionUpdateFailed";
44  ErrorDetails["KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED"] = "keySystemStatusOutputRestricted";
45  ErrorDetails["KEY_SYSTEM_STATUS_INTERNAL_ERROR"] = "keySystemStatusInternalError";
46  ErrorDetails["KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR"] = "keySystemDestroyMediaKeysError";
47  ErrorDetails["KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR"] = "keySystemDestroyCloseSessionError";
48  ErrorDetails["KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR"] = "keySystemDestroyRemoveSessionError";
49  // Identifier for a manifest load error - data: { url : faulty URL, response : { code: error code, text: error text }}
50  ErrorDetails["MANIFEST_LOAD_ERROR"] = "manifestLoadError";
51  // Identifier for a manifest load timeout - data: { url : faulty URL, response : { code: error code, text: error text }}
52  ErrorDetails["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut";
53  // Identifier for a manifest parsing error - data: { url : faulty URL, reason : error reason}
54  ErrorDetails["MANIFEST_PARSING_ERROR"] = "manifestParsingError";
55  // Identifier for a manifest with only incompatible codecs error - data: { url : faulty URL, reason : error reason}
56  ErrorDetails["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError";
57  // Identifier for a level which contains no fragments - data: { url: faulty URL, reason: "no fragments found in level", level: index of the bad level }
58  ErrorDetails["LEVEL_EMPTY_ERROR"] = "levelEmptyError";
59  // Identifier for a level load error - data: { url : faulty URL, response : { code: error code, text: error text }}
60  ErrorDetails["LEVEL_LOAD_ERROR"] = "levelLoadError";
61  // Identifier for a level load timeout - data: { url : faulty URL, response : { code: error code, text: error text }}
62  ErrorDetails["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut";
63  // Identifier for a level parse error - data: { url : faulty URL, error: Error, reason: error message }
64  ErrorDetails["LEVEL_PARSING_ERROR"] = "levelParsingError";
65  // Identifier for a level switch error - data: { level : faulty level Id, event : error description}
66  ErrorDetails["LEVEL_SWITCH_ERROR"] = "levelSwitchError";
67  // Identifier for an audio track load error - data: { url : faulty URL, response : { code: error code, text: error text }}
68  ErrorDetails["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError";
69  // Identifier for an audio track load timeout - data: { url : faulty URL, response : { code: error code, text: error text }}
70  ErrorDetails["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut";
71  // Identifier for a subtitle track load error - data: { url : faulty URL, response : { code: error code, text: error text }}
72  ErrorDetails["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError";
73  // Identifier for a subtitle track load timeout - data: { url : faulty URL, response : { code: error code, text: error text }}
74  ErrorDetails["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut";
75  // Identifier for fragment load error - data: { frag : fragment object, response : { code: error code, text: error text }}
76  ErrorDetails["FRAG_LOAD_ERROR"] = "fragLoadError";
77  // Identifier for fragment load timeout error - data: { frag : fragment object}
78  ErrorDetails["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut";
79  // Identifier for a fragment decryption error event - data: {id : demuxer Id,frag: fragment object, reason : parsing error description }
80  ErrorDetails["FRAG_DECRYPT_ERROR"] = "fragDecryptError";
81  // Identifier for a fragment parsing error event - data: { id : demuxer Id, reason : parsing error description }
82  // will be renamed DEMUX_PARSING_ERROR and switched to MUX_ERROR in the next major release
83  ErrorDetails["FRAG_PARSING_ERROR"] = "fragParsingError";
84  // Identifier for a fragment or part load skipped because of a GAP tag or attribute
85  ErrorDetails["FRAG_GAP"] = "fragGap";
86  // Identifier for a remux alloc error event - data: { id : demuxer Id, frag : fragment object, bytes : nb of bytes on which allocation failed , reason : error text }
87  ErrorDetails["REMUX_ALLOC_ERROR"] = "remuxAllocError";
88  // Identifier for decrypt key load error - data: { frag : fragment object, response : { code: error code, text: error text }}
89  ErrorDetails["KEY_LOAD_ERROR"] = "keyLoadError";
90  // Identifier for decrypt key load timeout error - data: { frag : fragment object}
91  ErrorDetails["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut";
92  // Triggered when an exception occurs while adding a sourceBuffer to MediaSource - data : { error : exception , mimeType : mimeType }
93  ErrorDetails["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError";
94  // Triggered when source buffer(s) could not be created using level (manifest CODECS attribute), parsed media, or best guess codec(s) - data: { reason : error reason }
95  ErrorDetails["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError";
96  // Identifier for a buffer append error - data: append error description
97  ErrorDetails["BUFFER_APPEND_ERROR"] = "bufferAppendError";
98  // Identifier for a buffer appending error event - data: appending error description
99  ErrorDetails["BUFFER_APPENDING_ERROR"] = "bufferAppendingError";
100  // Identifier for a buffer stalled error event
101  ErrorDetails["BUFFER_STALLED_ERROR"] = "bufferStalledError";
102  // Identifier for a buffer full event
103  ErrorDetails["BUFFER_FULL_ERROR"] = "bufferFullError";
104  // Identifier for a buffer seek over hole event
105  ErrorDetails["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole";
106  // Identifier for a buffer nudge on stall (playback is stuck although currentTime is in a buffered area)
107  ErrorDetails["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall";
108  // Identifier for a Interstitial Asset List load error - data: { url: faulty URL, response: { code: error code, text: error text } }
109  ErrorDetails["ASSET_LIST_LOAD_ERROR"] = "assetListLoadError";
110  // Identifier for a Interstitial Asset List load timeout - data: { url: faulty URL, response: { code: error code, text: error text } }
111  ErrorDetails["ASSET_LIST_LOAD_TIMEOUT"] = "assetListLoadTimeout";
112  // Identifier for a Interstitial Asset List parsing error - data: { url : faulty URL, reason : error reason, response : { code: error code, text: error text }}
113  ErrorDetails["ASSET_LIST_PARSING_ERROR"] = "assetListParsingError";
114  // Identifier for a Interstitial Asset List parsing error - data: { url : faulty URL, reason : error reason, response : { code: error code, text: error text }}
115  ErrorDetails["INTERSTITIAL_ASSET_ITEM_ERROR"] = "interstitialAssetItemError";
116  // Identifier for an internal exception happening inside hls.js while handling an event
117  ErrorDetails["INTERNAL_EXCEPTION"] = "internalException";
118  // Identifier for an internal call to abort a loader
119  ErrorDetails["INTERNAL_ABORTED"] = "aborted";
120  // Triggered when attachMedia fails
121  ErrorDetails["ATTACH_MEDIA_ERROR"] = "attachMediaError";
122  // Uncategorized error
123  ErrorDetails["UNKNOWN"] = "unknown";
124  return ErrorDetails;
125}({});
126
127let Events = /*#__PURE__*/function (Events) {
128  // Fired before MediaSource is attaching to media element
129  Events["MEDIA_ATTACHING"] = "hlsMediaAttaching";
130  // Fired when MediaSource has been successfully attached to media element
131  Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
132  // Fired before detaching MediaSource from media element
133  Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
134  // Fired when MediaSource has been detached from media element
135  Events["MEDIA_DETACHED"] = "hlsMediaDetached";
136  // Fired when HTMLMediaElement dispatches "ended" event, or stalls at end of VOD program
137  Events["MEDIA_ENDED"] = "hlsMediaEnded";
138  // Fired after playback stall is resolved with playing, seeked, or ended event following BUFFER_STALLED_ERROR
139  Events["STALL_RESOLVED"] = "hlsStallResolved";
140  // Fired when the buffer is going to be reset
141  Events["BUFFER_RESET"] = "hlsBufferReset";
142  // Fired when we know about the codecs that we need buffers for to push into - data: {tracks : { container, codec, levelCodec, initSegment, metadata }}
143  Events["BUFFER_CODECS"] = "hlsBufferCodecs";
144  // fired when sourcebuffers have been created - data: { tracks : tracks }
145  Events["BUFFER_CREATED"] = "hlsBufferCreated";
146  // fired when we append a segment to the buffer - data: { segment: segment object }
147  Events["BUFFER_APPENDING"] = "hlsBufferAppending";
148  // fired when we are done with appending a media segment to the buffer - data : { parent : segment parent that triggered BUFFER_APPENDING, pending : nb of segments waiting for appending for this segment parent}
149  Events["BUFFER_APPENDED"] = "hlsBufferAppended";
150  // fired when the stream is finished and we want to notify the media buffer that there will be no more data - data: { }
151  Events["BUFFER_EOS"] = "hlsBufferEos";
152  // fired when all buffers are full to the end of the program, after calling MediaSource.endOfStream() (unless restricted)
153  Events["BUFFERED_TO_END"] = "hlsBufferedToEnd";
154  // fired when the media buffer should be flushed - data { startOffset, endOffset }
155  Events["BUFFER_FLUSHING"] = "hlsBufferFlushing";
156  // fired when the media buffer has been flushed - data: { }
157  Events["BUFFER_FLUSHED"] = "hlsBufferFlushed";
158  // fired to signal that a manifest loading starts - data: { url : manifestURL}
159  Events["MANIFEST_LOADING"] = "hlsManifestLoading";
160  // fired after manifest has been loaded - data: { levels : [available quality levels], audioTracks : [ available audio tracks ], url : manifestURL, stats : LoaderStats }
161  Events["MANIFEST_LOADED"] = "hlsManifestLoaded";
162  // fired after manifest has been parsed - data: { levels : [available quality levels], firstLevel : index of first quality level appearing in Manifest}
163  Events["MANIFEST_PARSED"] = "hlsManifestParsed";
164  // fired when a level switch is requested - data: { level : id of new level }
165  Events["LEVEL_SWITCHING"] = "hlsLevelSwitching";
166  // fired when a level switch is effective - data: { level : id of new level }
167  Events["LEVEL_SWITCHED"] = "hlsLevelSwitched";
168  // fired when a level playlist loading starts - data: { url : level URL, level : id of level being loaded}
169  Events["LEVEL_LOADING"] = "hlsLevelLoading";
170  // fired when a level playlist loading finishes - data: { details : levelDetails object, level : id of loaded level, stats : LoaderStats }
171  Events["LEVEL_LOADED"] = "hlsLevelLoaded";
172  // fired when a level's details have been updated based on previous details, after it has been loaded - data: { details : levelDetails object, level : id of updated level }
173  Events["LEVEL_UPDATED"] = "hlsLevelUpdated";
174  // fired when a level's PTS information has been updated after parsing a fragment - data: { details : levelDetails object, level : id of updated level, drift: PTS drift observed when parsing last fragment }
175  Events["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated";
176  // fired to notify that levels have changed after removing a level - data: { levels : [available quality levels] }
177  Events["LEVELS_UPDATED"] = "hlsLevelsUpdated";
178  // fired to notify that audio track lists has been updated - data: { audioTracks : audioTracks }
179  Events["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated";
180  // fired when an audio track switching is requested - data: { id : audio track id }
181  Events["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching";
182  // fired when an audio track switch actually occurs - data: { id : audio track id }
183  Events["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched";
184  // fired when an audio track loading starts - data: { url : audio track URL, id : audio track id }
185  Events["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading";
186  // fired when an audio track loading finishes - data: { details : levelDetails object, id : audio track id, stats : LoaderStats }
187  Events["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded";
188  // fired when an audio tracks's details have been updated based on previous details, after it has been loaded - data: { details : levelDetails object, id : track id }
189  Events["AUDIO_TRACK_UPDATED"] = "hlsAudioTrackUpdated";
190  // fired to notify that subtitle track lists has been updated - data: { subtitleTracks : subtitleTracks }
191  Events["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated";
192  // fired to notify that subtitle tracks were cleared as a result of stopping the media
193  Events["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared";
194  // fired when an subtitle track switch occurs - data: { id : subtitle track id }
195  Events["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch";
196  // fired when a subtitle track loading starts - data: { url : subtitle track URL, id : subtitle track id }
197  Events["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading";
198  // fired when a subtitle track loading finishes - data: { details : levelDetails object, id : subtitle track id, stats : LoaderStats }
199  Events["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded";
200  // fired when a subtitle  racks's details have been updated based on previous details, after it has been loaded - data: { details : levelDetails object, id : track id }
201  Events["SUBTITLE_TRACK_UPDATED"] = "hlsSubtitleTrackUpdated";
202  // fired when a subtitle fragment has been processed - data: { success : boolean, frag : the processed frag }
203  Events["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed";
204  // fired when a set of VTTCues to be managed externally has been parsed - data: { type: string, track: string, cues: [ VTTCue ] }
205  Events["CUES_PARSED"] = "hlsCuesParsed";
206  // fired when a text track to be managed externally is found - data: { tracks: [ { label: string, kind: string, default: boolean } ] }
207  Events["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound";
vendor: 12,581 bytes, lines 208-508
208  // fired when the first timestamp is found - data: { id : demuxer id, initPTS: initPTS, timescale: timescale, frag : fragment object }
209  Events["INIT_PTS_FOUND"] = "hlsInitPtsFound";
210  // fired when a fragment loading starts - data: { frag : fragment object }
211  Events["FRAG_LOADING"] = "hlsFragLoading";
212  // fired when a fragment loading is progressing - data: { frag : fragment object, { trequest, tfirst, loaded } }
213  // FRAG_LOAD_PROGRESS = 'hlsFragLoadProgress',
214  // Identifier for fragment load aborting for emergency switch down - data: { frag : fragment object }
215  Events["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted";
216  // fired when a fragment loading is completed - data: { frag : fragment object, payload : fragment payload, stats : LoaderStats }
217  Events["FRAG_LOADED"] = "hlsFragLoaded";
218  // fired when a fragment has finished decrypting - data: { id : demuxer id, frag: fragment object, payload : fragment payload, stats : { tstart, tdecrypt } }
219  Events["FRAG_DECRYPTED"] = "hlsFragDecrypted";
220  // fired when Init Segment has been extracted from fragment - data: { id : demuxer id, frag: fragment object, moov : moov MP4 box, codecs : codecs found while parsing fragment }
221  Events["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment";
222  // fired when parsing sei text is completed - data: { id : demuxer id, frag: fragment object, samples : [ sei samples pes ] }
223  Events["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata";
224  // fired when parsing id3 is completed - data: { id : demuxer id, frag: fragment object, samples : [ id3 samples pes ] }
225  Events["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata";
226  // fired when data have been extracted from fragment - data: { id : demuxer id, frag: fragment object, data1 : moof MP4 box or TS fragments, data2 : mdat MP4 box or null}
227  // FRAG_PARSING_DATA = 'hlsFragParsingData',
228  // fired when fragment parsing is completed - data: { id : demuxer id, frag: fragment object }
229  Events["FRAG_PARSED"] = "hlsFragParsed";
230  // fired when fragment remuxed MP4 boxes have all been appended into SourceBuffer - data: { id : demuxer id, frag : fragment object, stats : LoaderStats }
231  Events["FRAG_BUFFERED"] = "hlsFragBuffered";
232  // fired when fragment matching with current media position is changing - data : { id : demuxer id, frag : fragment object }
233  Events["FRAG_CHANGED"] = "hlsFragChanged";
234  // Identifier for a FPS drop event - data: { currentDropped, currentDecoded, totalDroppedFrames }
235  Events["FPS_DROP"] = "hlsFpsDrop";
236  // triggered when FPS drop triggers auto level capping - data: { level, droppedLevel }
237  Events["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping";
238  // triggered when maxAutoLevel changes - data { autoLevelCapping, levels, maxAutoLevel, minAutoLevel, maxHdcpLevel }
239  Events["MAX_AUTO_LEVEL_UPDATED"] = "hlsMaxAutoLevelUpdated";
240  // Identifier for an error event - data: { type : error type, details : error details, fatal : if true, hls.js cannot/will not try to recover, if false, hls.js will try to recover,other error specific data }
241  Events["ERROR"] = "hlsError";
242  // fired when hls.js instance starts destroying. Different from MEDIA_DETACHED as one could want to detach and reattach a media to the instance of hls.js to handle mid-rolls for example - data: { }
243  Events["DESTROYING"] = "hlsDestroying";
244  // fired when a decrypt key loading starts - data: { frag : fragment object }
245  Events["KEY_LOADING"] = "hlsKeyLoading";
246  // fired when a decrypt key loading is completed - data: { frag : fragment object, keyInfo : KeyLoaderInfo }
247  Events["KEY_LOADED"] = "hlsKeyLoaded";
248  // deprecated; please use BACK_BUFFER_REACHED - data : { bufferEnd: number }
249  Events["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached";
250  // fired when the back buffer is reached as defined by the backBufferLength config option - data : { bufferEnd: number }
251  Events["BACK_BUFFER_REACHED"] = "hlsBackBufferReached";
252  // fired after steering manifest has been loaded - data: { steeringManifest: SteeringManifest object, url: steering manifest URL }
253  Events["STEERING_MANIFEST_LOADED"] = "hlsSteeringManifestLoaded";
254  // fired when asset list has begun loading
255  Events["ASSET_LIST_LOADING"] = "hlsAssetListLoading";
256  // fired when a valid asset list is loaded
257  Events["ASSET_LIST_LOADED"] = "hlsAssetListLoaded";
258  // fired when the list of Interstitial Events and Interstitial Schedule is updated
259  Events["INTERSTITIALS_UPDATED"] = "hlsInterstitialsUpdated";
260  // fired when the buffer reaches an Interstitial Schedule boundary (both Primary segments and Interstitial Assets)
261  Events["INTERSTITIALS_BUFFERED_TO_BOUNDARY"] = "hlsInterstitialsBufferedToBoundary";
262  // fired when a player instance for an Interstitial Asset has been created
263  Events["INTERSTITIAL_ASSET_PLAYER_CREATED"] = "hlsInterstitialAssetPlayerCreated";
264  // Interstitial playback started
265  Events["INTERSTITIAL_STARTED"] = "hlsInterstitialStarted";
266  // InterstitialAsset playback started
267  Events["INTERSTITIAL_ASSET_STARTED"] = "hlsInterstitialAssetStarted";
268  // InterstitialAsset playback ended
269  Events["INTERSTITIAL_ASSET_ENDED"] = "hlsInterstitialAssetEnded";
270  // InterstitialAsset playback errored
271  Events["INTERSTITIAL_ASSET_ERROR"] = "hlsInterstitialAssetError";
272  // Interstitial playback ended
273  Events["INTERSTITIAL_ENDED"] = "hlsInterstitialEnded";
274  // Interstitial schedule resumed primary playback
275  Events["INTERSTITIALS_PRIMARY_RESUMED"] = "hlsInterstitialsPrimaryResumed";
276  // Interstitial players dispatch this event when playout limit is reached
277  Events["PLAYOUT_LIMIT_REACHED"] = "hlsPlayoutLimitReached";
278  // Event DateRange cue "enter" event dispatched
279  Events["EVENT_CUE_ENTER"] = "hlsEventCueEnter";
280  return Events;
281}({});
282
283/**
284 * Defines each Event type and payload by Event name. Used in {@link hls.js#HlsEventEmitter} to strongly type the event listener API.
285 */
286
287var PlaylistContextType = {
288  MANIFEST: "manifest",
289  LEVEL: "level",
290  AUDIO_TRACK: "audioTrack",
291  SUBTITLE_TRACK: "subtitleTrack"
292};
293var PlaylistLevelType = {
294  MAIN: "main",
295  AUDIO: "audio",
296  SUBTITLE: "subtitle"
297};
298
299/*
300 * compute an Exponential Weighted moving average
301 * - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
302 *  - heavily inspired from shaka-player
303 */
304
305class EWMA {
306  //  About half of the estimated value will be from the last |halfLife| samples by weight.
307  constructor(halfLife, estimate = 0, weight = 0) {
308    this.halfLife = void 0;
309    this.alpha_ = void 0;
310    this.estimate_ = void 0;
311    this.totalWeight_ = void 0;
312    this.halfLife = halfLife;
313    // Larger values of alpha expire historical data more slowly.
314    this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0;
315    this.estimate_ = estimate;
316    this.totalWeight_ = weight;
317  }
318  sample(weight, value) {
319    const adjAlpha = Math.pow(this.alpha_, weight);
320    this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_;
321    this.totalWeight_ += weight;
322  }
323  getTotalWeight() {
324    return this.totalWeight_;
325  }
326  getEstimate() {
327    if (this.alpha_) {
328      const zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_);
329      if (zeroFactor) {
330        return this.estimate_ / zeroFactor;
331      }
332    }
333    return this.estimate_;
334  }
335}
336
337/*
338 * EWMA Bandwidth Estimator
339 *  - heavily inspired from shaka-player
340 * Tracks bandwidth samples and estimates available bandwidth.
341 * Based on the minimum of two exponentially-weighted moving averages with
342 * different half-lives.
343 */
344
345class EwmaBandWidthEstimator {
346  constructor(slow, fast, defaultEstimate, defaultTTFB = 100) {
347    this.defaultEstimate_ = void 0;
348    this.minWeight_ = void 0;
349    this.minDelayMs_ = void 0;
350    this.slow_ = void 0;
351    this.fast_ = void 0;
352    this.defaultTTFB_ = void 0;
353    this.ttfb_ = void 0;
354    this.defaultEstimate_ = defaultEstimate;
355    this.minWeight_ = 0.001;
356    this.minDelayMs_ = 50;
357    this.slow_ = new EWMA(slow);
358    this.fast_ = new EWMA(fast);
359    this.defaultTTFB_ = defaultTTFB;
360    this.ttfb_ = new EWMA(slow);
361  }
362  update(slow, fast) {
363    const {
364      slow_,
365      fast_,
366      ttfb_
367    } = this;
368    if (slow_.halfLife !== slow) {
369      this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight());
370    }
371    if (fast_.halfLife !== fast) {
372      this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight());
373    }
374    if (ttfb_.halfLife !== slow) {
375      this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight());
376    }
377  }
378  sample(durationMs, numBytes) {
379    durationMs = Math.max(durationMs, this.minDelayMs_);
380    const numBits = 8 * numBytes;
381    // weight is duration in seconds
382    const durationS = durationMs / 1000;
383    // value is bandwidth in bits/s
384    const bandwidthInBps = numBits / durationS;
385    this.fast_.sample(durationS, bandwidthInBps);
386    this.slow_.sample(durationS, bandwidthInBps);
387  }
388  sampleTTFB(ttfb) {
389    // weight is frequency curve applied to TTFB in seconds
390    // (longer times have less weight with expected input under 1 second)
391    const seconds = ttfb / 1000;
392    const weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2);
393    this.ttfb_.sample(weight, Math.max(ttfb, 5));
394  }
395  canEstimate() {
396    return this.fast_.getTotalWeight() >= this.minWeight_;
397  }
398  getEstimate() {
399    if (this.canEstimate()) {
400      // console.log('slow estimate:'+ Math.round(this.slow_.getEstimate()));
401      // console.log('fast estimate:'+ Math.round(this.fast_.getEstimate()));
402      // Take the minimum of these two estimates.  This should have the effect of
403      // adapting down quickly, but up more slowly.
404      return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate());
405    } else {
406      return this.defaultEstimate_;
407    }
408  }
409  getEstimateTTFB() {
410    if (this.ttfb_.getTotalWeight() >= this.minWeight_) {
411      return this.ttfb_.getEstimate();
412    } else {
413      return this.defaultTTFB_;
414    }
415  }
416  get defaultEstimate() {
417    return this.defaultEstimate_;
418  }
419  destroy() {}
420}
421
422function _defineProperty(e, r, t) {
423  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
424    value: t,
425    enumerable: true,
426    configurable: true,
427    writable: true
428  }) : e[r] = t, e;
429}
430function _extends() {
431  return _extends = Object.assign ? Object.assign.bind() : function (n) {
432    for (var e = 1; e < arguments.length; e++) {
433      var t = arguments[e];
434      for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
435    }
436    return n;
437  }, _extends.apply(null, arguments);
438}
439function ownKeys(e, r) {
440  var t = Object.keys(e);
441  if (Object.getOwnPropertySymbols) {
442    var o = Object.getOwnPropertySymbols(e);
443    r && (o = o.filter(function (r) {
444      return Object.getOwnPropertyDescriptor(e, r).enumerable;
445    })), t.push.apply(t, o);
446  }
447  return t;
448}
449function _objectSpread2(e) {
450  for (var r = 1; r < arguments.length; r++) {
451    var t = null != arguments[r] ? arguments[r] : {};
452    r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
453      _defineProperty(e, r, t[r]);
454    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
455      Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
456    });
457  }
458  return e;
459}
460function _toPrimitive(t, r) {
461  if ("object" != typeof t || !t) return t;
462  var e = t[Symbol.toPrimitive];
463  if (void 0 !== e) {
464    var i = e.call(t, r);
465    if ("object" != typeof i) return i;
466    throw new TypeError("@@toPrimitive must return a primitive value.");
467  }
468  return ("string" === r ? String : Number)(t);
469}
470function _toPropertyKey(t) {
471  var i = _toPrimitive(t, "string");
472  return "symbol" == typeof i ? i : i + "";
473}
474
475class Logger {
476  constructor(label, logger) {
477    this.trace = void 0;
478    this.debug = void 0;
479    this.log = void 0;
480    this.warn = void 0;
481    this.info = void 0;
482    this.error = void 0;
483    const lb = `[${label}]:`;
484    this.trace = noop;
485    this.debug = logger.debug.bind(null, lb);
486    this.log = logger.log.bind(null, lb);
487    this.warn = logger.warn.bind(null, lb);
488    this.info = logger.info.bind(null, lb);
489    this.error = logger.error.bind(null, lb);
490  }
491}
492const noop = function noop() {};
493const fakeLogger = {
494  trace: noop,
495  debug: noop,
496  log: noop,
497  warn: noop,
498  info: noop,
499  error: noop
500};
501function createLogger() {
502  return _extends({}, fakeLogger);
503}
504
505// let lastCallTime;
506// function formatMsgWithTimeInfo(type, msg) {
507//   const now = Date.now();
508//   const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
vendor: 5,023 bytes, lines 509-673
509//   lastCallTime = now;
510//   msg = (new Date(now)).toISOString() + ' | [' +  type + '] > ' + msg + ' ( ' + diff + ' ms )';
511//   return msg;
512// }
513
514function consolePrintFn(type, id) {
515  const func = self.console[type];
516  return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
517}
518function getLoggerFn(key, debugConfig, id) {
519  return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
520}
521const exportedLogger = createLogger();
522function enableLogs(debugConfig, context, id) {
523  // check that console is available
524  const newLogger = createLogger();
525  if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
526    const keys = [
527    // Remove out from list here to hard-disable a log-level
528    // 'trace',
529    'debug', 'log', 'info', 'warn', 'error'];
530    keys.forEach(key => {
531      newLogger[key] = getLoggerFn(key, debugConfig, id);
532    });
533    // Some browsers don't allow to use bind on console object anyway
534    // fallback to default if needed
535    try {
536      newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.6.16"}`);
537    } catch (e) {
538      /* log fn threw an exception. All logger methods are no-ops. */
539      return createLogger();
540    }
541    // global exported logger uses the same functions as new logger without `id`
542    keys.forEach(key => {
543      exportedLogger[key] = getLoggerFn(key, debugConfig);
544    });
545  } else {
546    // Reset global exported logger
547    _extends(exportedLogger, newLogger);
548  }
549  return newLogger;
550}
551const logger = exportedLogger;
552
553function getMediaSource(preferManagedMediaSource = true) {
554  if (typeof self === 'undefined') return undefined;
555  const mms = (preferManagedMediaSource || !self.MediaSource) && self.ManagedMediaSource;
556  return mms || self.MediaSource || self.WebKitMediaSource;
557}
558function isManagedMediaSource(source) {
559  return typeof self !== 'undefined' && source === self.ManagedMediaSource;
560}
561function isCompatibleTrackChange(currentTracks, requiredTracks) {
562  const trackNames = Object.keys(currentTracks);
563  const requiredTrackNames = Object.keys(requiredTracks);
564  const trackCount = trackNames.length;
565  const requiredTrackCount = requiredTrackNames.length;
566  return !trackCount || !requiredTrackCount || trackCount === requiredTrackCount && !trackNames.some(name => requiredTrackNames.indexOf(name) === -1);
567}
568
569// http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197
570// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
571/* utf.js - UTF-8 <=> UTF-16 convertion
572 *
573 * Copyright (C) 1999 Masanao Izumo <[email protected]>
574 * Version: 1.0
575 * LastModified: Dec 25 1999
576 * This library is free.  You can redistribute it and/or modify it.
577 */
578/**
579 * Converts a UTF-8 array to a string.
580 *
581 * @param array - The UTF-8 array to convert
582 *
583 * @returns The string
584 *
585 * @group Utils
586 *
587 * @beta
588 */
589function utf8ArrayToStr(array, exitOnNull = false) {
590  if (typeof TextDecoder !== 'undefined') {
591    const decoder = new TextDecoder('utf-8');
592    const decoded = decoder.decode(array);
593    if (exitOnNull) {
594      // grab up to the first null
595      const idx = decoded.indexOf('\0');
596      return idx !== -1 ? decoded.substring(0, idx) : decoded;
597    }
598    // remove any null characters
599    return decoded.replace(/\0/g, '');
600  }
601  const len = array.length;
602  let c;
603  let char2;
604  let char3;
605  let out = '';
606  let i = 0;
607  while (i < len) {
608    c = array[i++];
609    if (c === 0x00 && exitOnNull) {
610      return out;
611    } else if (c === 0x00 || c === 0x03) {
612      // If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it
613      continue;
614    }
615    switch (c >> 4) {
616      case 0:
617      case 1:
618      case 2:
619      case 3:
620      case 4:
621      case 5:
622      case 6:
623      case 7:
624        // 0xxxxxxx
625        out += String.fromCharCode(c);
626        break;
627      case 12:
628      case 13:
629        // 110x xxxx   10xx xxxx
630        char2 = array[i++];
631        out += String.fromCharCode((c & 0x1f) << 6 | char2 & 0x3f);
632        break;
633      case 14:
634        // 1110 xxxx  10xx xxxx  10xx xxxx
635        char2 = array[i++];
636        char3 = array[i++];
637        out += String.fromCharCode((c & 0x0f) << 12 | (char2 & 0x3f) << 6 | (char3 & 0x3f) << 0);
638        break;
639    }
640  }
641  return out;
642}
643
644/**
645 *  hex dump helper class
646 */
647
648function arrayToHex(array) {
649  let str = '';
650  for (let i = 0; i < array.length; i++) {
651    let h = array[i].toString(16);
652    if (h.length < 2) {
653      h = '0' + h;
654    }
655    str += h;
656  }
657  return str;
658}
659function hexToArrayBuffer(str) {
660  return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer;
661}
662
663function getDefaultExportFromCjs (x) {
664	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
665}
666
667var urlToolkit = {exports: {}};
668
669var hasRequiredUrlToolkit;
670
671function requireUrlToolkit () {
672	if (hasRequiredUrlToolkit) return urlToolkit.exports;
673	hasRequiredUrlToolkit = 1;
vendor: 38,922 bytes, lines 674-1813
674	(function (module, exports) {
675		// see https://tools.ietf.org/html/rfc1808
676
677		(function (root) {
678		  var URL_REGEX =
679		    /^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/;
680		  var FIRST_SEGMENT_REGEX = /^(?=([^\/?#]*))\1([^]*)$/;
681		  var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
682		  var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g;
683
684		  var URLToolkit = {
685		    // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
686		    // E.g
687		    // With opts.alwaysNormalize = false (default, spec compliant)
688		    // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
689		    // With opts.alwaysNormalize = true (not spec compliant)
690		    // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
691		    buildAbsoluteURL: function (baseURL, relativeURL, opts) {
692		      opts = opts || {};
693		      // remove any remaining space and CRLF
694		      baseURL = baseURL.trim();
695		      relativeURL = relativeURL.trim();
696		      if (!relativeURL) {
697		        // 2a) If the embedded URL is entirely empty, it inherits the
698		        // entire base URL (i.e., is set equal to the base URL)
699		        // and we are done.
700		        if (!opts.alwaysNormalize) {
701		          return baseURL;
702		        }
703		        var basePartsForNormalise = URLToolkit.parseURL(baseURL);
704		        if (!basePartsForNormalise) {
705		          throw new Error('Error trying to parse base URL.');
706		        }
707		        basePartsForNormalise.path = URLToolkit.normalizePath(
708		          basePartsForNormalise.path
709		        );
710		        return URLToolkit.buildURLFromParts(basePartsForNormalise);
711		      }
712		      var relativeParts = URLToolkit.parseURL(relativeURL);
713		      if (!relativeParts) {
714		        throw new Error('Error trying to parse relative URL.');
715		      }
716		      if (relativeParts.scheme) {
717		        // 2b) If the embedded URL starts with a scheme name, it is
718		        // interpreted as an absolute URL and we are done.
719		        if (!opts.alwaysNormalize) {
720		          return relativeURL;
721		        }
722		        relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
723		        return URLToolkit.buildURLFromParts(relativeParts);
724		      }
725		      var baseParts = URLToolkit.parseURL(baseURL);
726		      if (!baseParts) {
727		        throw new Error('Error trying to parse base URL.');
728		      }
729		      if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
730		        // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
731		        // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
732		        var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
733		        baseParts.netLoc = pathParts[1];
734		        baseParts.path = pathParts[2];
735		      }
736		      if (baseParts.netLoc && !baseParts.path) {
737		        baseParts.path = '/';
738		      }
739		      var builtParts = {
740		        // 2c) Otherwise, the embedded URL inherits the scheme of
741		        // the base URL.
742		        scheme: baseParts.scheme,
743		        netLoc: relativeParts.netLoc,
744		        path: null,
745		        params: relativeParts.params,
746		        query: relativeParts.query,
747		        fragment: relativeParts.fragment,
748		      };
749		      if (!relativeParts.netLoc) {
750		        // 3) If the embedded URL's <net_loc> is non-empty, we skip to
751		        // Step 7.  Otherwise, the embedded URL inherits the <net_loc>
752		        // (if any) of the base URL.
753		        builtParts.netLoc = baseParts.netLoc;
754		        // 4) If the embedded URL path is preceded by a slash "/", the
755		        // path is not relative and we skip to Step 7.
756		        if (relativeParts.path[0] !== '/') {
757		          if (!relativeParts.path) {
758		            // 5) If the embedded URL path is empty (and not preceded by a
759		            // slash), then the embedded URL inherits the base URL path
760		            builtParts.path = baseParts.path;
761		            // 5a) if the embedded URL's <params> is non-empty, we skip to
762		            // step 7; otherwise, it inherits the <params> of the base
763		            // URL (if any) and
764		            if (!relativeParts.params) {
765		              builtParts.params = baseParts.params;
766		              // 5b) if the embedded URL's <query> is non-empty, we skip to
767		              // step 7; otherwise, it inherits the <query> of the base
768		              // URL (if any) and we skip to step 7.
769		              if (!relativeParts.query) {
770		                builtParts.query = baseParts.query;
771		              }
772		            }
773		          } else {
774		            // 6) The last segment of the base URL's path (anything
775		            // following the rightmost slash "/", or the entire path if no
776		            // slash is present) is removed and the embedded URL's path is
777		            // appended in its place.
778		            var baseURLPath = baseParts.path;
779		            var newPath =
780		              baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) +
781		              relativeParts.path;
782		            builtParts.path = URLToolkit.normalizePath(newPath);
783		          }
784		        }
785		      }
786		      if (builtParts.path === null) {
787		        builtParts.path = opts.alwaysNormalize
788		          ? URLToolkit.normalizePath(relativeParts.path)
789		          : relativeParts.path;
790		      }
791		      return URLToolkit.buildURLFromParts(builtParts);
792		    },
793		    parseURL: function (url) {
794		      var parts = URL_REGEX.exec(url);
795		      if (!parts) {
796		        return null;
797		      }
798		      return {
799		        scheme: parts[1] || '',
800		        netLoc: parts[2] || '',
801		        path: parts[3] || '',
802		        params: parts[4] || '',
803		        query: parts[5] || '',
804		        fragment: parts[6] || '',
805		      };
806		    },
807		    normalizePath: function (path) {
808		      // The following operations are
809		      // then applied, in order, to the new path:
810		      // 6a) All occurrences of "./", where "." is a complete path
811		      // segment, are removed.
812		      // 6b) If the path ends with "." as a complete path segment,
813		      // that "." is removed.
814		      path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
815		      // 6c) All occurrences of "<segment>/../", where <segment> is a
816		      // complete path segment not equal to "..", are removed.
817		      // Removal of these path segments is performed iteratively,
818		      // removing the leftmost matching pattern on each iteration,
819		      // until no matching pattern remains.
820		      // 6d) If the path ends with "<segment>/..", where <segment> is a
821		      // complete path segment not equal to "..", that
822		      // "<segment>/.." is removed.
823		      while (
824		        path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length
825		      ) {}
826		      return path.split('').reverse().join('');
827		    },
828		    buildURLFromParts: function (parts) {
829		      return (
830		        parts.scheme +
831		        parts.netLoc +
832		        parts.path +
833		        parts.params +
834		        parts.query +
835		        parts.fragment
836		      );
837		    },
838		  };
839
840		  module.exports = URLToolkit;
841		})(); 
842	} (urlToolkit));
843	return urlToolkit.exports;
844}
845
846var urlToolkitExports = requireUrlToolkit();
847
848class LoadStats {
849  constructor() {
850    this.aborted = false;
851    this.loaded = 0;
852    this.retry = 0;
853    this.total = 0;
854    this.chunkCount = 0;
855    this.bwEstimate = 0;
856    this.loading = {
857      start: 0,
858      first: 0,
859      end: 0
860    };
861    this.parsing = {
862      start: 0,
863      end: 0
864    };
865    this.buffering = {
866      start: 0,
867      first: 0,
868      end: 0
869    };
870  }
871}
872
873var ElementaryStreamTypes = {
874  AUDIO: "audio",
875  VIDEO: "video",
876  AUDIOVIDEO: "audiovideo"
877};
878class BaseSegment {
879  constructor(base) {
880    this._byteRange = null;
881    this._url = null;
882    this._stats = null;
883    this._streams = null;
884    // baseurl is the URL to the playlist
885    this.base = void 0;
886    // relurl is the portion of the URL that comes from inside the playlist.
887    this.relurl = void 0;
888    if (typeof base === 'string') {
889      base = {
890        url: base
891      };
892    }
893    this.base = base;
894    makeEnumerable(this, 'stats');
895  }
896
897  // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array
898  setByteRange(value, previous) {
899    const params = value.split('@', 2);
900    let start;
901    if (params.length === 1) {
902      start = (previous == null ? void 0 : previous.byteRangeEndOffset) || 0;
903    } else {
904      start = parseInt(params[1]);
905    }
906    this._byteRange = [start, parseInt(params[0]) + start];
907  }
908  get baseurl() {
909    return this.base.url;
910  }
911  get byteRange() {
912    if (this._byteRange === null) {
913      return [];
914    }
915    return this._byteRange;
916  }
917  get byteRangeStartOffset() {
918    return this.byteRange[0];
919  }
920  get byteRangeEndOffset() {
921    return this.byteRange[1];
922  }
923  get elementaryStreams() {
924    if (this._streams === null) {
925      this._streams = {
926        [ElementaryStreamTypes.AUDIO]: null,
927        [ElementaryStreamTypes.VIDEO]: null,
928        [ElementaryStreamTypes.AUDIOVIDEO]: null
929      };
930    }
931    return this._streams;
932  }
933  set elementaryStreams(value) {
934    this._streams = value;
935  }
936  get hasStats() {
937    return this._stats !== null;
938  }
939  get hasStreams() {
940    return this._streams !== null;
941  }
942  get stats() {
943    if (this._stats === null) {
944      this._stats = new LoadStats();
945    }
946    return this._stats;
947  }
948  set stats(value) {
949    this._stats = value;
950  }
951  get url() {
952    if (!this._url && this.baseurl && this.relurl) {
953      this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, {
954        alwaysNormalize: true
955      });
956    }
957    return this._url || '';
958  }
959  set url(value) {
960    this._url = value;
961  }
962  clearElementaryStreamInfo() {
963    const {
964      elementaryStreams
965    } = this;
966    elementaryStreams[ElementaryStreamTypes.AUDIO] = null;
967    elementaryStreams[ElementaryStreamTypes.VIDEO] = null;
968    elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null;
969  }
970}
971function isMediaFragment(frag) {
972  return frag.sn !== 'initSegment';
973}
974
975/**
976 * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
977 */
978class Fragment extends BaseSegment {
979  constructor(type, base) {
980    super(base);
981    this._decryptdata = null;
982    this._programDateTime = null;
983    this._ref = null;
984    // Approximate bit rate of the fragment expressed in bits per second (bps) as indicated by the last EXT-X-BITRATE (kbps) tag
985    this._bitrate = void 0;
986    this.rawProgramDateTime = null;
987    this.tagList = [];
988    // EXTINF has to be present for a m3u8 to be considered valid
989    this.duration = 0;
990    // sn notates the sequence number for a segment, and if set to a string can be 'initSegment'
991    this.sn = 0;
992    // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption
993    // core difference from the private field _decryptdata is the lack of the initialized IV
994    // _decryptdata will set the IV for this segment based on the segment number in the fragment
995    this.levelkeys = void 0;
996    // A string representing the fragment type
997    this.type = void 0;
998    // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading
999    this.loader = null;
1000    // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading
1001    this.keyLoader = null;
1002    // The level/track index to which the fragment belongs
1003    this.level = -1;
1004    // The continuity counter of the fragment
1005    this.cc = 0;
1006    // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
1007    this.startPTS = void 0;
1008    // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
1009    this.endPTS = void 0;
1010    // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
1011    this.startDTS = void 0;
1012    // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
1013    this.endDTS = void 0;
1014    // The start time of the fragment, as listed in the manifest. Updated after transmux complete.
1015    this.start = 0;
1016    // The offset time (seconds) of the fragment from the start of the Playlist
1017    this.playlistOffset = 0;
1018    // Set by `updateFragPTSDTS` in level-helper
1019    this.deltaPTS = void 0;
1020    // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
1021    this.maxStartPTS = void 0;
1022    // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete.
1023    this.minEndPTS = void 0;
1024    // Init Segment bytes (unset for media segments)
1025    this.data = void 0;
1026    // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered
1027    this.bitrateTest = false;
1028    // #EXTINF  segment title
1029    this.title = null;
1030    // The Media Initialization Section for this segment
1031    this.initSegment = null;
1032    // Fragment is the last fragment in the media playlist
1033    this.endList = void 0;
1034    // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded
1035    this.gap = void 0;
1036    // Deprecated
1037    this.urlId = 0;
1038    this.type = type;
1039  }
1040  get byteLength() {
1041    if (this.hasStats) {
1042      const total = this.stats.total;
1043      if (total) {
1044        return total;
1045      }
1046    }
1047    if (this.byteRange.length) {
1048      const start = this.byteRange[0];
1049      const end = this.byteRange[1];
1050      if (isFiniteNumber(start) && isFiniteNumber(end)) {
1051        return end - start;
1052      }
1053    }
1054    return null;
1055  }
1056  get bitrate() {
1057    if (this.byteLength) {
1058      return this.byteLength * 8 / this.duration;
1059    }
1060    if (this._bitrate) {
1061      return this._bitrate;
1062    }
1063    return null;
1064  }
1065  set bitrate(value) {
1066    this._bitrate = value;
1067  }
1068  get decryptdata() {
1069    var _this$_decryptdata;
1070    const {
1071      levelkeys
1072    } = this;
1073    if (!levelkeys || levelkeys.NONE) {
1074      return null;
1075    }
1076    if (levelkeys.identity) {
1077      if (!this._decryptdata) {
1078        this._decryptdata = levelkeys.identity.getDecryptData(this.sn);
1079      }
1080    } else if (!((_this$_decryptdata = this._decryptdata) != null && _this$_decryptdata.keyId)) {
1081      const keyFormats = Object.keys(levelkeys);
1082      if (keyFormats.length === 1) {
1083        const levelKey = this._decryptdata = levelkeys[keyFormats[0]] || null;
1084        if (levelKey) {
1085          this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys);
1086        }
1087      }
1088    }
1089    return this._decryptdata;
1090  }
1091  get end() {
1092    return this.start + this.duration;
1093  }
1094  get endProgramDateTime() {
1095    if (this.programDateTime === null) {
1096      return null;
1097    }
1098    const duration = !isFiniteNumber(this.duration) ? 0 : this.duration;
1099    return this.programDateTime + duration * 1000;
1100  }
1101  get encrypted() {
1102    var _this$_decryptdata2;
1103    // At the m3u8-parser level we need to add support for manifest signalled keyformats
1104    // when we want the fragment to start reporting that it is encrypted.
1105    // Currently, keyFormat will only be set for identity keys
1106    if ((_this$_decryptdata2 = this._decryptdata) != null && _this$_decryptdata2.encrypted) {
1107      return true;
1108    } else if (this.levelkeys) {
1109      var _this$levelkeys$keyFo;
1110      const keyFormats = Object.keys(this.levelkeys);
1111      const len = keyFormats.length;
1112      if (len > 1 || len === 1 && (_this$levelkeys$keyFo = this.levelkeys[keyFormats[0]]) != null && _this$levelkeys$keyFo.encrypted) {
1113        return true;
1114      }
1115    }
1116    return false;
1117  }
1118  get programDateTime() {
1119    if (this._programDateTime === null && this.rawProgramDateTime) {
1120      this.programDateTime = Date.parse(this.rawProgramDateTime);
1121    }
1122    return this._programDateTime;
1123  }
1124  set programDateTime(value) {
1125    if (!isFiniteNumber(value)) {
1126      this._programDateTime = this.rawProgramDateTime = null;
1127      return;
1128    }
1129    this._programDateTime = value;
1130  }
1131  get ref() {
1132    if (!isMediaFragment(this)) {
1133      return null;
1134    }
1135    if (!this._ref) {
1136      this._ref = {
1137        base: this.base,
1138        start: this.start,
1139        duration: this.duration,
1140        sn: this.sn,
1141        programDateTime: this.programDateTime
1142      };
1143    }
1144    return this._ref;
1145  }
1146  addStart(value) {
1147    this.setStart(this.start + value);
1148  }
1149  setStart(value) {
1150    this.start = value;
1151    if (this._ref) {
1152      this._ref.start = value;
1153    }
1154  }
1155  setDuration(value) {
1156    this.duration = value;
1157    if (this._ref) {
1158      this._ref.duration = value;
1159    }
1160  }
1161  setKeyFormat(keyFormat) {
1162    const levelkeys = this.levelkeys;
1163    if (levelkeys) {
1164      var _this$_decryptdata3;
1165      const key = levelkeys[keyFormat];
1166      if (key && !((_this$_decryptdata3 = this._decryptdata) != null && _this$_decryptdata3.keyId)) {
1167        this._decryptdata = key.getDecryptData(this.sn, levelkeys);
1168      }
1169    }
1170  }
1171  abortRequests() {
1172    var _this$loader, _this$keyLoader;
1173    (_this$loader = this.loader) == null || _this$loader.abort();
1174    (_this$keyLoader = this.keyLoader) == null || _this$keyLoader.abort();
1175  }
1176  setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial = false) {
1177    const {
1178      elementaryStreams
1179    } = this;
1180    const info = elementaryStreams[type];
1181    if (!info) {
1182      elementaryStreams[type] = {
1183        startPTS,
1184        endPTS,
1185        startDTS,
1186        endDTS,
1187        partial
1188      };
1189      return;
1190    }
1191    info.startPTS = Math.min(info.startPTS, startPTS);
1192    info.endPTS = Math.max(info.endPTS, endPTS);
1193    info.startDTS = Math.min(info.startDTS, startDTS);
1194    info.endDTS = Math.max(info.endDTS, endDTS);
1195  }
1196}
1197
1198/**
1199 * Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}.
1200 */
1201class Part extends BaseSegment {
1202  constructor(partAttrs, frag, base, index, previous) {
1203    super(base);
1204    this.fragOffset = 0;
1205    this.duration = 0;
1206    this.gap = false;
1207    this.independent = false;
1208    this.relurl = void 0;
1209    this.fragment = void 0;
1210    this.index = void 0;
1211    this.duration = partAttrs.decimalFloatingPoint('DURATION');
1212    this.gap = partAttrs.bool('GAP');
1213    this.independent = partAttrs.bool('INDEPENDENT');
1214    this.relurl = partAttrs.enumeratedString('URI');
1215    this.fragment = frag;
1216    this.index = index;
1217    const byteRange = partAttrs.enumeratedString('BYTERANGE');
1218    if (byteRange) {
1219      this.setByteRange(byteRange, previous);
1220    }
1221    if (previous) {
1222      this.fragOffset = previous.fragOffset + previous.duration;
1223    }
1224  }
1225  get start() {
1226    return this.fragment.start + this.fragOffset;
1227  }
1228  get end() {
1229    return this.start + this.duration;
1230  }
1231  get loaded() {
1232    const {
1233      elementaryStreams
1234    } = this;
1235    return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo);
1236  }
1237}
1238function getOwnPropertyDescriptorFromPrototypeChain(object, property) {
1239  const prototype = Object.getPrototypeOf(object);
1240  if (prototype) {
1241    const propertyDescriptor = Object.getOwnPropertyDescriptor(prototype, property);
1242    if (propertyDescriptor) {
1243      return propertyDescriptor;
1244    }
1245    return getOwnPropertyDescriptorFromPrototypeChain(prototype, property);
1246  }
1247}
1248function makeEnumerable(object, property) {
1249  const d = getOwnPropertyDescriptorFromPrototypeChain(object, property);
1250  if (d) {
1251    d.enumerable = true;
1252    Object.defineProperty(object, property, d);
1253  }
1254}
1255
1256const UINT32_MAX$1 = Math.pow(2, 32) - 1;
1257const push = [].push;
1258
1259// We are using fixed track IDs for driving the MP4 remuxer
1260// instead of following the TS PIDs.
1261// There is no reason not to do this and some browsers/SourceBuffer-demuxers
1262// may not like if there are TrackID "switches"
1263// See https://github.com/video-dev/hls.js/issues/1331
1264// Here we are mapping our internal track types to constant MP4 track IDs
1265// With MSE currently one can only have one track of each, and we are muxing
1266// whatever video/audio rendition in them.
1267const RemuxerTrackIdConfig = {
1268  video: 1,
1269  audio: 2,
1270  id3: 3,
1271  text: 4
1272};
1273function bin2str(data) {
1274  return String.fromCharCode.apply(null, data);
1275}
1276function readUint16(buffer, offset) {
1277  const val = buffer[offset] << 8 | buffer[offset + 1];
1278  return val < 0 ? 65536 + val : val;
1279}
1280function readUint32(buffer, offset) {
1281  const val = readSint32(buffer, offset);
1282  return val < 0 ? 4294967296 + val : val;
1283}
1284function readUint64(buffer, offset) {
1285  let result = readUint32(buffer, offset);
1286  result *= Math.pow(2, 32);
1287  result += readUint32(buffer, offset + 4);
1288  return result;
1289}
1290function readSint32(buffer, offset) {
1291  return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3];
1292}
1293
1294// Find "moof" box
1295function hasMoofData(data) {
1296  const end = data.byteLength;
1297  for (let i = 0; i < end;) {
1298    const size = readUint32(data, i);
1299    if (size > 8 && data[i + 4] === 0x6d && data[i + 5] === 0x6f && data[i + 6] === 0x6f && data[i + 7] === 0x66) {
1300      return true;
1301    }
1302    i = size > 1 ? i + size : end;
1303  }
1304  return false;
1305}
1306
1307// Find the data for a box specified by its path
1308function findBox(data, path) {
1309  const results = [];
1310  if (!path.length) {
1311    // short-circuit the search for empty paths
1312    return results;
1313  }
1314  const end = data.byteLength;
1315  for (let i = 0; i < end;) {
1316    const size = readUint32(data, i);
1317    const type = bin2str(data.subarray(i + 4, i + 8));
1318    const endbox = size > 1 ? i + size : end;
1319    if (type === path[0]) {
1320      if (path.length === 1) {
1321        // this is the end of the path and we've found the box we were
1322        // looking for
1323        results.push(data.subarray(i + 8, endbox));
1324      } else {
1325        // recursively search for the next box along the path
1326        const subresults = findBox(data.subarray(i + 8, endbox), path.slice(1));
1327        if (subresults.length) {
1328          push.apply(results, subresults);
1329        }
1330      }
1331    }
1332    i = endbox;
1333  }
1334
1335  // we've finished searching all of data
1336  return results;
1337}
1338function parseSegmentIndex(sidx) {
1339  const references = [];
1340  const version = sidx[0];
1341
1342  // set initial offset, we skip the reference ID (not needed)
1343  let index = 8;
1344  const timescale = readUint32(sidx, index);
1345  index += 4;
1346  let earliestPresentationTime = 0;
1347  let firstOffset = 0;
1348  if (version === 0) {
1349    earliestPresentationTime = readUint32(sidx, index);
1350    firstOffset = readUint32(sidx, index + 4);
1351    index += 8;
1352  } else {
1353    earliestPresentationTime = readUint64(sidx, index);
1354    firstOffset = readUint64(sidx, index + 8);
1355    index += 16;
1356  }
1357
1358  // skip reserved
1359  index += 2;
1360  let startByte = sidx.length + firstOffset;
1361  const referencesCount = readUint16(sidx, index);
1362  index += 2;
1363  for (let i = 0; i < referencesCount; i++) {
1364    let referenceIndex = index;
1365    const referenceInfo = readUint32(sidx, referenceIndex);
1366    referenceIndex += 4;
1367    const referenceSize = referenceInfo & 0x7fffffff;
1368    const referenceType = (referenceInfo & 0x80000000) >>> 31;
1369    if (referenceType === 1) {
1370      logger.warn('SIDX has hierarchical references (not supported)');
1371      return null;
1372    }
1373    const subsegmentDuration = readUint32(sidx, referenceIndex);
1374    referenceIndex += 4;
1375    references.push({
1376      referenceSize,
1377      subsegmentDuration,
1378      // unscaled
1379      info: {
1380        duration: subsegmentDuration / timescale,
1381        start: startByte,
1382        end: startByte + referenceSize - 1
1383      }
1384    });
1385    startByte += referenceSize;
1386
1387    // Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits
1388    // for |sapDelta|.
1389    referenceIndex += 4;
1390
1391    // skip to next ref
1392    index = referenceIndex;
1393  }
1394  return {
1395    earliestPresentationTime,
1396    timescale,
1397    version,
1398    referencesCount,
1399    references
1400  };
1401}
1402
1403/**
1404 * Parses an MP4 initialization segment and extracts stream type and
1405 * timescale values for any declared tracks. Timescale values indicate the
1406 * number of clock ticks per second to assume for time-based values
1407 * elsewhere in the MP4.
1408 *
1409 * To determine the start time of an MP4, you need two pieces of
1410 * information: the timescale unit and the earliest base media decode
1411 * time. Multiple timescales can be specified within an MP4 but the
1412 * base media decode time is always expressed in the timescale from
1413 * the media header box for the track:
1414 * ```
1415 * moov > trak > mdia > mdhd.timescale
1416 * moov > trak > mdia > hdlr
1417 * ```
1418 * @param initSegment the bytes of the init segment
1419 * @returns a hash of track type to timescale values or null if
1420 * the init segment is malformed.
1421 */
1422
1423function parseInitSegment(initSegment) {
1424  const result = [];
1425  const traks = findBox(initSegment, ['moov', 'trak']);
1426  for (let i = 0; i < traks.length; i++) {
1427    const trak = traks[i];
1428    const tkhd = findBox(trak, ['tkhd'])[0];
1429    if (tkhd) {
1430      let version = tkhd[0];
1431      const trackId = readUint32(tkhd, version === 0 ? 12 : 20);
1432      const mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
1433      if (mdhd) {
1434        version = mdhd[0];
1435        const timescale = readUint32(mdhd, version === 0 ? 12 : 20);
1436        const hdlr = findBox(trak, ['mdia', 'hdlr'])[0];
1437        if (hdlr) {
1438          const hdlrType = bin2str(hdlr.subarray(8, 12));
1439          const type = {
1440            soun: ElementaryStreamTypes.AUDIO,
1441            vide: ElementaryStreamTypes.VIDEO
1442          }[hdlrType];
1443          // Parse codec details
1444          const stsdBox = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
1445          const stsd = parseStsd(stsdBox);
1446          if (type) {
1447            // Add 'audio', 'video', and 'audiovideo' track records that will map to SourceBuffers
1448            result[trackId] = {
1449              timescale,
1450              type,
1451              stsd
1452            };
1453            result[type] = _objectSpread2({
1454              timescale,
1455              id: trackId
1456            }, stsd);
1457          } else {
1458            // Add 'meta' and other track records
1459            result[trackId] = {
1460              timescale,
1461              type: hdlrType,
1462              stsd
1463            };
1464          }
1465        }
1466      }
1467    }
1468  }
1469  const trex = findBox(initSegment, ['moov', 'mvex', 'trex']);
1470  trex.forEach(trex => {
1471    const trackId = readUint32(trex, 4);
1472    const track = result[trackId];
1473    if (track) {
1474      track.default = {
1475        duration: readUint32(trex, 12),
1476        flags: readUint32(trex, 20)
1477      };
1478    }
1479  });
1480  return result;
1481}
1482function parseStsd(stsd) {
1483  const sampleEntries = stsd.subarray(8);
1484  const sampleEntriesEnd = sampleEntries.subarray(8 + 78);
1485  const fourCC = bin2str(sampleEntries.subarray(4, 8));
1486  let codec = fourCC;
1487  let supplemental;
1488  const encrypted = fourCC === 'enca' || fourCC === 'encv';
1489  if (encrypted) {
1490    const encBox = findBox(sampleEntries, [fourCC])[0];
1491    const encBoxChildren = encBox.subarray(fourCC === 'enca' ? 28 : 78);
1492    const sinfs = findBox(encBoxChildren, ['sinf']);
1493    sinfs.forEach(sinf => {
1494      const schm = findBox(sinf, ['schm'])[0];
1495      if (schm) {
1496        const scheme = bin2str(schm.subarray(4, 8));
1497        if (scheme === 'cbcs' || scheme === 'cenc') {
1498          const frma = findBox(sinf, ['frma'])[0];
1499          if (frma) {
1500            // for encrypted content codec fourCC will be in frma
1501            codec = bin2str(frma);
1502          }
1503        }
1504      }
1505    });
1506  }
1507  const codecFourCC = codec;
1508  switch (codec) {
1509    case 'avc1':
1510    case 'avc2':
1511    case 'avc3':
1512    case 'avc4':
1513      {
1514        // extract profile + compatibility + level out of avcC box
1515        const avcCBox = findBox(sampleEntriesEnd, ['avcC'])[0];
1516        if (avcCBox && avcCBox.length > 3) {
1517          codec += '.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]);
1518          supplemental = parseSupplementalDoViCodec(codecFourCC === 'avc1' ? 'dva1' : 'dvav', sampleEntriesEnd);
1519        }
1520        break;
1521      }
1522    case 'mp4a':
1523      {
1524        const codecBox = findBox(sampleEntries, [fourCC])[0];
1525        const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
1526        if (esdsBox && esdsBox.length > 7) {
1527          let i = 4;
1528          // ES Descriptor tag
1529          if (esdsBox[i++] !== 0x03) {
1530            break;
1531          }
1532          i = skipBERInteger(esdsBox, i);
1533          i += 2; // skip es_id;
1534          const flags = esdsBox[i++];
1535          if (flags & 0x80) {
1536            i += 2; // skip dependency es_id
1537          }
1538          if (flags & 0x40) {
1539            i += esdsBox[i++]; // skip URL
1540          }
1541          // Decoder config descriptor
1542          if (esdsBox[i++] !== 0x04) {
1543            break;
1544          }
1545          i = skipBERInteger(esdsBox, i);
1546          const objectType = esdsBox[i++];
1547          if (objectType === 0x40) {
1548            codec += '.' + toHex(objectType);
1549          } else {
1550            break;
1551          }
1552          i += 12;
1553          // Decoder specific info
1554          if (esdsBox[i++] !== 0x05) {
1555            break;
1556          }
1557          i = skipBERInteger(esdsBox, i);
1558          const firstByte = esdsBox[i++];
1559          let audioObjectType = (firstByte & 0xf8) >> 3;
1560          if (audioObjectType === 31) {
1561            audioObjectType += 1 + ((firstByte & 0x7) << 3) + ((esdsBox[i] & 0xe0) >> 5);
1562          }
1563          codec += '.' + audioObjectType;
1564        }
1565        break;
1566      }
1567    case 'hvc1':
1568    case 'hev1':
1569      {
1570        const hvcCBox = findBox(sampleEntriesEnd, ['hvcC'])[0];
1571        if (hvcCBox && hvcCBox.length > 12) {
1572          const profileByte = hvcCBox[1];
1573          const profileSpace = ['', 'A', 'B', 'C'][profileByte >> 6];
1574          const generalProfileIdc = profileByte & 0x1f;
1575          const profileCompat = readUint32(hvcCBox, 2);
1576          const tierFlag = (profileByte & 0x20) >> 5 ? 'H' : 'L';
1577          const levelIDC = hvcCBox[12];
1578          const constraintIndicator = hvcCBox.subarray(6, 12);
1579          codec += '.' + profileSpace + generalProfileIdc;
1580          codec += '.' + reverse32BitInt(profileCompat).toString(16).toUpperCase();
1581          codec += '.' + tierFlag + levelIDC;
1582          let constraintString = '';
1583          for (let i = constraintIndicator.length; i--;) {
1584            const byte = constraintIndicator[i];
1585            if (byte || constraintString) {
1586              const encodedByte = byte.toString(16).toUpperCase();
1587              constraintString = '.' + encodedByte + constraintString;
1588            }
1589          }
1590          codec += constraintString;
1591        }
1592        supplemental = parseSupplementalDoViCodec(codecFourCC == 'hev1' ? 'dvhe' : 'dvh1', sampleEntriesEnd);
1593        break;
1594      }
1595    case 'dvh1':
1596    case 'dvhe':
1597    case 'dvav':
1598    case 'dva1':
1599    case 'dav1':
1600      {
1601        codec = parseSupplementalDoViCodec(codec, sampleEntriesEnd) || codec;
1602        break;
1603      }
1604    case 'vp09':
1605      {
1606        const vpcCBox = findBox(sampleEntriesEnd, ['vpcC'])[0];
1607        if (vpcCBox && vpcCBox.length > 6) {
1608          const profile = vpcCBox[4];
1609          const level = vpcCBox[5];
1610          const bitDepth = vpcCBox[6] >> 4 & 0x0f;
1611          codec += '.' + addLeadingZero(profile) + '.' + addLeadingZero(level) + '.' + addLeadingZero(bitDepth);
1612        }
1613        break;
1614      }
1615    case 'av01':
1616      {
1617        const av1CBox = findBox(sampleEntriesEnd, ['av1C'])[0];
1618        if (av1CBox && av1CBox.length > 2) {
1619          const profile = av1CBox[1] >>> 5;
1620          const level = av1CBox[1] & 0x1f;
1621          const tierFlag = av1CBox[2] >>> 7 ? 'H' : 'M';
1622          const highBitDepth = (av1CBox[2] & 0x40) >> 6;
1623          const twelveBit = (av1CBox[2] & 0x20) >> 5;
1624          const bitDepth = profile === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8;
1625          const monochrome = (av1CBox[2] & 0x10) >> 4;
1626          const chromaSubsamplingX = (av1CBox[2] & 0x08) >> 3;
1627          const chromaSubsamplingY = (av1CBox[2] & 0x04) >> 2;
1628          const chromaSamplePosition = av1CBox[2] & 0x03;
1629          // TODO: parse color_description_present_flag
1630          // default it to BT.709/limited range for now
1631          // more info https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax
1632          const colorPrimaries = 1;
1633          const transferCharacteristics = 1;
1634          const matrixCoefficients = 1;
1635          const videoFullRangeFlag = 0;
1636          codec += '.' + profile + '.' + addLeadingZero(level) + tierFlag + '.' + addLeadingZero(bitDepth) + '.' + monochrome + '.' + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition + '.' + addLeadingZero(colorPrimaries) + '.' + addLeadingZero(transferCharacteristics) + '.' + addLeadingZero(matrixCoefficients) + '.' + videoFullRangeFlag;
1637          supplemental = parseSupplementalDoViCodec('dav1', sampleEntriesEnd);
1638        }
1639        break;
1640      }
1641  }
1642  return {
1643    codec,
1644    encrypted,
1645    supplemental
1646  };
1647}
1648function parseSupplementalDoViCodec(fourCC, sampleEntriesEnd) {
1649  const dvvCResult = findBox(sampleEntriesEnd, ['dvvC']); // used by DoVi Profile 8 to 10
1650  const dvXCBox = dvvCResult.length ? dvvCResult[0] : findBox(sampleEntriesEnd, ['dvcC'])[0]; // used by DoVi Profiles up to 7 and 20
1651  if (dvXCBox) {
1652    const doViProfile = dvXCBox[2] >> 1 & 0x7f;
1653    const doViLevel = dvXCBox[2] << 5 & 0x20 | dvXCBox[3] >> 3 & 0x1f;
1654    return fourCC + '.' + addLeadingZero(doViProfile) + '.' + addLeadingZero(doViLevel);
1655  }
1656}
1657function reverse32BitInt(val) {
1658  let result = 0;
1659  for (let i = 0; i < 32; i++) {
1660    result |= (val >> i & 1) << 32 - 1 - i;
1661  }
1662  return result >>> 0;
1663}
1664function skipBERInteger(bytes, i) {
1665  const limit = i + 5;
1666  while (bytes[i++] & 0x80 && i < limit) {
1667    /* do nothing */
1668  }
1669  return i;
1670}
1671function toHex(x) {
1672  return ('0' + x.toString(16).toUpperCase()).slice(-2);
1673}
1674function addLeadingZero(num) {
1675  return (num < 10 ? '0' : '') + num;
1676}
1677function patchEncyptionData(initSegment, decryptdata) {
1678  if (!initSegment || !decryptdata) {
1679    return;
1680  }
1681  const keyId = decryptdata.keyId;
1682  if (keyId && decryptdata.isCommonEncryption) {
1683    applyToTencBoxes(initSegment, (tenc, isAudio) => {
1684      // Look for default key id (keyID offset is always 8 within the tenc box):
1685      const tencKeyId = tenc.subarray(8, 24);
1686      if (!tencKeyId.some(b => b !== 0)) {
1687        logger.log(`[eme] Patching keyId in 'enc${isAudio ? 'a' : 'v'}>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex(keyId)}`);
1688        tenc.set(keyId, 8);
1689      }
1690    });
1691  }
1692}
1693function parseKeyIdsFromTenc(initSegment) {
1694  const keyIds = [];
1695  applyToTencBoxes(initSegment, tenc => keyIds.push(tenc.subarray(8, 24)));
1696  return keyIds;
1697}
1698function applyToTencBoxes(initSegment, predicate) {
1699  const traks = findBox(initSegment, ['moov', 'trak']);
1700  traks.forEach(trak => {
1701    const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
1702    if (!stsd) return;
1703    const sampleEntries = stsd.subarray(8);
1704    let encBoxes = findBox(sampleEntries, ['enca']);
1705    const isAudio = encBoxes.length > 0;
1706    if (!isAudio) {
1707      encBoxes = findBox(sampleEntries, ['encv']);
1708    }
1709    encBoxes.forEach(enc => {
1710      const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78);
1711      const sinfBoxes = findBox(encBoxChildren, ['sinf']);
1712      sinfBoxes.forEach(sinf => {
1713        const tenc = parseSinf(sinf);
1714        if (tenc) {
1715          predicate(tenc, isAudio);
1716        }
1717      });
1718    });
1719  });
1720}
1721function parseSinf(sinf) {
1722  const schm = findBox(sinf, ['schm'])[0];
1723  if (schm) {
1724    const scheme = bin2str(schm.subarray(4, 8));
1725    if (scheme === 'cbcs' || scheme === 'cenc') {
1726      const tenc = findBox(sinf, ['schi', 'tenc'])[0];
1727      if (tenc) {
1728        return tenc;
1729      }
1730    }
1731  }
1732}
1733
1734/*
1735  For Reference:
1736  aligned(8) class TrackFragmentHeaderBox
1737           extends FullBox(‘tfhd’, 0, tf_flags){
1738     unsigned int(32)  track_ID;
1739     // all the following are optional fields
1740     unsigned int(64)  base_data_offset;
1741     unsigned int(32)  sample_description_index;
1742     unsigned int(32)  default_sample_duration;
1743     unsigned int(32)  default_sample_size;
1744     unsigned int(32)  default_sample_flags
1745  }
1746 */
1747
1748function getSampleData(data, initData, logger) {
1749  const tracks = {};
1750  const trafs = findBox(data, ['moof', 'traf']);
1751  for (let i = 0; i < trafs.length; i++) {
1752    const traf = trafs[i];
1753    // There is only one tfhd & trun per traf
1754    // This is true for CMAF style content, and we should perhaps check the ftyp
1755    // and only look for a single trun then, but for ISOBMFF we should check
1756    // for multiple track runs.
1757    const tfhd = findBox(traf, ['tfhd'])[0];
1758    // get the track id from the tfhd
1759    const id = readUint32(tfhd, 4);
1760    const track = initData[id];
1761    if (!track) {
1762      continue;
1763    }
1764    tracks[id] || (tracks[id] = {
1765      start: NaN,
1766      duration: 0,
1767      sampleCount: 0,
1768      timescale: track.timescale,
1769      type: track.type
1770    });
1771    const trackTimes = tracks[id];
1772    // get start DTS
1773    const tfdt = findBox(traf, ['tfdt'])[0];
1774    if (tfdt) {
1775      const version = tfdt[0];
1776      let baseTime = readUint32(tfdt, 4);
1777      if (version === 1) {
1778        // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild.
1779        // This prevents large values from being used for initPTS, which can cause playlist sync issues.
1780        // https://github.com/video-dev/hls.js/issues/5303
1781        if (baseTime === UINT32_MAX$1) {
1782          logger.warn(`[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`);
1783        } else {
1784          baseTime *= UINT32_MAX$1 + 1;
1785          baseTime += readUint32(tfdt, 8);
1786        }
1787      }
1788      if (isFiniteNumber(baseTime) && (!isFiniteNumber(trackTimes.start) || baseTime < trackTimes.start)) {
1789        trackTimes.start = baseTime;
1790      }
1791    }
1792    const trackDefault = track.default;
1793    const tfhdFlags = readUint32(tfhd, 0) | (trackDefault == null ? void 0 : trackDefault.flags);
1794    let defaultSampleDuration = (trackDefault == null ? void 0 : trackDefault.duration) || 0;
1795    if (tfhdFlags & 0x000008) {
1796      // 0x000008 indicates the presence of the default_sample_duration field
1797      if (tfhdFlags & 0x000002) {
1798        // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration
1799        // If present, the default_sample_duration exists at byte offset 12
1800        defaultSampleDuration = readUint32(tfhd, 12);
1801      } else {
1802        // Otherwise, the duration is at byte offset 8
1803        defaultSampleDuration = readUint32(tfhd, 8);
1804      }
1805    }
1806    const truns = findBox(traf, ['trun']);
1807    let sampleDTS = trackTimes.start || 0;
1808    let rawDuration = 0;
1809    let sampleDuration = defaultSampleDuration;
1810    for (let j = 0; j < truns.length; j++) {
1811      const trun = truns[j];
1812      const sampleCount = readUint32(trun, 4);
1813      const sampleIndex = trackTimes.sampleCount;
vendor: 2,995 bytes, lines 1814-1894
1814      trackTimes.sampleCount += sampleCount;
1815      // Get duration from samples
1816      const dataOffsetPresent = trun[3] & 0x01;
1817      const firstSampleFlagsPresent = trun[3] & 0x04;
1818      const sampleDurationPresent = trun[2] & 0x01;
1819      const sampleSizePresent = trun[2] & 0x02;
1820      const sampleFlagsPresent = trun[2] & 0x04;
1821      const sampleCompositionTimeOffsetPresent = trun[2] & 0x08;
1822      let offset = 8;
1823      let remaining = sampleCount;
1824      if (dataOffsetPresent) {
1825        offset += 4;
1826      }
1827      if (firstSampleFlagsPresent && sampleCount) {
1828        const isNonSyncSample = trun[offset + 1] & 0x01;
1829        if (!isNonSyncSample && trackTimes.keyFrameIndex === undefined) {
1830          trackTimes.keyFrameIndex = sampleIndex;
1831        }
1832        offset += 4;
1833        if (sampleDurationPresent) {
1834          sampleDuration = readUint32(trun, offset);
1835          offset += 4;
1836        } else {
1837          sampleDuration = defaultSampleDuration;
1838        }
1839        if (sampleSizePresent) {
1840          offset += 4;
1841        }
1842        if (sampleCompositionTimeOffsetPresent) {
1843          offset += 4;
1844        }
1845        sampleDTS += sampleDuration;
1846        rawDuration += sampleDuration;
1847        remaining--;
1848      }
1849      while (remaining--) {
1850        if (sampleDurationPresent) {
1851          sampleDuration = readUint32(trun, offset);
1852          offset += 4;
1853        } else {
1854          sampleDuration = defaultSampleDuration;
1855        }
1856        if (sampleSizePresent) {
1857          offset += 4;
1858        }
1859        if (sampleFlagsPresent) {
1860          const isNonSyncSample = trun[offset + 1] & 0x01;
1861          if (!isNonSyncSample) {
1862            if (trackTimes.keyFrameIndex === undefined) {
1863              trackTimes.keyFrameIndex = trackTimes.sampleCount - (remaining + 1);
1864              trackTimes.keyFrameStart = sampleDTS;
1865            }
1866          }
1867          offset += 4;
1868        }
1869        if (sampleCompositionTimeOffsetPresent) {
1870          offset += 4;
1871        }
1872        sampleDTS += sampleDuration;
1873        rawDuration += sampleDuration;
1874      }
1875      if (!rawDuration && defaultSampleDuration) {
1876        rawDuration += defaultSampleDuration * sampleCount;
1877      }
1878    }
1879    trackTimes.duration += rawDuration;
1880  }
1881  if (!Object.keys(tracks).some(trackId => tracks[trackId].duration)) {
1882    // If duration samples are not available in the traf use sidx subsegment_duration
1883    let sidxMinStart = Infinity;
1884    let sidxMaxEnd = 0;
1885    const sidxs = findBox(data, ['sidx']);
1886    for (let i = 0; i < sidxs.length; i++) {
1887      const sidx = parseSegmentIndex(sidxs[i]);
1888      if (sidx != null && sidx.references) {
1889        sidxMinStart = Math.min(sidxMinStart, sidx.earliestPresentationTime / sidx.timescale);
1890        const subSegmentDuration = sidx.references.reduce((dur, ref) => dur + ref.info.duration || 0, 0);
1891        sidxMaxEnd = Math.max(sidxMaxEnd, subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale);
1892      }
1893    }
1894    if (sidxMaxEnd && isFiniteNumber(sidxMaxEnd)) {
vendor: 92,033 bytes, lines 1895-4324
1895      Object.keys(tracks).forEach(trackId => {
1896        if (!tracks[trackId].duration) {
1897          tracks[trackId].duration = sidxMaxEnd * tracks[trackId].timescale - tracks[trackId].start;
1898        }
1899      });
1900    }
1901  }
1902  return tracks;
1903}
1904
1905// TODO: Check if the last moof+mdat pair is part of the valid range
1906function segmentValidRange(data) {
1907  const segmentedRange = {
1908    valid: null,
1909    remainder: null
1910  };
1911  const moofs = findBox(data, ['moof']);
1912  if (moofs.length < 2) {
1913    segmentedRange.remainder = data;
1914    return segmentedRange;
1915  }
1916  const last = moofs[moofs.length - 1];
1917  // Offset by 8 bytes; findBox offsets the start by as much
1918  segmentedRange.valid = data.slice(0, last.byteOffset - 8);
1919  segmentedRange.remainder = data.slice(last.byteOffset - 8);
1920  return segmentedRange;
1921}
1922function appendUint8Array(data1, data2) {
1923  const temp = new Uint8Array(data1.length + data2.length);
1924  temp.set(data1);
1925  temp.set(data2, data1.length);
1926  return temp;
1927}
1928function parseSamples(timeOffset, track) {
1929  const seiSamples = [];
1930  const videoData = track.samples;
1931  const timescale = track.timescale;
1932  const trackId = track.id;
1933  let isHEVCFlavor = false;
1934  const moofs = findBox(videoData, ['moof']);
1935  moofs.map(moof => {
1936    const moofOffset = moof.byteOffset - 8;
1937    const trafs = findBox(moof, ['traf']);
1938    trafs.map(traf => {
1939      // get the base media decode time from the tfdt
1940      const baseTime = findBox(traf, ['tfdt']).map(tfdt => {
1941        const version = tfdt[0];
1942        let result = readUint32(tfdt, 4);
1943        if (version === 1) {
1944          result *= Math.pow(2, 32);
1945          result += readUint32(tfdt, 8);
1946        }
1947        return result / timescale;
1948      })[0];
1949      if (baseTime !== undefined) {
1950        timeOffset = baseTime;
1951      }
1952      return findBox(traf, ['tfhd']).map(tfhd => {
1953        const id = readUint32(tfhd, 4);
1954        const tfhdFlags = readUint32(tfhd, 0) & 0xffffff;
1955        const baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0;
1956        const sampleDescriptionIndexPresent = (tfhdFlags & 0x000002) !== 0;
1957        const defaultSampleDurationPresent = (tfhdFlags & 0x000008) !== 0;
1958        let defaultSampleDuration = 0;
1959        const defaultSampleSizePresent = (tfhdFlags & 0x000010) !== 0;
1960        let defaultSampleSize = 0;
1961        const defaultSampleFlagsPresent = (tfhdFlags & 0x000020) !== 0;
1962        let tfhdOffset = 8;
1963        if (id === trackId) {
1964          if (baseDataOffsetPresent) {
1965            tfhdOffset += 8;
1966          }
1967          if (sampleDescriptionIndexPresent) {
1968            tfhdOffset += 4;
1969          }
1970          if (defaultSampleDurationPresent) {
1971            defaultSampleDuration = readUint32(tfhd, tfhdOffset);
1972            tfhdOffset += 4;
1973          }
1974          if (defaultSampleSizePresent) {
1975            defaultSampleSize = readUint32(tfhd, tfhdOffset);
1976            tfhdOffset += 4;
1977          }
1978          if (defaultSampleFlagsPresent) {
1979            tfhdOffset += 4;
1980          }
1981          if (track.type === 'video') {
1982            isHEVCFlavor = isHEVC(track.codec);
1983          }
1984          findBox(traf, ['trun']).map(trun => {
1985            const version = trun[0];
1986            const flags = readUint32(trun, 0) & 0xffffff;
1987            const dataOffsetPresent = (flags & 0x000001) !== 0;
1988            let dataOffset = 0;
1989            const firstSampleFlagsPresent = (flags & 0x000004) !== 0;
1990            const sampleDurationPresent = (flags & 0x000100) !== 0;
1991            let sampleDuration = 0;
1992            const sampleSizePresent = (flags & 0x000200) !== 0;
1993            let sampleSize = 0;
1994            const sampleFlagsPresent = (flags & 0x000400) !== 0;
1995            const sampleCompositionOffsetsPresent = (flags & 0x000800) !== 0;
1996            let compositionOffset = 0;
1997            const sampleCount = readUint32(trun, 4);
1998            let trunOffset = 8; // past version, flags, and sample count
1999
2000            if (dataOffsetPresent) {
2001              dataOffset = readUint32(trun, trunOffset);
2002              trunOffset += 4;
2003            }
2004            if (firstSampleFlagsPresent) {
2005              trunOffset += 4;
2006            }
2007            let sampleOffset = dataOffset + moofOffset;
2008            for (let ix = 0; ix < sampleCount; ix++) {
2009              if (sampleDurationPresent) {
2010                sampleDuration = readUint32(trun, trunOffset);
2011                trunOffset += 4;
2012              } else {
2013                sampleDuration = defaultSampleDuration;
2014              }
2015              if (sampleSizePresent) {
2016                sampleSize = readUint32(trun, trunOffset);
2017                trunOffset += 4;
2018              } else {
2019                sampleSize = defaultSampleSize;
2020              }
2021              if (sampleFlagsPresent) {
2022                trunOffset += 4;
2023              }
2024              if (sampleCompositionOffsetsPresent) {
2025                if (version === 0) {
2026                  compositionOffset = readUint32(trun, trunOffset);
2027                } else {
2028                  compositionOffset = readSint32(trun, trunOffset);
2029                }
2030                trunOffset += 4;
2031              }
2032              if (track.type === ElementaryStreamTypes.VIDEO) {
2033                let naluTotalSize = 0;
2034                while (naluTotalSize < sampleSize) {
2035                  const naluSize = readUint32(videoData, sampleOffset);
2036                  sampleOffset += 4;
2037                  if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) {
2038                    const data = videoData.subarray(sampleOffset, sampleOffset + naluSize);
2039                    parseSEIMessageFromNALu(data, isHEVCFlavor ? 2 : 1, timeOffset + compositionOffset / timescale, seiSamples);
2040                  }
2041                  sampleOffset += naluSize;
2042                  naluTotalSize += naluSize + 4;
2043                }
2044              }
2045              timeOffset += sampleDuration / timescale;
2046            }
2047          });
2048        }
2049      });
2050    });
2051  });
2052  return seiSamples;
2053}
2054function isHEVC(codec) {
2055  if (!codec) {
2056    return false;
2057  }
2058  const baseCodec = codec.substring(0, 4);
2059  return baseCodec === 'hvc1' || baseCodec === 'hev1' ||
2060  // Dolby Vision
2061  baseCodec === 'dvh1' || baseCodec === 'dvhe';
2062}
2063function isSEIMessage(isHEVCFlavor, naluHeader) {
2064  if (isHEVCFlavor) {
2065    const naluType = naluHeader >> 1 & 0x3f;
2066    return naluType === 39 || naluType === 40;
2067  } else {
2068    const naluType = naluHeader & 0x1f;
2069    return naluType === 6;
2070  }
2071}
2072function parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples) {
2073  const data = discardEPB(unescapedData);
2074  let seiPtr = 0;
2075  // skip nal header
2076  seiPtr += headerSize;
2077  let payloadType = 0;
2078  let payloadSize = 0;
2079  let b = 0;
2080  while (seiPtr < data.length) {
2081    payloadType = 0;
2082    do {
2083      if (seiPtr >= data.length) {
2084        break;
2085      }
2086      b = data[seiPtr++];
2087      payloadType += b;
2088    } while (b === 0xff);
2089
2090    // Parse payload size.
2091    payloadSize = 0;
2092    do {
2093      if (seiPtr >= data.length) {
2094        break;
2095      }
2096      b = data[seiPtr++];
2097      payloadSize += b;
2098    } while (b === 0xff);
2099    const leftOver = data.length - seiPtr;
2100    // Create a variable to process the payload
2101    let payPtr = seiPtr;
2102
2103    // Increment the seiPtr to the end of the payload
2104    if (payloadSize < leftOver) {
2105      seiPtr += payloadSize;
2106    } else if (payloadSize > leftOver) {
2107      // Some type of corruption has happened?
2108      logger.error(`Malformed SEI payload. ${payloadSize} is too small, only ${leftOver} bytes left to parse.`);
2109      // We might be able to parse some data, but let's be safe and ignore it.
2110      break;
2111    }
2112    if (payloadType === 4) {
2113      const countryCode = data[payPtr++];
2114      if (countryCode === 181) {
2115        const providerCode = readUint16(data, payPtr);
2116        payPtr += 2;
2117        if (providerCode === 49) {
2118          const userStructure = readUint32(data, payPtr);
2119          payPtr += 4;
2120          if (userStructure === 0x47413934) {
2121            const userDataType = data[payPtr++];
2122
2123            // Raw CEA-608 bytes wrapped in CEA-708 packet
2124            if (userDataType === 3) {
2125              const firstByte = data[payPtr++];
2126              const totalCCs = 0x1f & firstByte;
2127              const enabled = 0x40 & firstByte;
2128              const totalBytes = enabled ? 2 + totalCCs * 3 : 0;
2129              const byteArray = new Uint8Array(totalBytes);
2130              if (enabled) {
2131                byteArray[0] = firstByte;
2132                for (let i = 1; i < totalBytes; i++) {
2133                  byteArray[i] = data[payPtr++];
2134                }
2135              }
2136              samples.push({
2137                type: userDataType,
2138                payloadType,
2139                pts,
2140                bytes: byteArray
2141              });
2142            }
2143          }
2144        }
2145      }
2146    } else if (payloadType === 5) {
2147      if (payloadSize > 16) {
2148        const uuidStrArray = [];
2149        for (let i = 0; i < 16; i++) {
2150          const _b = data[payPtr++].toString(16);
2151          uuidStrArray.push(_b.length == 1 ? '0' + _b : _b);
2152          if (i === 3 || i === 5 || i === 7 || i === 9) {
2153            uuidStrArray.push('-');
2154          }
2155        }
2156        const length = payloadSize - 16;
2157        const userDataBytes = new Uint8Array(length);
2158        for (let i = 0; i < length; i++) {
2159          userDataBytes[i] = data[payPtr++];
2160        }
2161        samples.push({
2162          payloadType,
2163          pts,
2164          uuid: uuidStrArray.join(''),
2165          userData: utf8ArrayToStr(userDataBytes),
2166          userDataBytes
2167        });
2168      }
2169    }
2170  }
2171}
2172
2173/**
2174 * remove Emulation Prevention bytes from a RBSP
2175 */
2176function discardEPB(data) {
2177  const length = data.byteLength;
2178  const EPBPositions = [];
2179  let i = 1;
2180
2181  // Find all `Emulation Prevention Bytes`
2182  while (i < length - 2) {
2183    if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
2184      EPBPositions.push(i + 2);
2185      i += 2;
2186    } else {
2187      i++;
2188    }
2189  }
2190
2191  // If no Emulation Prevention Bytes were found just return the original
2192  // array
2193  if (EPBPositions.length === 0) {
2194    return data;
2195  }
2196
2197  // Create a new array to hold the NAL unit data
2198  const newLength = length - EPBPositions.length;
2199  const newData = new Uint8Array(newLength);
2200  let sourceIndex = 0;
2201  for (i = 0; i < newLength; sourceIndex++, i++) {
2202    if (sourceIndex === EPBPositions[0]) {
2203      // Skip this byte
2204      sourceIndex++;
2205      // Remove this position index
2206      EPBPositions.shift();
2207    }
2208    newData[i] = data[sourceIndex];
2209  }
2210  return newData;
2211}
2212function parseEmsg(data) {
2213  const version = data[0];
2214  let schemeIdUri = '';
2215  let value = '';
2216  let timeScale = 0;
2217  let presentationTimeDelta = 0;
2218  let presentationTime = 0;
2219  let eventDuration = 0;
2220  let id = 0;
2221  let offset = 0;
2222  if (version === 0) {
2223    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
2224      schemeIdUri += bin2str(data.subarray(offset, offset + 1));
2225      offset += 1;
2226    }
2227    schemeIdUri += bin2str(data.subarray(offset, offset + 1));
2228    offset += 1;
2229    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
2230      value += bin2str(data.subarray(offset, offset + 1));
2231      offset += 1;
2232    }
2233    value += bin2str(data.subarray(offset, offset + 1));
2234    offset += 1;
2235    timeScale = readUint32(data, 12);
2236    presentationTimeDelta = readUint32(data, 16);
2237    eventDuration = readUint32(data, 20);
2238    id = readUint32(data, 24);
2239    offset = 28;
2240  } else if (version === 1) {
2241    offset += 4;
2242    timeScale = readUint32(data, offset);
2243    offset += 4;
2244    const leftPresentationTime = readUint32(data, offset);
2245    offset += 4;
2246    const rightPresentationTime = readUint32(data, offset);
2247    offset += 4;
2248    presentationTime = 2 ** 32 * leftPresentationTime + rightPresentationTime;
2249    if (!isSafeInteger(presentationTime)) {
2250      presentationTime = Number.MAX_SAFE_INTEGER;
2251      logger.warn('Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box');
2252    }
2253    eventDuration = readUint32(data, offset);
2254    offset += 4;
2255    id = readUint32(data, offset);
2256    offset += 4;
2257    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
2258      schemeIdUri += bin2str(data.subarray(offset, offset + 1));
2259      offset += 1;
2260    }
2261    schemeIdUri += bin2str(data.subarray(offset, offset + 1));
2262    offset += 1;
2263    while (bin2str(data.subarray(offset, offset + 1)) !== '\0') {
2264      value += bin2str(data.subarray(offset, offset + 1));
2265      offset += 1;
2266    }
2267    value += bin2str(data.subarray(offset, offset + 1));
2268    offset += 1;
2269  }
2270  const payload = data.subarray(offset, data.byteLength);
2271  return {
2272    schemeIdUri,
2273    value,
2274    timeScale,
2275    presentationTime,
2276    presentationTimeDelta,
2277    eventDuration,
2278    id,
2279    payload
2280  };
2281}
2282function mp4Box(type, ...payload) {
2283  const len = payload.length;
2284  let size = 8;
2285  let i = len;
2286  while (i--) {
2287    size += payload[i].byteLength;
2288  }
2289  const result = new Uint8Array(size);
2290  result[0] = size >> 24 & 0xff;
2291  result[1] = size >> 16 & 0xff;
2292  result[2] = size >> 8 & 0xff;
2293  result[3] = size & 0xff;
2294  result.set(type, 4);
2295  for (i = 0, size = 8; i < len; i++) {
2296    result.set(payload[i], size);
2297    size += payload[i].byteLength;
2298  }
2299  return result;
2300}
2301function mp4pssh(systemId, keyids, data) {
2302  if (systemId.byteLength !== 16) {
2303    throw new RangeError('Invalid system id');
2304  }
2305  let version;
2306  let kids;
2307  {
2308    version = 0;
2309    kids = new Uint8Array();
2310  }
2311  let kidCount;
2312  if (version > 0) {
2313    kidCount = new Uint8Array(4);
2314    if (keyids.length > 0) {
2315      new DataView(kidCount.buffer).setUint32(0, keyids.length, false);
2316    }
2317  } else {
2318    kidCount = new Uint8Array();
2319  }
2320  const dataSize = new Uint8Array(4);
2321  if (data.byteLength > 0) {
2322    new DataView(dataSize.buffer).setUint32(0, data.byteLength, false);
2323  }
2324  return mp4Box([112, 115, 115, 104], new Uint8Array([version, 0x00, 0x00, 0x00 // Flags
2325  ]), systemId,
2326  // 16 bytes
2327  kidCount, kids, dataSize, data);
2328}
2329function parseMultiPssh(initData) {
2330  const results = [];
2331  if (initData instanceof ArrayBuffer) {
2332    const length = initData.byteLength;
2333    let offset = 0;
2334    while (offset + 32 < length) {
2335      const view = new DataView(initData, offset);
2336      const pssh = parsePssh(view);
2337      results.push(pssh);
2338      offset += pssh.size;
2339    }
2340  }
2341  return results;
2342}
2343function parsePssh(view) {
2344  const size = view.getUint32(0);
2345  const offset = view.byteOffset;
2346  const length = view.byteLength;
2347  if (length < size) {
2348    return {
2349      offset,
2350      size: length
2351    };
2352  }
2353  const type = view.getUint32(4);
2354  if (type !== 0x70737368) {
2355    return {
2356      offset,
2357      size
2358    };
2359  }
2360  const version = view.getUint32(8) >>> 24;
2361  if (version !== 0 && version !== 1) {
2362    return {
2363      offset,
2364      size
2365    };
2366  }
2367  const buffer = view.buffer;
2368  const systemId = arrayToHex(new Uint8Array(buffer, offset + 12, 16));
2369  let kids = null;
2370  let data = null;
2371  let dataSizeOffset = 0;
2372  if (version === 0) {
2373    dataSizeOffset = 28;
2374  } else {
2375    const kidCounts = view.getUint32(28);
2376    if (!kidCounts || length < 32 + kidCounts * 16) {
2377      return {
2378        offset,
2379        size
2380      };
2381    }
2382    kids = [];
2383    for (let i = 0; i < kidCounts; i++) {
2384      kids.push(new Uint8Array(buffer, offset + 32 + i * 16, 16));
2385    }
2386    dataSizeOffset = 32 + kidCounts * 16;
2387  }
2388  if (!dataSizeOffset) {
2389    return {
2390      offset,
2391      size
2392    };
2393  }
2394  const dataSizeOrKidCount = view.getUint32(dataSizeOffset);
2395  if (size - 32 < dataSizeOrKidCount) {
2396    return {
2397      offset,
2398      size
2399    };
2400  }
2401  data = new Uint8Array(buffer, offset + dataSizeOffset + 4, dataSizeOrKidCount);
2402  return {
2403    version,
2404    systemId,
2405    kids,
2406    data,
2407    offset,
2408    size
2409  };
2410}
2411
2412const userAgentHevcSupportIsInaccurate = () => {
2413  return /\(Windows.+Firefox\//i.test(navigator.userAgent);
2414};
2415
2416// from http://mp4ra.org/codecs.html
2417// values indicate codec selection preference (lower is higher priority)
2418const sampleEntryCodesISO = {
2419  audio: {
2420    a3ds: 1,
2421    'ac-3': 0.95,
2422    'ac-4': 1,
2423    alac: 0.9,
2424    alaw: 1,
2425    dra1: 1,
2426    'dts+': 1,
2427    'dts-': 1,
2428    dtsc: 1,
2429    dtse: 1,
2430    dtsh: 1,
2431    'ec-3': 0.9,
2432    enca: 1,
2433    fLaC: 0.9,
2434    // MP4-RA listed codec entry for FLAC
2435    flac: 0.9,
2436    // legacy browser codec name for FLAC
2437    FLAC: 0.9,
2438    // some manifests may list "FLAC" with Apple's tools
2439    g719: 1,
2440    g726: 1,
2441    m4ae: 1,
2442    mha1: 1,
2443    mha2: 1,
2444    mhm1: 1,
2445    mhm2: 1,
2446    mlpa: 1,
2447    mp4a: 1,
2448    'raw ': 1,
2449    Opus: 1,
2450    opus: 1,
2451    // browsers expect this to be lowercase despite MP4RA says 'Opus'
2452    samr: 1,
2453    sawb: 1,
2454    sawp: 1,
2455    sevc: 1,
2456    sqcp: 1,
2457    ssmv: 1,
2458    twos: 1,
2459    ulaw: 1
2460  },
2461  video: {
2462    avc1: 1,
2463    avc2: 1,
2464    avc3: 1,
2465    avc4: 1,
2466    avcp: 1,
2467    av01: 0.8,
2468    dav1: 0.8,
2469    drac: 1,
2470    dva1: 1,
2471    dvav: 1,
2472    dvh1: 0.7,
2473    dvhe: 0.7,
2474    encv: 1,
2475    hev1: 0.75,
2476    hvc1: 0.75,
2477    mjp2: 1,
2478    mp4v: 1,
2479    mvc1: 1,
2480    mvc2: 1,
2481    mvc3: 1,
2482    mvc4: 1,
2483    resv: 1,
2484    rv60: 1,
2485    s263: 1,
2486    svc1: 1,
2487    svc2: 1,
2488    'vc-1': 1,
2489    vp08: 1,
2490    vp09: 0.9
2491  },
2492  text: {
2493    stpp: 1,
2494    wvtt: 1
2495  }
2496};
2497function isCodecType(codec, type) {
2498  const typeCodes = sampleEntryCodesISO[type];
2499  return !!typeCodes && !!typeCodes[codec.slice(0, 4)];
2500}
2501function areCodecsMediaSourceSupported(codecs, type, preferManagedMediaSource = true) {
2502  return !codecs.split(',').some(codec => !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource));
2503}
2504function isCodecMediaSourceSupported(codec, type, preferManagedMediaSource = true) {
2505  var _MediaSource$isTypeSu;
2506  const MediaSource = getMediaSource(preferManagedMediaSource);
2507  return (_MediaSource$isTypeSu = MediaSource == null ? void 0 : MediaSource.isTypeSupported(mimeTypeForCodec(codec, type))) != null ? _MediaSource$isTypeSu : false;
2508}
2509function mimeTypeForCodec(codec, type) {
2510  return `${type}/mp4;codecs=${codec}`;
2511}
2512function videoCodecPreferenceValue(videoCodec) {
2513  if (videoCodec) {
2514    const fourCC = videoCodec.substring(0, 4);
2515    return sampleEntryCodesISO.video[fourCC];
2516  }
2517  return 2;
2518}
2519function codecsSetSelectionPreferenceValue(codecSet) {
2520  const limitedHevcSupport = userAgentHevcSupportIsInaccurate();
2521  return codecSet.split(',').reduce((num, fourCC) => {
2522    const lowerPriority = limitedHevcSupport && isHEVC(fourCC);
2523    const preferenceValue = lowerPriority ? 9 : sampleEntryCodesISO.video[fourCC];
2524    if (preferenceValue) {
2525      return (preferenceValue * 2 + num) / (num ? 3 : 2);
2526    }
2527    return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2 : 1);
2528  }, 0);
2529}
2530const CODEC_COMPATIBLE_NAMES = {};
2531function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource = true) {
2532  if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
2533    return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
2534  }
2535  const codecsToCheck = {
2536    // Idealy fLaC and Opus would be first (spec-compliant) but
2537    // some browsers will report that fLaC is supported then fail.
2538    // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
2539    flac: ['flac', 'fLaC', 'FLAC'],
2540    opus: ['opus', 'Opus'],
2541    // Replace audio codec info if browser does not support mp4a.40.34,
2542    // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
2543    'mp4a.40.34': ['mp3']
2544  }[lowerCaseCodec];
2545  for (let i = 0; i < codecsToCheck.length; i++) {
2546    var _getMediaSource;
2547    if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
2548      CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
2549      return codecsToCheck[i];
2550    } else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
2551      return '';
2552    }
2553  }
2554  return lowerCaseCodec;
2555}
2556const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
2557function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
2558  return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
2559}
2560function replaceVideoCodec(originalCodecs, newVideoCodec) {
2561  const codecs = [];
2562  if (originalCodecs) {
2563    const allCodecs = originalCodecs.split(',');
2564    for (let i = 0; i < allCodecs.length; i++) {
2565      if (!isCodecType(allCodecs[i], 'video')) {
2566        codecs.push(allCodecs[i]);
2567      }
2568    }
2569  }
2570  if (newVideoCodec) {
2571    codecs.push(newVideoCodec);
2572  }
2573  return codecs.join(',');
2574}
2575function pickMostCompleteCodecName(parsedCodec, levelCodec) {
2576  // Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a
2577  // so use level codec is parsed codec is unavailable or incomplete
2578  if (parsedCodec && (parsedCodec.length > 4 || ['ac-3', 'ec-3', 'alac', 'fLaC', 'Opus'].indexOf(parsedCodec) !== -1)) {
2579    if (isCodecSupportedAsType(parsedCodec, 'audio') || isCodecSupportedAsType(parsedCodec, 'video')) {
2580      return parsedCodec;
2581    }
2582  }
2583  if (levelCodec) {
2584    const levelCodecs = levelCodec.split(',');
2585    if (levelCodecs.length > 1) {
2586      if (parsedCodec) {
2587        for (let i = levelCodecs.length; i--;) {
2588          if (levelCodecs[i].substring(0, 4) === parsedCodec.substring(0, 4)) {
2589            return levelCodecs[i];
2590          }
2591        }
2592      }
2593      return levelCodecs[0];
2594    }
2595  }
2596  return levelCodec || parsedCodec;
2597}
2598function isCodecSupportedAsType(codec, type) {
2599  return isCodecType(codec, type) && isCodecMediaSourceSupported(codec, type);
2600}
2601function convertAVC1ToAVCOTI(videoCodecs) {
2602  // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported
2603  // Examples: avc1.66.30 to avc1.42001e and avc1.77.30,avc1.66.30 to avc1.4d001e,avc1.42001e.
2604  const codecs = videoCodecs.split(',');
2605  for (let i = 0; i < codecs.length; i++) {
2606    const avcdata = codecs[i].split('.');
2607    // only convert codec strings starting with avc1 (Examples: avc1.64001f,dvh1.05.07)
2608    if (avcdata.length > 2 && avcdata[0] === 'avc1') {
2609      codecs[i] = `avc1.${parseInt(avcdata[1]).toString(16)}${('000' + parseInt(avcdata[2]).toString(16)).slice(-4)}`;
2610    }
2611  }
2612  return codecs.join(',');
2613}
2614function fillInMissingAV01Params(videoCodec) {
2615  // Used to fill in incomplete AV1 playlist CODECS strings for mediaCapabilities.decodingInfo queries
2616  if (videoCodec.startsWith('av01.')) {
2617    const av1params = videoCodec.split('.');
2618    const placeholders = ['0', '111', '01', '01', '01', '0'];
2619    for (let i = av1params.length; i > 4 && i < 10; i++) {
2620      av1params[i] = placeholders[i - 4];
2621    }
2622    return av1params.join('.');
2623  }
2624  return videoCodec;
2625}
2626function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
2627  const MediaSource = getMediaSource(preferManagedMediaSource) || {
2628    isTypeSupported: () => false
2629  };
2630  return {
2631    mpeg: MediaSource.isTypeSupported('audio/mpeg'),
2632    mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
2633    ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"') 
2634  };
2635}
2636function getCodecsForMimeType(mimeType) {
2637  return mimeType.replace(/^.+codecs=["']?([^"']+).*$/, '$1');
2638}
2639
2640// @ts-ignore
2641const supportedResult = {
2642  supported: true,
2643  powerEfficient: true,
2644  smooth: true
2645  // keySystemAccess: null,
2646};
2647
2648// @ts-ignore
2649const unsupportedResult = {
2650  supported: false,
2651  smooth: false,
2652  powerEfficient: false
2653  // keySystemAccess: null,
2654};
2655const SUPPORTED_INFO_DEFAULT = {
2656  supported: true,
2657  configurations: [],
2658  decodingInfoResults: [supportedResult]
2659};
2660function getUnsupportedResult(error, configurations) {
2661  return {
2662    supported: false,
2663    configurations,
2664    decodingInfoResults: [unsupportedResult],
2665    error
2666  };
2667}
2668function requiresMediaCapabilitiesDecodingInfo(level, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference) {
2669  // Only test support when configuration is exceeds minimum options
2670  const videoCodecs = level.videoCodec;
2671  const audioGroups = level.audioCodec ? level.audioGroups : null;
2672  const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec;
2673  const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels;
2674  const maxChannels = channelsPreference ? parseInt(channelsPreference) : audioCodecPreference ? Infinity : 2;
2675  let audioChannels = null;
2676  if (audioGroups != null && audioGroups.length) {
2677    try {
2678      if (audioGroups.length === 1 && audioGroups[0]) {
2679        audioChannels = audioTracksByGroup.groups[audioGroups[0]].channels;
2680      } else {
2681        audioChannels = audioGroups.reduce((acc, groupId) => {
2682          if (groupId) {
2683            const audioTrackGroup = audioTracksByGroup.groups[groupId];
2684            if (!audioTrackGroup) {
2685              throw new Error(`Audio track group ${groupId} not found`);
2686            }
2687            // Sum all channel key values
2688            Object.keys(audioTrackGroup.channels).forEach(key => {
2689              acc[key] = (acc[key] || 0) + audioTrackGroup.channels[key];
2690            });
2691          }
2692          return acc;
2693        }, {
2694          2: 0
2695        });
2696      }
2697    } catch (error) {
2698      return true;
2699    }
2700  }
2701  return videoCodecs !== undefined && (
2702  // Force media capabilities check for HEVC to avoid failure on Windows
2703  videoCodecs.split(',').some(videoCodec => isHEVC(videoCodec)) || 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);
2704}
2705function getMediaDecodingInfoPromise(level, audioTracksByGroup, mediaCapabilities, cache = {}) {
2706  const videoCodecs = level.videoCodec;
2707  if (!videoCodecs && !level.audioCodec || !mediaCapabilities) {
2708    return Promise.resolve(SUPPORTED_INFO_DEFAULT);
2709  }
2710  const configurations = [];
2711  const videoDecodeList = makeVideoConfigurations(level);
2712  const videoCount = videoDecodeList.length;
2713  const audioDecodeList = makeAudioConfigurations(level, audioTracksByGroup, videoCount > 0);
2714  const audioCount = audioDecodeList.length;
2715  for (let i = videoCount || 1 * audioCount || 1; i--;) {
2716    const configuration = {
2717      type: 'media-source'
2718    };
2719    if (videoCount) {
2720      configuration.video = videoDecodeList[i % videoCount];
2721    }
2722    if (audioCount) {
2723      configuration.audio = audioDecodeList[i % audioCount];
2724      const audioBitrate = configuration.audio.bitrate;
2725      if (configuration.video && audioBitrate) {
2726        configuration.video.bitrate -= audioBitrate;
2727      }
2728    }
2729    configurations.push(configuration);
2730  }
2731  if (videoCodecs) {
2732    // Override Windows Firefox HEVC MediaCapabilities result (https://github.com/video-dev/hls.js/issues/7046)
2733    const ua = navigator.userAgent;
2734    if (videoCodecs.split(',').some(videoCodec => isHEVC(videoCodec)) && userAgentHevcSupportIsInaccurate()) {
2735      return Promise.resolve(getUnsupportedResult(new Error(`Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent string: (${ua})`), configurations));
2736    }
2737  }
2738  return Promise.all(configurations.map(configuration => {
2739    // Cache MediaCapabilities promises
2740    const decodingInfoKey = getMediaDecodingInfoKey(configuration);
2741    return cache[decodingInfoKey] || (cache[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration));
2742  })).then(decodingInfoResults => ({
2743    supported: !decodingInfoResults.some(info => !info.supported),
2744    configurations,
2745    decodingInfoResults
2746  })).catch(error => ({
2747    supported: false,
2748    configurations,
2749    decodingInfoResults: [],
2750    error
2751  }));
2752}
2753function makeVideoConfigurations(level) {
2754  var _level$videoCodec;
2755  const videoCodecs = (_level$videoCodec = level.videoCodec) == null ? void 0 : _level$videoCodec.split(',');
2756  const bitrate = getVariantDecodingBitrate(level);
2757  const width = level.width || 640;
2758  const height = level.height || 480;
2759  // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0.
2760  const framerate = level.frameRate || 30;
2761  const videoRange = level.videoRange.toLowerCase();
2762  return videoCodecs ? videoCodecs.map(videoCodec => {
2763    const videoConfiguration = {
2764      contentType: mimeTypeForCodec(fillInMissingAV01Params(videoCodec), 'video'),
2765      width,
2766      height,
2767      bitrate,
2768      framerate
2769    };
2770    if (videoRange !== 'sdr') {
2771      videoConfiguration.transferFunction = videoRange;
2772    }
2773    return videoConfiguration;
2774  }) : [];
2775}
2776function makeAudioConfigurations(level, audioTracksByGroup, hasVideo) {
2777  var _level$audioCodec;
2778  const audioCodecs = (_level$audioCodec = level.audioCodec) == null ? void 0 : _level$audioCodec.split(',');
2779  const combinedBitrate = getVariantDecodingBitrate(level);
2780  if (audioCodecs && level.audioGroups) {
2781    return level.audioGroups.reduce((configurations, audioGroupId) => {
2782      var _audioTracksByGroup$g;
2783      const tracks = audioGroupId ? (_audioTracksByGroup$g = audioTracksByGroup.groups[audioGroupId]) == null ? void 0 : _audioTracksByGroup$g.tracks : null;
2784      if (tracks) {
2785        return tracks.reduce((configs, audioTrack) => {
2786          if (audioTrack.groupId === audioGroupId) {
2787            const channelsNumber = parseFloat(audioTrack.channels || '');
2788            audioCodecs.forEach(audioCodec => {
2789              const audioConfiguration = {
2790                contentType: mimeTypeForCodec(audioCodec, 'audio'),
2791                bitrate: hasVideo ? estimatedAudioBitrate(audioCodec, combinedBitrate) : combinedBitrate
2792              };
2793              if (channelsNumber) {
2794                audioConfiguration.channels = '' + channelsNumber;
2795              }
2796              configs.push(audioConfiguration);
2797            });
2798          }
2799          return configs;
2800        }, configurations);
2801      }
2802      return configurations;
2803    }, []);
2804  }
2805  return [];
2806}
2807function estimatedAudioBitrate(audioCodec, levelBitrate) {
2808  if (levelBitrate <= 1) {
2809    return 1;
2810  }
2811  let audioBitrate = 128000;
2812  if (audioCodec === 'ec-3') {
2813    audioBitrate = 768000;
2814  } else if (audioCodec === 'ac-3') {
2815    audioBitrate = 640000;
2816  }
2817  return Math.min(levelBitrate / 2, audioBitrate); // Don't exceed some % of level bitrate
2818}
2819function getVariantDecodingBitrate(level) {
2820  return Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate) / 1000) * 1000 || 1;
2821}
2822function getMediaDecodingInfoKey(config) {
2823  let key = '';
2824  const {
2825    audio,
2826    video
2827  } = config;
2828  if (video) {
2829    const codec = getCodecsForMimeType(video.contentType);
2830    key += `${codec}_r${video.height}x${video.width}f${Math.ceil(video.framerate)}${video.transferFunction || 'sd'}_${Math.ceil(video.bitrate / 1e5)}`;
2831  }
2832  if (audio) {
2833    const codec = getCodecsForMimeType(audio.contentType);
2834    key += `${video ? '_' : ''}${codec}_c${audio.channels}`;
2835  }
2836  return key;
2837}
2838
2839const HdcpLevels = ['NONE', 'TYPE-0', 'TYPE-1', null];
2840function isHdcpLevel(value) {
2841  return HdcpLevels.indexOf(value) > -1;
2842}
2843const VideoRangeValues = ['SDR', 'PQ', 'HLG'];
2844function isVideoRange(value) {
2845  return !!value && VideoRangeValues.indexOf(value) > -1;
2846}
2847var HlsSkip = {
2848  No: "",
2849  Yes: "YES",
2850  v2: "v2"
2851};
2852function getSkipValue(details) {
2853  const {
2854    canSkipUntil,
2855    canSkipDateRanges,
2856    age
2857  } = details;
2858  // A Client SHOULD NOT request a Playlist Delta Update unless it already
2859  // has a version of the Playlist that is no older than one-half of the Skip Boundary.
2860  // @see: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.3.7
2861  const playlistRecentEnough = age < canSkipUntil / 2;
2862  if (canSkipUntil && playlistRecentEnough) {
2863    if (canSkipDateRanges) {
2864      return HlsSkip.v2;
2865    }
2866    return HlsSkip.Yes;
2867  }
2868  return HlsSkip.No;
2869}
2870class HlsUrlParameters {
2871  constructor(msn, part, skip) {
2872    this.msn = void 0;
2873    this.part = void 0;
2874    this.skip = void 0;
2875    this.msn = msn;
2876    this.part = part;
2877    this.skip = skip;
2878  }
2879  addDirectives(uri) {
2880    const url = new self.URL(uri);
2881    if (this.msn !== undefined) {
2882      url.searchParams.set('_HLS_msn', this.msn.toString());
2883    }
2884    if (this.part !== undefined) {
2885      url.searchParams.set('_HLS_part', this.part.toString());
2886    }
2887    if (this.skip) {
2888      url.searchParams.set('_HLS_skip', this.skip);
2889    }
2890    return url.href;
2891  }
2892}
2893class Level {
2894  constructor(data) {
2895    this._attrs = void 0;
2896    this.audioCodec = void 0;
2897    this.bitrate = void 0;
2898    this.codecSet = void 0;
2899    this.url = void 0;
2900    this.frameRate = void 0;
2901    this.height = void 0;
2902    this.id = void 0;
2903    this.name = void 0;
2904    this.supplemental = void 0;
2905    this.videoCodec = void 0;
2906    this.width = void 0;
2907    this.details = void 0;
2908    this.fragmentError = 0;
2909    this.loadError = 0;
2910    this.loaded = void 0;
2911    this.realBitrate = 0;
2912    this.supportedPromise = void 0;
2913    this.supportedResult = void 0;
2914    this._avgBitrate = 0;
2915    this._audioGroups = void 0;
2916    this._subtitleGroups = void 0;
2917    // Deprecated (retained for backwards compatibility)
2918    this._urlId = 0;
2919    this.url = [data.url];
2920    this._attrs = [data.attrs];
2921    this.bitrate = data.bitrate;
2922    if (data.details) {
2923      this.details = data.details;
2924    }
2925    this.id = data.id || 0;
2926    this.name = data.name;
2927    this.width = data.width || 0;
2928    this.height = data.height || 0;
2929    this.frameRate = data.attrs.optionalFloat('FRAME-RATE', 0);
2930    this._avgBitrate = data.attrs.decimalInteger('AVERAGE-BANDWIDTH');
2931    this.audioCodec = data.audioCodec;
2932    this.videoCodec = data.videoCodec;
2933    this.codecSet = [data.videoCodec, data.audioCodec].filter(c => !!c).map(s => s.substring(0, 4)).join(',');
2934    if ('supplemental' in data) {
2935      var _data$supplemental;
2936      this.supplemental = data.supplemental;
2937      const supplementalVideo = (_data$supplemental = data.supplemental) == null ? void 0 : _data$supplemental.videoCodec;
2938      if (supplementalVideo && supplementalVideo !== data.videoCodec) {
2939        this.codecSet += `,${supplementalVideo.substring(0, 4)}`;
2940      }
2941    }
2942    this.addGroupId('audio', data.attrs.AUDIO);
2943    this.addGroupId('text', data.attrs.SUBTITLES);
2944  }
2945  get maxBitrate() {
2946    return Math.max(this.realBitrate, this.bitrate);
2947  }
2948  get averageBitrate() {
2949    return this._avgBitrate || this.realBitrate || this.bitrate;
2950  }
2951  get attrs() {
2952    return this._attrs[0];
2953  }
2954  get codecs() {
2955    return this.attrs.CODECS || '';
2956  }
2957  get pathwayId() {
2958    return this.attrs['PATHWAY-ID'] || '.';
2959  }
2960  get videoRange() {
2961    return this.attrs['VIDEO-RANGE'] || 'SDR';
2962  }
2963  get score() {
2964    return this.attrs.optionalFloat('SCORE', 0);
2965  }
2966  get uri() {
2967    return this.url[0] || '';
2968  }
2969  hasAudioGroup(groupId) {
2970    return hasGroup(this._audioGroups, groupId);
2971  }
2972  hasSubtitleGroup(groupId) {
2973    return hasGroup(this._subtitleGroups, groupId);
2974  }
2975  get audioGroups() {
2976    return this._audioGroups;
2977  }
2978  get subtitleGroups() {
2979    return this._subtitleGroups;
2980  }
2981  addGroupId(type, groupId) {
2982    if (!groupId) {
2983      return;
2984    }
2985    if (type === 'audio') {
2986      let audioGroups = this._audioGroups;
2987      if (!audioGroups) {
2988        audioGroups = this._audioGroups = [];
2989      }
2990      if (audioGroups.indexOf(groupId) === -1) {
2991        audioGroups.push(groupId);
2992      }
2993    } else if (type === 'text') {
2994      let subtitleGroups = this._subtitleGroups;
2995      if (!subtitleGroups) {
2996        subtitleGroups = this._subtitleGroups = [];
2997      }
2998      if (subtitleGroups.indexOf(groupId) === -1) {
2999        subtitleGroups.push(groupId);
3000      }
3001    }
3002  }
3003
3004  // Deprecated methods (retained for backwards compatibility)
3005  get urlId() {
3006    return 0;
3007  }
3008  set urlId(value) {}
3009  get audioGroupIds() {
3010    return this.audioGroups ? [this.audioGroupId] : undefined;
3011  }
3012  get textGroupIds() {
3013    return this.subtitleGroups ? [this.textGroupId] : undefined;
3014  }
3015  get audioGroupId() {
3016    var _this$audioGroups;
3017    return (_this$audioGroups = this.audioGroups) == null ? void 0 : _this$audioGroups[0];
3018  }
3019  get textGroupId() {
3020    var _this$subtitleGroups;
3021    return (_this$subtitleGroups = this.subtitleGroups) == null ? void 0 : _this$subtitleGroups[0];
3022  }
3023  addFallback() {}
3024}
3025function hasGroup(groups, groupId) {
3026  if (!groupId || !groups) {
3027    return false;
3028  }
3029  return groups.indexOf(groupId) !== -1;
3030}
3031
3032/**
3033 * @returns Whether we can detect and validate HDR capability within the window context
3034 */
3035function isHdrSupported() {
3036  if (typeof matchMedia === 'function') {
3037    const mediaQueryList = matchMedia('(dynamic-range: high)');
3038    const badQuery = matchMedia('bad query');
3039    if (mediaQueryList.media !== badQuery.media) {
3040      return mediaQueryList.matches === true;
3041    }
3042  }
3043  return false;
3044}
3045
3046/**
3047 * Sanitizes inputs to return the active video selection options for HDR/SDR.
3048 * When both inputs are null:
3049 *
3050 *    `{ preferHDR: false, allowedVideoRanges: [] }`
3051 *
3052 * When `currentVideoRange` non-null, maintain the active range:
3053 *
3054 *    `{ preferHDR: currentVideoRange !== 'SDR', allowedVideoRanges: [currentVideoRange] }`
3055 *
3056 * When VideoSelectionOption non-null:
3057 *
3058 *  - Allow all video ranges if `allowedVideoRanges` unspecified.
3059 *  - If `preferHDR` is non-null use the value to filter `allowedVideoRanges`.
3060 *  - Else check window for HDR support and set `preferHDR` to the result.
3061 *
3062 * @param currentVideoRange
3063 * @param videoPreference
3064 */
3065function getVideoSelectionOptions(currentVideoRange, videoPreference) {
3066  let preferHDR = false;
3067  let allowedVideoRanges = [];
3068  if (currentVideoRange) {
3069    preferHDR = currentVideoRange !== 'SDR';
3070    allowedVideoRanges = [currentVideoRange];
3071  }
3072  if (videoPreference) {
3073    allowedVideoRanges = videoPreference.allowedVideoRanges || VideoRangeValues.slice(0);
3074    const allowAutoPreferHDR = allowedVideoRanges.join('') !== 'SDR' && !videoPreference.videoCodec;
3075    preferHDR = videoPreference.preferHDR !== undefined ? videoPreference.preferHDR : allowAutoPreferHDR && isHdrSupported();
3076    if (!preferHDR) {
3077      allowedVideoRanges = ['SDR'];
3078    }
3079  }
3080  return {
3081    preferHDR,
3082    allowedVideoRanges
3083  };
3084}
3085
3086const omitCircularRefsReplacer = replacer => {
3087  const known = new WeakSet();
3088  return (_, value) => {
3089    if (replacer) {
3090      value = replacer(_, value);
3091    }
3092    if (typeof value === 'object' && value !== null) {
3093      if (known.has(value)) {
3094        return;
3095      }
3096      known.add(value);
3097    }
3098    return value;
3099  };
3100};
3101const stringify = (object, replacer) => JSON.stringify(object, omitCircularRefsReplacer(replacer));
3102
3103function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference) {
3104  const codecSets = Object.keys(codecTiers);
3105  const channelsPreference = audioPreference == null ? void 0 : audioPreference.channels;
3106  const audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec;
3107  const videoCodecPreference = videoPreference == null ? void 0 : videoPreference.videoCodec;
3108  const preferStereo = channelsPreference && parseInt(channelsPreference) === 2;
3109  // Use first level set to determine stereo, and minimum resolution and framerate
3110  let hasStereo = false;
3111  let hasCurrentVideoRange = false;
3112  let minHeight = Infinity;
3113  let minFramerate = Infinity;
3114  let minBitrate = Infinity;
3115  let minIndex = Infinity;
3116  let selectedScore = 0;
3117  let videoRanges = [];
3118  const {
3119    preferHDR,
3120    allowedVideoRanges
3121  } = getVideoSelectionOptions(currentVideoRange, videoPreference);
3122  for (let i = codecSets.length; i--;) {
3123    const tier = codecTiers[codecSets[i]];
3124    hasStereo || (hasStereo = tier.channels[2] > 0);
3125    minHeight = Math.min(minHeight, tier.minHeight);
3126    minFramerate = Math.min(minFramerate, tier.minFramerate);
3127    minBitrate = Math.min(minBitrate, tier.minBitrate);
3128    const matchingVideoRanges = allowedVideoRanges.filter(range => tier.videoRanges[range] > 0);
3129    if (matchingVideoRanges.length > 0) {
3130      hasCurrentVideoRange = true;
3131    }
3132  }
3133  minHeight = isFiniteNumber(minHeight) ? minHeight : 0;
3134  minFramerate = isFiniteNumber(minFramerate) ? minFramerate : 0;
3135  const maxHeight = Math.max(1080, minHeight);
3136  const maxFramerate = Math.max(30, minFramerate);
3137  minBitrate = isFiniteNumber(minBitrate) ? minBitrate : currentBw;
3138  currentBw = Math.max(minBitrate, currentBw);
3139  // If there are no variants with matching preference, set currentVideoRange to undefined
3140  if (!hasCurrentVideoRange) {
3141    currentVideoRange = undefined;
3142  }
3143  const hasMultipleSets = codecSets.length > 1;
3144  const codecSet = codecSets.reduce((selected, candidate) => {
3145    // 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
3146    const candidateTier = codecTiers[candidate];
3147    if (candidate === selected) {
3148      return selected;
3149    }
3150    videoRanges = hasCurrentVideoRange ? allowedVideoRanges.filter(range => candidateTier.videoRanges[range] > 0) : [];
3151    if (hasMultipleSets) {
3152      if (candidateTier.minBitrate > currentBw) {
3153        logStartCodecCandidateIgnored(candidate, `min bitrate of ${candidateTier.minBitrate} > current estimate of ${currentBw}`);
3154        return selected;
3155      }
3156      if (!candidateTier.hasDefaultAudio) {
3157        logStartCodecCandidateIgnored(candidate, `no renditions with default or auto-select sound found`);
3158        return selected;
3159      }
3160      if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) {
3161        logStartCodecCandidateIgnored(candidate, `audio codec preference "${audioCodecPreference}" not found`);
3162        return selected;
3163      }
3164      if (channelsPreference && !preferStereo) {
3165        if (!candidateTier.channels[channelsPreference]) {
3166          logStartCodecCandidateIgnored(candidate, `no renditions with ${channelsPreference} channel sound found (channels options: ${Object.keys(candidateTier.channels)})`);
3167          return selected;
3168        }
3169      } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) {
3170        logStartCodecCandidateIgnored(candidate, `no renditions with stereo sound found`);
3171        return selected;
3172      }
3173      if (candidateTier.minHeight > maxHeight) {
3174        logStartCodecCandidateIgnored(candidate, `min resolution of ${candidateTier.minHeight} > maximum of ${maxHeight}`);
3175        return selected;
3176      }
3177      if (candidateTier.minFramerate > maxFramerate) {
3178        logStartCodecCandidateIgnored(candidate, `min framerate of ${candidateTier.minFramerate} > maximum of ${maxFramerate}`);
3179        return selected;
3180      }
3181      if (!videoRanges.some(range => candidateTier.videoRanges[range] > 0)) {
3182        logStartCodecCandidateIgnored(candidate, `no variants with VIDEO-RANGE of ${stringify(videoRanges)} found`);
3183        return selected;
3184      }
3185      if (videoCodecPreference && candidate.indexOf(videoCodecPreference.substring(0, 4)) % 5 !== 0) {
3186        logStartCodecCandidateIgnored(candidate, `video codec preference "${videoCodecPreference}" not found`);
3187        return selected;
3188      }
3189      if (candidateTier.maxScore < selectedScore) {
3190        logStartCodecCandidateIgnored(candidate, `max score of ${candidateTier.maxScore} < selected max of ${selectedScore}`);
3191        return selected;
3192      }
3193    }
3194    // Remove candiates with less preferred codecs or more errors
3195    if (selected && (codecsSetSelectionPreferenceValue(candidate) >= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) {
3196      return selected;
3197    }
3198    minIndex = candidateTier.minIndex;
3199    selectedScore = candidateTier.maxScore;
3200    return candidate;
3201  }, undefined);
3202  return {
3203    codecSet,
3204    videoRanges,
3205    preferHDR,
3206    minFramerate,
3207    minBitrate,
3208    minIndex
3209  };
3210}
3211function logStartCodecCandidateIgnored(codeSet, reason) {
3212  logger.log(`[abr] start candidates with "${codeSet}" ignored because ${reason}`);
3213}
3214function getAudioTracksByGroup(allAudioTracks) {
3215  return allAudioTracks.reduce((audioTracksByGroup, track) => {
3216    let trackGroup = audioTracksByGroup.groups[track.groupId];
3217    if (!trackGroup) {
3218      trackGroup = audioTracksByGroup.groups[track.groupId] = {
3219        tracks: [],
3220        channels: {
3221          2: 0
3222        },
3223        hasDefault: false,
3224        hasAutoSelect: false
3225      };
3226    }
3227    trackGroup.tracks.push(track);
3228    const channelsKey = track.channels || '2';
3229    trackGroup.channels[channelsKey] = (trackGroup.channels[channelsKey] || 0) + 1;
3230    trackGroup.hasDefault = trackGroup.hasDefault || track.default;
3231    trackGroup.hasAutoSelect = trackGroup.hasAutoSelect || track.autoselect;
3232    if (trackGroup.hasDefault) {
3233      audioTracksByGroup.hasDefaultAudio = true;
3234    }
3235    if (trackGroup.hasAutoSelect) {
3236      audioTracksByGroup.hasAutoSelectAudio = true;
3237    }
3238    return audioTracksByGroup;
3239  }, {
3240    hasDefaultAudio: false,
3241    hasAutoSelectAudio: false,
3242    groups: {}
3243  });
3244}
3245function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
3246  return levels.slice(minAutoLevel, maxAutoLevel + 1).reduce((tiers, level, index) => {
3247    if (!level.codecSet) {
3248      return tiers;
3249    }
3250    const audioGroups = level.audioGroups;
3251    let tier = tiers[level.codecSet];
3252    if (!tier) {
3253      tiers[level.codecSet] = tier = {
3254        minBitrate: Infinity,
3255        minHeight: Infinity,
3256        minFramerate: Infinity,
3257        minIndex: index,
3258        maxScore: 0,
3259        videoRanges: {
3260          SDR: 0
3261        },
3262        channels: {
3263          '2': 0
3264        },
3265        hasDefaultAudio: !audioGroups,
3266        fragmentError: 0
3267      };
3268    }
3269    tier.minBitrate = Math.min(tier.minBitrate, level.bitrate);
3270    const lesserWidthOrHeight = Math.min(level.height, level.width);
3271    tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight);
3272    tier.minFramerate = Math.min(tier.minFramerate, level.frameRate);
3273    tier.minIndex = Math.min(tier.minIndex, index);
3274    tier.maxScore = Math.max(tier.maxScore, level.score);
3275    tier.fragmentError += level.fragmentError;
3276    tier.videoRanges[level.videoRange] = (tier.videoRanges[level.videoRange] || 0) + 1;
3277    if (audioGroups) {
3278      audioGroups.forEach(audioGroupId => {
3279        if (!audioGroupId) {
3280          return;
3281        }
3282        const audioGroup = audioTracksByGroup.groups[audioGroupId];
3283        if (!audioGroup) {
3284          return;
3285        }
3286        // Default audio is any group with DEFAULT=YES, or if missing then any group with AUTOSELECT=YES, or all variants
3287        tier.hasDefaultAudio = tier.hasDefaultAudio || audioTracksByGroup.hasDefaultAudio ? audioGroup.hasDefault : audioGroup.hasAutoSelect || !audioTracksByGroup.hasDefaultAudio && !audioTracksByGroup.hasAutoSelectAudio;
3288        Object.keys(audioGroup.channels).forEach(channels => {
3289          tier.channels[channels] = (tier.channels[channels] || 0) + audioGroup.channels[channels];
3290        });
3291      });
3292    }
3293    return tiers;
3294  }, {});
3295}
3296function getBasicSelectionOption(option) {
3297  if (!option) {
3298    return option;
3299  }
3300  const {
3301    lang,
3302    assocLang,
3303    characteristics,
3304    channels,
3305    audioCodec
3306  } = option;
3307  return {
3308    lang,
3309    assocLang,
3310    characteristics,
3311    channels,
3312    audioCodec
3313  };
3314}
3315function findMatchingOption(option, tracks, matchPredicate) {
3316  if ('attrs' in option) {
3317    const index = tracks.indexOf(option);
3318    if (index !== -1) {
3319      return index;
3320    }
3321  }
3322  for (let i = 0; i < tracks.length; i++) {
3323    const track = tracks[i];
3324    if (matchesOption(option, track, matchPredicate)) {
3325      return i;
3326    }
3327  }
3328  return -1;
3329}
3330function matchesOption(option, track, matchPredicate) {
3331  const {
3332    groupId,
3333    name,
3334    lang,
3335    assocLang,
3336    default: isDefault
3337  } = option;
3338  const forced = option.forced;
3339  return (groupId === undefined || track.groupId === groupId) && (name === undefined || track.name === name) && (lang === undefined || languagesMatch(lang, track.lang)) && (lang === undefined || track.assocLang === assocLang) && (isDefault === undefined || track.default === isDefault) && (forced === undefined || track.forced === forced) && (!('characteristics' in option) || characteristicsMatch(option.characteristics || '', track.characteristics)) && (matchPredicate === undefined || matchPredicate(option, track));
3340}
3341function languagesMatch(languageA, languageB = '--') {
3342  if (languageA.length === languageB.length) {
3343    return languageA === languageB;
3344  }
3345  return languageA.startsWith(languageB) || languageB.startsWith(languageA);
3346}
3347function characteristicsMatch(characteristicsA, characteristicsB = '') {
3348  const arrA = characteristicsA.split(',');
3349  const arrB = characteristicsB.split(',');
3350  // Expects each item to be unique:
3351  return arrA.length === arrB.length && !arrA.some(el => arrB.indexOf(el) === -1);
3352}
3353function audioMatchPredicate(option, track) {
3354  const {
3355    audioCodec,
3356    channels
3357  } = option;
3358  return (audioCodec === undefined || (track.audioCodec || '').substring(0, 4) === audioCodec.substring(0, 4)) && (channels === undefined || channels === (track.channels || '2'));
3359}
3360function findClosestLevelWithAudioGroup(option, levels, allAudioTracks, searchIndex, matchPredicate) {
3361  const currentLevel = levels[searchIndex];
3362  // Are there variants with same URI as current level?
3363  // If so, find a match that does not require any level URI change
3364  const variants = levels.reduce((variantMap, level, index) => {
3365    const uri = level.uri;
3366    const renditions = variantMap[uri] || (variantMap[uri] = []);
3367    renditions.push(index);
3368    return variantMap;
3369  }, {});
3370  const renditions = variants[currentLevel.uri];
3371  if (renditions.length > 1) {
3372    searchIndex = Math.max.apply(Math, renditions);
3373  }
3374  // Find best match
3375  const currentVideoRange = currentLevel.videoRange;
3376  const currentFrameRate = currentLevel.frameRate;
3377  const currentVideoCodec = currentLevel.codecSet.substring(0, 4);
3378  const matchingVideo = searchDownAndUpList(levels, searchIndex, level => {
3379    if (level.videoRange !== currentVideoRange || level.frameRate !== currentFrameRate || level.codecSet.substring(0, 4) !== currentVideoCodec) {
3380      return false;
3381    }
3382    const audioGroups = level.audioGroups;
3383    const tracks = allAudioTracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
3384    return findMatchingOption(option, tracks, matchPredicate) > -1;
3385  });
3386  if (matchingVideo > -1) {
3387    return matchingVideo;
3388  }
3389  return searchDownAndUpList(levels, searchIndex, level => {
3390    const audioGroups = level.audioGroups;
3391    const tracks = allAudioTracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
3392    return findMatchingOption(option, tracks, matchPredicate) > -1;
3393  });
3394}
3395function searchDownAndUpList(arr, searchIndex, predicate) {
3396  for (let i = searchIndex; i > -1; i--) {
3397    if (predicate(arr[i])) {
3398      return i;
3399    }
3400  }
3401  for (let i = searchIndex + 1; i < arr.length; i++) {
3402    if (predicate(arr[i])) {
3403      return i;
3404    }
3405  }
3406  return -1;
3407}
3408function useAlternateAudio(audioTrackUrl, hls) {
3409  var _hls$loadLevelObj;
3410  return !!audioTrackUrl && audioTrackUrl !== ((_hls$loadLevelObj = hls.loadLevelObj) == null ? void 0 : _hls$loadLevelObj.uri);
3411}
3412
3413class AbrController extends Logger {
3414  constructor(_hls) {
3415    super('abr', _hls.logger);
3416    this.hls = void 0;
3417    this.lastLevelLoadSec = 0;
3418    this.lastLoadedFragLevel = -1;
3419    this.firstSelection = -1;
3420    this._nextAutoLevel = -1;
3421    this.nextAutoLevelKey = '';
3422    this.audioTracksByGroup = null;
3423    this.codecTiers = null;
3424    this.timer = -1;
3425    this.fragCurrent = null;
3426    this.partCurrent = null;
3427    this.bitrateTestDelay = 0;
3428    this.rebufferNotice = -1;
3429    this.supportedCache = {};
3430    this.bwEstimator = void 0;
3431    /*
3432        This method monitors the download rate of the current fragment, and will downswitch if that fragment will not load
3433        quickly enough to prevent underbuffering
3434      */
3435    this._abandonRulesCheck = levelLoaded => {
3436      var _ref;
3437      const {
3438        fragCurrent: frag,
3439        partCurrent: part,
3440        hls
3441      } = this;
3442      const {
3443        autoLevelEnabled,
3444        media
3445      } = hls;
3446      if (!frag || !media) {
3447        return;
3448      }
3449      const now = performance.now();
3450      const stats = part ? part.stats : frag.stats;
3451      const duration = part ? part.duration : frag.duration;
3452      const timeLoading = now - stats.loading.start;
3453      const minAutoLevel = hls.minAutoLevel;
3454      const loadingFragForLevel = frag.level;
3455      const currentAutoLevel = this._nextAutoLevel;
3456      // If frag loading is aborted, complete, or from lowest level, stop timer and return
3457      if (stats.aborted || stats.loaded && stats.loaded === stats.total || loadingFragForLevel <= minAutoLevel) {
3458        this.clearTimer();
3459        // reset forced auto level value so that next level will be selected
3460        this._nextAutoLevel = -1;
3461        return;
3462      }
3463
3464      // This check only runs if we're in ABR mode
3465      if (!autoLevelEnabled) {
3466        return;
3467      }
3468
3469      // Must be loading/loaded a new level or be in a playing state
3470      const fragBlockingSwitch = currentAutoLevel > -1 && currentAutoLevel !== loadingFragForLevel;
3471      const levelChange = !!levelLoaded || fragBlockingSwitch;
3472      if (!levelChange && (media.paused || !media.playbackRate || !media.readyState)) {
3473        return;
3474      }
3475      const bufferInfo = hls.mainForwardBufferInfo;
3476      if (!levelChange && bufferInfo === null) {
3477        return;
3478      }
3479      const ttfbEstimate = this.bwEstimator.getEstimateTTFB();
3480      const playbackRate = Math.abs(media.playbackRate);
3481      // To maintain stable adaptive playback, only begin monitoring frag loading after half or more of its playback duration has passed
3482      if (timeLoading <= Math.max(ttfbEstimate, 1000 * (duration / (playbackRate * 2)))) {
3483        return;
3484      }
3485
3486      // bufferStarvationDelay is an estimate of the amount time (in seconds) it will take to exhaust the buffer
3487      const bufferStarvationDelay = bufferInfo ? bufferInfo.len / playbackRate : 0;
3488      const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
3489      const loadedFirstByte = stats.loaded && ttfb > -1;
3490      const bwEstimate = this.getBwEstimate();
3491      const levels = hls.levels;
3492      const level = levels[loadingFragForLevel];
3493      const expectedLen = Math.max(stats.loaded, Math.round(duration * (frag.bitrate || level.averageBitrate) / 8));
3494      let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
3495      if (timeStreaming < 1 && loadedFirstByte) {
3496        timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
3497      }
3498      const loadRate = loadedFirstByte ? stats.loaded * 1000 / timeStreaming : 0;
3499      // fragLoadDelay is an estimate of the time (in seconds) it will take to buffer the remainder of the fragment
3500      const ttfbSeconds = ttfbEstimate / 1000;
3501      const fragLoadedDelay = loadRate ? (expectedLen - stats.loaded) / loadRate : expectedLen * 8 / bwEstimate + ttfbSeconds;
3502      // Only downswitch if the time to finish loading the current fragment is greater than the amount of buffer left
3503      if (fragLoadedDelay <= bufferStarvationDelay) {
3504        return;
3505      }
3506      const bwe = loadRate ? loadRate * 8 : bwEstimate;
3507      const live = ((_ref = (levelLoaded == null ? void 0 : levelLoaded.details) || this.hls.latestLevelDetails) == null ? void 0 : _ref.live) === true;
3508      const abrBandWidthUpFactor = this.hls.config.abrBandWidthUpFactor;
3509      let fragLevelNextLoadedDelay = Number.POSITIVE_INFINITY;
3510      let nextLoadLevel;
3511      // Iterate through lower level and try to find the largest one that avoids rebuffering
3512      for (nextLoadLevel = loadingFragForLevel - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) {
3513        // compute time to load next fragment at lower level
3514        // 8 = bits per byte (bps/Bps)
3515        const levelNextBitrate = levels[nextLoadLevel].maxBitrate;
3516        const requiresLevelLoad = !levels[nextLoadLevel].details || live;
3517        fragLevelNextLoadedDelay = this.getTimeToLoadFrag(ttfbSeconds, bwe, duration * levelNextBitrate, requiresLevelLoad);
3518        if (fragLevelNextLoadedDelay < Math.min(bufferStarvationDelay, duration + ttfbSeconds)) {
3519          break;
3520        }
3521      }
3522      // Only emergency switch down if it takes less time to load a new fragment at lowest level instead of continuing
3523      // to load the current one
3524      if (fragLevelNextLoadedDelay >= fragLoadedDelay) {
3525        return;
3526      }
3527
3528      // if estimated load time of new segment is completely unreasonable, ignore and do not emergency switch down
3529      if (fragLevelNextLoadedDelay > duration * 10) {
3530        return;
3531      }
3532      if (loadedFirstByte) {
3533        // If there has been loading progress, sample bandwidth using loading time offset by minimum TTFB time
3534        this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
3535      } else {
3536        // If there has been no loading progress, sample TTFB
3537        this.bwEstimator.sampleTTFB(timeLoading);
3538      }
3539      const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
3540      if (this.getBwEstimate() * abrBandWidthUpFactor > nextLoadLevelBitrate) {
3541        this.resetEstimator(nextLoadLevelBitrate);
3542      }
3543      const bestSwitchLevel = this.findBestLevel(nextLoadLevelBitrate, minAutoLevel, nextLoadLevel, 0, bufferStarvationDelay, 1, 1);
3544      if (bestSwitchLevel > -1) {
3545        nextLoadLevel = bestSwitchLevel;
3546      }
3547      this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${loadingFragForLevel} is loading too slowly;
3548      Fragment duration: ${frag.duration.toFixed(3)}
3549      Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
3550      Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
3551      Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
3552      TTFB estimate: ${ttfb | 0} ms
3553      Current BW estimate: ${isFiniteNumber(bwEstimate) ? bwEstimate | 0 : 'Unknown'} bps
3554      New BW estimate: ${this.getBwEstimate() | 0} bps
3555      Switching to level ${nextLoadLevel} @ ${nextLoadLevelBitrate | 0} bps`);
3556      hls.nextLoadLevel = hls.nextAutoLevel = nextLoadLevel;
3557      this.clearTimer();
3558      const abortAndSwitch = () => {
3559        // Are nextLoadLevel details available or is stream-controller still in "WAITING_LEVEL" state?
3560        this.clearTimer();
3561        if (this.fragCurrent === frag && this.hls.loadLevel === nextLoadLevel && nextLoadLevel > 0) {
3562          const bufferStarvationDelay = this.getStarvationDelay();
3563          this.warn(`Aborting inflight request ${nextLoadLevel > 0 ? 'and switching down' : ''}
3564      Fragment duration: ${frag.duration.toFixed(3)} s
3565      Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s`);
3566          frag.abortRequests();
3567          this.fragCurrent = this.partCurrent = null;
3568          if (nextLoadLevel > minAutoLevel) {
3569            let lowestSwitchLevel = this.findBestLevel(this.hls.levels[minAutoLevel].bitrate, minAutoLevel, nextLoadLevel, 0, bufferStarvationDelay, 1, 1);
3570            if (lowestSwitchLevel === -1) {
3571              lowestSwitchLevel = minAutoLevel;
3572            }
3573            this.hls.nextLoadLevel = this.hls.nextAutoLevel = lowestSwitchLevel;
3574            this.resetEstimator(this.hls.levels[lowestSwitchLevel].bitrate);
3575          }
3576        }
3577      };
3578      if (fragBlockingSwitch || fragLoadedDelay > fragLevelNextLoadedDelay * 2) {
3579        abortAndSwitch();
3580      } else {
3581        this.timer = self.setInterval(abortAndSwitch, fragLevelNextLoadedDelay * 1000);
3582      }
3583      hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, {
3584        frag,
3585        part,
3586        stats
3587      });
3588    };
3589    this.hls = _hls;
3590    this.bwEstimator = this.initEstimator();
3591    this.registerListeners();
3592  }
3593  resetEstimator(abrEwmaDefaultEstimate) {
3594    if (abrEwmaDefaultEstimate) {
3595      this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
3596      this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
3597    }
3598    this.firstSelection = -1;
3599    this.bwEstimator = this.initEstimator();
3600  }
3601  initEstimator() {
3602    const config = this.hls.config;
3603    return new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate);
3604  }
3605  registerListeners() {
3606    const {
3607      hls
3608    } = this;
3609    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
3610    hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
3611    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
3612    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
3613    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
3614    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
3615    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
3616    hls.on(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
3617    hls.on(Events.ERROR, this.onError, this);
3618  }
3619  unregisterListeners() {
3620    const {
3621      hls
3622    } = this;
3623    if (!hls) {
3624      return;
3625    }
3626    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
3627    hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
3628    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
3629    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
3630    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
3631    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
3632    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
3633    hls.off(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this);
3634    hls.off(Events.ERROR, this.onError, this);
3635  }
3636  destroy() {
3637    this.unregisterListeners();
3638    this.clearTimer();
3639    // @ts-ignore
3640    this.hls = this._abandonRulesCheck = this.supportedCache = null;
3641    this.fragCurrent = this.partCurrent = null;
3642  }
3643  onManifestLoading(event, data) {
3644    this.lastLoadedFragLevel = -1;
3645    this.firstSelection = -1;
3646    this.lastLevelLoadSec = 0;
3647    this.supportedCache = {};
3648    this.fragCurrent = this.partCurrent = null;
3649    this.onLevelsUpdated();
3650    this.clearTimer();
3651  }
3652  onLevelsUpdated() {
3653    if (this.lastLoadedFragLevel > -1 && this.fragCurrent) {
3654      this.lastLoadedFragLevel = this.fragCurrent.level;
3655    }
3656    this._nextAutoLevel = -1;
3657    this.onMaxAutoLevelUpdated();
3658    this.codecTiers = null;
3659    this.audioTracksByGroup = null;
3660  }
3661  onMaxAutoLevelUpdated() {
3662    this.firstSelection = -1;
3663    this.nextAutoLevelKey = '';
3664  }
3665  onFragLoading(event, data) {
3666    const frag = data.frag;
3667    if (this.ignoreFragment(frag)) {
3668      return;
3669    }
3670    if (!frag.bitrateTest) {
3671      var _data$part;
3672      this.fragCurrent = frag;
3673      this.partCurrent = (_data$part = data.part) != null ? _data$part : null;
3674    }
3675    this.clearTimer();
3676    this.timer = self.setInterval(this._abandonRulesCheck, 100);
3677  }
3678  onLevelSwitching(event, data) {
3679    this.clearTimer();
3680  }
3681  onError(event, data) {
3682    if (data.fatal) {
3683      return;
3684    }
3685    switch (data.details) {
3686      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
3687      case ErrorDetails.BUFFER_APPEND_ERROR:
3688        // Reset last loaded level so that a new selection can be made after calling recoverMediaError
3689        this.lastLoadedFragLevel = -1;
3690        this.firstSelection = -1;
3691        break;
3692      case ErrorDetails.FRAG_LOAD_TIMEOUT:
3693        {
3694          const frag = data.frag;
3695          const {
3696            fragCurrent,
3697            partCurrent: part
3698          } = this;
3699          if (frag && fragCurrent && frag.sn === fragCurrent.sn && frag.level === fragCurrent.level) {
3700            const now = performance.now();
3701            const stats = part ? part.stats : frag.stats;
3702            const timeLoading = now - stats.loading.start;
3703            const ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1;
3704            const loadedFirstByte = stats.loaded && ttfb > -1;
3705            if (loadedFirstByte) {
3706              const ttfbEstimate = this.bwEstimator.getEstimateTTFB();
3707              this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded);
3708            } else {
3709              this.bwEstimator.sampleTTFB(timeLoading);
3710            }
3711          }
3712          break;
3713        }
3714    }
3715  }
3716  getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch) {
3717    const fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth;
3718    const playlistLoadSec = isSwitch ? timeToFirstByteSec + this.lastLevelLoadSec : 0;
3719    return fragLoadSec + playlistLoadSec;
3720  }
3721  onLevelLoaded(event, data) {
3722    const config = this.hls.config;
3723    const {
3724      loading
3725    } = data.stats;
3726    const timeLoadingMs = loading.end - loading.first;
3727    if (isFiniteNumber(timeLoadingMs)) {
3728      this.lastLevelLoadSec = timeLoadingMs / 1000;
3729    }
3730    if (data.details.live) {
3731      this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive);
3732    } else {
3733      this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD);
3734    }
3735    if (this.timer > -1) {
3736      this._abandonRulesCheck(data.levelInfo);
3737    }
3738  }
3739  onFragLoaded(event, {
3740    frag,
3741    part
3742  }) {
3743    const stats = part ? part.stats : frag.stats;
3744    if (frag.type === PlaylistLevelType.MAIN) {
3745      this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start);
3746    }
3747    if (this.ignoreFragment(frag)) {
3748      return;
3749    }
3750    // stop monitoring bw once frag loaded
3751    this.clearTimer();
3752    // reset forced auto level value so that next level will be selected
3753    if (frag.level === this._nextAutoLevel) {
3754      this._nextAutoLevel = -1;
3755    }
3756    this.firstSelection = -1;
3757
3758    // compute level average bitrate
3759    if (this.hls.config.abrMaxWithRealBitrate) {
3760      const duration = part ? part.duration : frag.duration;
3761      const level = this.hls.levels[frag.level];
3762      const loadedBytes = (level.loaded ? level.loaded.bytes : 0) + stats.loaded;
3763      const loadedDuration = (level.loaded ? level.loaded.duration : 0) + duration;
3764      level.loaded = {
3765        bytes: loadedBytes,
3766        duration: loadedDuration
3767      };
3768      level.realBitrate = Math.round(8 * loadedBytes / loadedDuration);
3769    }
3770    if (frag.bitrateTest) {
3771      const fragBufferedData = {
3772        stats,
3773        frag,
3774        part,
3775        id: frag.type
3776      };
3777      this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData);
3778      frag.bitrateTest = false;
3779    } else {
3780      // store level id after successful fragment load for playback
3781      this.lastLoadedFragLevel = frag.level;
3782    }
3783  }
3784  onFragBuffered(event, data) {
3785    const {
3786      frag,
3787      part
3788    } = data;
3789    const stats = part != null && part.stats.loaded ? part.stats : frag.stats;
3790    if (stats.aborted) {
3791      return;
3792    }
3793    if (this.ignoreFragment(frag)) {
3794      return;
3795    }
3796    // Use the difference between parsing and request instead of buffering and request to compute fragLoadingProcessing;
3797    // rationale is that buffer appending only happens once media is attached. This can happen when config.startFragPrefetch
3798    // is used. If we used buffering in that case, our BW estimate sample will be very large.
3799    const processingMs = stats.parsing.end - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB());
3800    this.bwEstimator.sample(processingMs, stats.loaded);
3801    stats.bwEstimate = this.getBwEstimate();
3802    if (frag.bitrateTest) {
3803      this.bitrateTestDelay = processingMs / 1000;
3804    } else {
3805      this.bitrateTestDelay = 0;
3806    }
3807  }
3808  ignoreFragment(frag) {
3809    // Only count non-alt-audio frags which were actually buffered in our BW calculations
3810    return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment';
3811  }
3812  clearTimer() {
3813    if (this.timer > -1) {
3814      self.clearInterval(this.timer);
3815      this.timer = -1;
3816    }
3817  }
3818  get firstAutoLevel() {
3819    const {
3820      maxAutoLevel,
3821      minAutoLevel
3822    } = this.hls;
3823    const bwEstimate = this.getBwEstimate();
3824    const maxStartDelay = this.hls.config.maxStarvationDelay;
3825    const abrAutoLevel = this.findBestLevel(bwEstimate, minAutoLevel, maxAutoLevel, 0, maxStartDelay, 1, 1);
3826    if (abrAutoLevel > -1) {
3827      return abrAutoLevel;
3828    }
3829    const firstLevel = this.hls.firstLevel;
3830    const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
3831    this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
3832    return clamped;
3833  }
3834  get forcedAutoLevel() {
3835    if (this.nextAutoLevelKey) {
3836      return -1;
3837    }
3838    return this._nextAutoLevel;
3839  }
3840
3841  // return next auto level
3842  get nextAutoLevel() {
3843    const forcedAutoLevel = this.forcedAutoLevel;
3844    const bwEstimator = this.bwEstimator;
3845    const useEstimate = bwEstimator.canEstimate();
3846    const loadedFirstFrag = this.lastLoadedFragLevel > -1;
3847    // in case next auto level has been forced, and bw not available or not reliable, return forced value
3848    if (forcedAutoLevel !== -1 && (!useEstimate || !loadedFirstFrag || this.nextAutoLevelKey === this.getAutoLevelKey())) {
3849      return forcedAutoLevel;
3850    }
3851
3852    // compute next level using ABR logic
3853    const nextABRAutoLevel = useEstimate && loadedFirstFrag ? this.getNextABRAutoLevel() : this.firstAutoLevel;
3854
3855    // use forced auto level while it hasn't errored more than ABR selection
3856    if (forcedAutoLevel !== -1) {
3857      const levels = this.hls.levels;
3858      if (levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel) && levels[forcedAutoLevel].loadError <= levels[nextABRAutoLevel].loadError) {
3859        return forcedAutoLevel;
3860      }
3861    }
3862
3863    // save result until state has changed
3864    this._nextAutoLevel = nextABRAutoLevel;
3865    this.nextAutoLevelKey = this.getAutoLevelKey();
3866    return nextABRAutoLevel;
3867  }
3868  getAutoLevelKey() {
3869    return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`;
3870  }
3871  getNextABRAutoLevel() {
3872    const {
3873      fragCurrent,
3874      partCurrent,
3875      hls
3876    } = this;
3877    if (hls.levels.length <= 1) {
3878      return hls.loadLevel;
3879    }
3880    const {
3881      maxAutoLevel,
3882      config,
3883      minAutoLevel
3884    } = hls;
3885    const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
3886    const avgbw = this.getBwEstimate();
3887    // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
3888    const bufferStarvationDelay = this.getStarvationDelay();
3889    let bwFactor = config.abrBandWidthFactor;
3890    let bwUpFactor = config.abrBandWidthUpFactor;
3891
3892    // First, look to see if we can find a level matching with our avg bandwidth AND that could also guarantee no rebuffering at all
3893    if (bufferStarvationDelay) {
3894      const _bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, 0, bwFactor, bwUpFactor);
3895      if (_bestLevel >= 0) {
3896        this.rebufferNotice = -1;
3897        return _bestLevel;
3898      }
3899    }
3900    // not possible to get rid of rebuffering... try to find level that will guarantee less than maxStarvationDelay of rebuffering
3901    let maxStarvationDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay) : config.maxStarvationDelay;
3902    if (!bufferStarvationDelay) {
3903      // in case buffer is empty, let's check if previous fragment was loaded to perform a bitrate test
3904      const bitrateTestDelay = this.bitrateTestDelay;
3905      if (bitrateTestDelay) {
3906        // if it is the case, then we need to adjust our max starvation delay using maxLoadingDelay config value
3907        // max video loading delay used in  automatic start level selection :
3908        // in that mode ABR controller will ensure that video loading time (ie the time to fetch the first fragment at lowest quality level +
3909        // the time to fetch the fragment at the appropriate quality level is less than ```maxLoadingDelay``` )
3910        // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
3911        const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
3912        maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
3913        this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
3914        // don't use conservative factor on bitrate test
3915        bwFactor = bwUpFactor = 1;
3916      }
3917    }
3918    const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
3919    if (this.rebufferNotice !== bestLevel) {
3920      this.rebufferNotice = bestLevel;
3921      this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
3922    }
3923    if (bestLevel > -1) {
3924      return bestLevel;
3925    }
3926    // If no matching level found, see if min auto level would be a better option
3927    const minLevel = hls.levels[minAutoLevel];
3928    const autoLevel = hls.loadLevelObj;
3929    if (autoLevel && (minLevel == null ? void 0 : minLevel.bitrate) < autoLevel.bitrate) {
3930      return minAutoLevel;
3931    }
3932    // or if bitrate is not lower, continue to use loadLevel
3933    return hls.loadLevel;
3934  }
3935  getStarvationDelay() {
3936    const hls = this.hls;
3937    const media = hls.media;
3938    if (!media) {
3939      return Infinity;
3940    }
3941    // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
3942    // if we're playing back at the normal rate.
3943    const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
3944    const bufferInfo = hls.mainForwardBufferInfo;
3945    return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
3946  }
3947  getBwEstimate() {
3948    return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate;
3949  }
3950  findBestLevel(currentBw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor) {
3951    var _this$hls$latestLevel;
3952    const maxFetchDuration = bufferStarvationDelay + maxStarvationDelay;
3953    const lastLoadedFragLevel = this.lastLoadedFragLevel;
3954    const selectionBaseLevel = lastLoadedFragLevel === -1 ? this.hls.firstLevel : lastLoadedFragLevel;
3955    const {
3956      fragCurrent,
3957      partCurrent
3958    } = this;
3959    const {
3960      levels,
3961      allAudioTracks,
3962      loadLevel,
3963      config
3964    } = this.hls;
3965    if (levels.length === 1) {
3966      return 0;
3967    }
3968    const level = levels[selectionBaseLevel];
3969    const live = !!((_this$hls$latestLevel = this.hls.latestLevelDetails) != null && _this$hls$latestLevel.live);
3970    const firstSelection = loadLevel === -1 || lastLoadedFragLevel === -1;
3971    let currentCodecSet;
3972    let currentVideoRange = 'SDR';
3973    let currentFrameRate = (level == null ? void 0 : level.frameRate) || 0;
3974    const {
3975      audioPreference,
3976      videoPreference
3977    } = config;
3978    const audioTracksByGroup = this.audioTracksByGroup || (this.audioTracksByGroup = getAudioTracksByGroup(allAudioTracks));
3979    let minStartIndex = -1;
3980    if (firstSelection) {
3981      if (this.firstSelection !== -1) {
3982        return this.firstSelection;
3983      }
3984      const codecTiers = this.codecTiers || (this.codecTiers = getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel));
3985      const startTier = getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference);
3986      const {
3987        codecSet,
3988        videoRanges,
3989        minFramerate,
3990        minBitrate,
3991        minIndex,
3992        preferHDR
3993      } = startTier;
3994      minStartIndex = minIndex;
3995      currentCodecSet = codecSet;
3996      currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
3997      currentFrameRate = minFramerate;
3998      currentBw = Math.max(currentBw, minBitrate);
3999      this.log(`picked start tier ${stringify(startTier)}`);
4000    } else {
4001      currentCodecSet = level == null ? void 0 : level.codecSet;
4002      currentVideoRange = level == null ? void 0 : level.videoRange;
4003    }
4004    const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
4005    const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000;
4006    const levelsSkipped = [];
4007    for (let i = maxAutoLevel; i >= minAutoLevel; i--) {
4008      var _levelInfo$supportedR;
4009      const levelInfo = levels[i];
4010      const upSwitch = i > selectionBaseLevel;
4011      if (!levelInfo) {
4012        continue;
4013      }
4014      if (config.useMediaCapabilities && !levelInfo.supportedResult && !levelInfo.supportedPromise) {
4015        const mediaCapabilities = navigator.mediaCapabilities;
4016        if (typeof (mediaCapabilities == null ? void 0 : mediaCapabilities.decodingInfo) === 'function' && requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)) {
4017          levelInfo.supportedPromise = getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities, this.supportedCache);
4018          levelInfo.supportedPromise.then(decodingInfo => {
4019            if (!this.hls) {
4020              return;
4021            }
4022            levelInfo.supportedResult = decodingInfo;
4023            const levels = this.hls.levels;
4024            const index = levels.indexOf(levelInfo);
4025            if (decodingInfo.error) {
4026              this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${stringify(decodingInfo)}`);
4027            } else if (!decodingInfo.supported) {
4028              this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${stringify(decodingInfo)}`);
4029              if (index > -1 && levels.length > 1) {
4030                this.log(`Removing unsupported level ${index}`);
4031                this.hls.removeLevel(index);
4032                if (this.hls.loadLevel === -1) {
4033                  this.hls.nextLoadLevel = 0;
4034                }
4035              }
4036            } else if (decodingInfo.decodingInfoResults.some(info => info.smooth === false || info.powerEfficient === false)) {
4037              this.log(`MediaCapabilities decodingInfo for level ${index} not smooth or powerEfficient: ${stringify(decodingInfo)}`);
4038            }
4039          }).catch(error => {
4040            this.warn(`Error handling MediaCapabilities decodingInfo: ${error}`);
4041          });
4042        } else {
4043          levelInfo.supportedResult = SUPPORTED_INFO_DEFAULT;
4044        }
4045      }
4046
4047      // skip candidates which change codec-family or video-range,
4048      // and which decrease or increase frame-rate for up and down-switch respectfully
4049      if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || (_levelInfo$supportedR = levelInfo.supportedResult) != null && (_levelInfo$supportedR = _levelInfo$supportedR.decodingInfoResults) != null && _levelInfo$supportedR.some(info => info.smooth === false)) {
4050        if (!firstSelection || i !== minStartIndex) {
4051          levelsSkipped.push(i);
4052          continue;
4053        }
4054      }
4055      const levelDetails = levelInfo.details;
4056      const avgDuration = (partCurrent ? levelDetails == null ? void 0 : levelDetails.partTarget : levelDetails == null ? void 0 : levelDetails.averagetargetduration) || currentFragDuration;
4057      let adjustedbw;
4058      // follow algorithm captured from stagefright :
4059      // https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp
4060      // Pick the highest bandwidth stream below or equal to estimated bandwidth.
4061      // consider only 80% of the available bandwidth, but if we are switching up,
4062      // be even more conservative (70%) to avoid overestimating and immediately
4063      // switching back.
4064      if (!upSwitch) {
4065        adjustedbw = bwFactor * currentBw;
4066      } else {
4067        adjustedbw = bwUpFactor * currentBw;
4068      }
4069
4070      // Use average bitrate when starvation delay (buffer length) is gt or eq two segment durations and rebuffering is not expected (maxStarvationDelay > 0)
4071      const bitrate = currentFragDuration && bufferStarvationDelay >= currentFragDuration * 2 && maxStarvationDelay === 0 ? levelInfo.averageBitrate : levelInfo.maxBitrate;
4072      const fetchDuration = this.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, levelDetails === undefined);
4073      const canSwitchWithinTolerance =
4074      // if adjusted bw is greater than level bitrate AND
4075      adjustedbw >= bitrate && (
4076      // no level change, or new level has no error history
4077      i === lastLoadedFragLevel || levelInfo.loadError === 0 && levelInfo.fragmentError === 0) && (
4078      // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches
4079      // 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 ...
4080      // special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that findBestLevel will return -1
4081      fetchDuration <= ttfbEstimateSec || !isFiniteNumber(fetchDuration) || live && !this.bitrateTestDelay || fetchDuration < maxFetchDuration);
4082      if (canSwitchWithinTolerance) {
4083        const forcedAutoLevel = this.forcedAutoLevel;
4084        if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
4085          if (levelsSkipped.length) {
4086            this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${currentCodecSet}" ${currentVideoRange}`);
4087          }
4088          this.info(`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:${levelInfo.codecSet} videoRange:${levelInfo.videoRange} hls.loadLevel:${loadLevel}`);
4089        }
4090        if (firstSelection) {
4091          this.firstSelection = i;
4092        }
4093        // as we are looping from highest to lowest, this will return the best achievable quality level
4094        return i;
4095      }
4096    }
4097    // not enough time budget even with quality level 0 ... rebuffering might happen
4098    return -1;
4099  }
4100  set nextAutoLevel(nextLevel) {
4101    const value = this.deriveNextAutoLevel(nextLevel);
4102    if (this._nextAutoLevel !== value) {
4103      this.nextAutoLevelKey = '';
4104      this._nextAutoLevel = value;
4105    }
4106  }
4107  deriveNextAutoLevel(nextLevel) {
4108    const {
4109      maxAutoLevel,
4110      minAutoLevel
4111    } = this.hls;
4112    return Math.min(Math.max(nextLevel, minAutoLevel), maxAutoLevel);
4113  }
4114}
4115
4116const BinarySearch = {
4117  /**
4118   * Searches for an item in an array which matches a certain condition.
4119   * This requires the condition to only match one item in the array,
4120   * and for the array to be ordered.
4121   *
4122   * @param list The array to search.
4123   * @param comparisonFn
4124   *      Called and provided a candidate item as the first argument.
4125   *      Should return:
4126   *          > -1 if the item should be located at a lower index than the provided item.
4127   *          > 1 if the item should be located at a higher index than the provided item.
4128   *          > 0 if the item is the item you're looking for.
4129   *
4130   * @returns the object if found, otherwise returns null
4131   */
4132  search: function (list, comparisonFn) {
4133    let minIndex = 0;
4134    let maxIndex = list.length - 1;
4135    let currentIndex = null;
4136    let currentElement = null;
4137    while (minIndex <= maxIndex) {
4138      currentIndex = (minIndex + maxIndex) / 2 | 0;
4139      currentElement = list[currentIndex];
4140      const comparisonResult = comparisonFn(currentElement);
4141      if (comparisonResult > 0) {
4142        minIndex = currentIndex + 1;
4143      } else if (comparisonResult < 0) {
4144        maxIndex = currentIndex - 1;
4145      } else {
4146        return currentElement;
4147      }
4148    }
4149    return null;
4150  }
4151};
4152
4153/**
4154 * Returns first fragment whose endPdt value exceeds the given PDT, or null.
4155 * @param fragments - The array of candidate fragments
4156 * @param PDTValue - The PDT value which must be exceeded
4157 * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
4158 */
4159function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) {
4160  if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !isFiniteNumber(PDTValue)) {
4161    return null;
4162  }
4163
4164  // if less than start
4165  const startPDT = fragments[0].programDateTime;
4166  if (PDTValue < (startPDT || 0)) {
4167    return null;
4168  }
4169  const endPDT = fragments[fragments.length - 1].endProgramDateTime;
4170  if (PDTValue >= (endPDT || 0)) {
4171    return null;
4172  }
4173  for (let seg = 0; seg < fragments.length; ++seg) {
4174    const frag = fragments[seg];
4175    if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) {
4176      return frag;
4177    }
4178  }
4179  return null;
4180}
4181
4182/**
4183 * Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer.
4184 * This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus
4185 * breaking any traps which would cause the same fragment to be continuously selected within a small range.
4186 * @param fragPrevious - The last frag successfully appended
4187 * @param fragments - The array of candidate fragments
4188 * @param bufferEnd - The end of the contiguous buffered range the playhead is currently within
4189 * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous
4190 * @returns a matching fragment or null
4191 */
4192function findFragmentByPTS(fragPrevious, fragments, bufferEnd = 0, maxFragLookUpTolerance = 0, nextFragLookupTolerance = 0.005) {
4193  let fragNext = null;
4194  if (fragPrevious) {
4195    fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
4196    // check for buffer-end rounding error
4197    const bufferEdgeError = fragPrevious.endDTS - bufferEnd;
4198    if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
4199      bufferEnd += 0.0000015;
4200    }
4201    if (fragNext && fragPrevious.level !== fragNext.level && fragNext.end <= fragPrevious.end) {
4202      fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
4203    }
4204  } else if (bufferEnd === 0 && fragments[0].start === 0) {
4205    fragNext = fragments[0];
4206  }
4207  // Prefer the next fragment if it's within tolerance
4208  if (fragNext && ((!fragPrevious || fragPrevious.level === fragNext.level) && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0 || fragmentWithinFastStartSwitch(fragNext, fragPrevious, Math.min(nextFragLookupTolerance, maxFragLookUpTolerance)))) {
4209    return fragNext;
4210  }
4211  // We might be seeking past the tolerance so find the best match
4212  const foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance));
4213  if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) {
4214    return foundFragment;
4215  }
4216  // If no match was found return the next fragment after fragPrevious, or null
4217  return fragNext;
4218}
4219function fragmentWithinFastStartSwitch(fragNext, fragPrevious, nextFragLookupTolerance) {
4220  if (fragPrevious && fragPrevious.start === 0 && fragPrevious.level < fragNext.level && (fragPrevious.endPTS || 0) > 0) {
4221    const firstDuration = fragPrevious.tagList.reduce((duration, tag) => {
4222      if (tag[0] === 'INF') {
4223        duration += parseFloat(tag[1]);
4224      }
4225      return duration;
4226    }, nextFragLookupTolerance);
4227    return fragNext.start <= firstDuration;
4228  }
4229  return false;
4230}
4231
4232/**
4233 * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions.
4234 * @param candidate - The fragment to test
4235 * @param bufferEnd - The end of the current buffered range the playhead is currently within
4236 * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous
4237 * @returns 0 if it matches, 1 if too low, -1 if too high
4238 */
4239function fragmentWithinToleranceTest(bufferEnd = 0, maxFragLookUpTolerance = 0, candidate) {
4240  // eagerly accept an accurate match (no tolerance)
4241  if (candidate.start <= bufferEnd && candidate.start + candidate.duration > bufferEnd) {
4242    return 0;
4243  }
4244  // offset should be within fragment boundary - config.maxFragLookUpTolerance
4245  // this is to cope with situations like
4246  // bufferEnd = 9.991
4247  // frag[Ø] : [0,10]
4248  // frag[1] : [10,20]
4249  // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here
4250  //              frag start               frag start+duration
4251  //                  |-----------------------------|
4252  //              <--->                         <--->
4253  //  ...--------><-----------------------------><---------....
4254  // previous frag         matching fragment         next frag
4255  //  return -1             return 0                 return 1
4256  // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`);
4257  // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments
4258  const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0));
4259  if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) {
4260    return 1;
4261  } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) {
4262    // if maxFragLookUpTolerance will have negative value then don't return -1 for first element
4263    return -1;
4264  }
4265  return 0;
4266}
4267
4268/**
4269 * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions.
4270 * This function tests the candidate's program date time values, as represented in Unix time
4271 * @param candidate - The fragment to test
4272 * @param pdtBufferEnd - The Unix time representing the end of the current buffered range
4273 * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous
4274 * @returns true if contiguous, false otherwise
4275 */
4276function pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate) {
4277  const candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000;
4278
4279  // endProgramDateTime can be null, default to zero
4280  const endProgramDateTime = candidate.endProgramDateTime || 0;
4281  return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd;
4282}
4283function findNearestWithCC(details, cc, pos) {
4284  if (details) {
4285    if (details.startCC <= cc && details.endCC >= cc) {
4286      let fragments = details.fragments;
4287      const {
4288        fragmentHint
4289      } = details;
4290      if (fragmentHint) {
4291        fragments = fragments.concat(fragmentHint);
4292      }
4293      let closest;
4294      BinarySearch.search(fragments, candidate => {
4295        if (candidate.cc < cc) {
4296          return 1;
4297        }
4298        if (candidate.cc > cc) {
4299          return -1;
4300        }
4301        closest = candidate;
4302        if (candidate.end <= pos) {
4303          return 1;
4304        }
4305        if (candidate.start > pos) {
4306          return -1;
4307        }
4308        return 0;
4309      });
4310      return closest || null;
4311    }
4312  }
4313  return null;
4314}
4315
4316function isTimeoutError(error) {
4317  switch (error.details) {
4318    case ErrorDetails.FRAG_LOAD_TIMEOUT:
4319    case ErrorDetails.KEY_LOAD_TIMEOUT:
4320    case ErrorDetails.LEVEL_LOAD_TIMEOUT:
4321    case ErrorDetails.MANIFEST_LOAD_TIMEOUT:
4322      return true;
4323  }
4324  return false;
vendor: 5,728 bytes, lines 4325-4488
4325}
4326function isKeyError(error) {
4327  return error.details.startsWith('key');
4328}
4329function isUnusableKeyError(error) {
4330  return isKeyError(error) && !!error.frag && !error.frag.decryptdata;
4331}
4332function getRetryConfig(loadPolicy, error) {
4333  const isTimeout = isTimeoutError(error);
4334  return loadPolicy.default[`${isTimeout ? 'timeout' : 'error'}Retry`];
4335}
4336function getRetryDelay(retryConfig, retryCount) {
4337  // exponential backoff capped to max retry delay
4338  const backoffFactor = retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount);
4339  return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs);
4340}
4341function getLoaderConfigWithoutReties(loderConfig) {
4342  return _objectSpread2(_objectSpread2({}, loderConfig), {
4343    errorRetry: null,
4344    timeoutRetry: null
4345  });
4346}
4347function shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse) {
4348  if (!retryConfig) {
4349    return false;
4350  }
4351  const httpStatus = loaderResponse == null ? void 0 : loaderResponse.code;
4352  const retry = retryCount < retryConfig.maxNumRetry && (retryForHttpStatus(httpStatus) || !!isTimeout);
4353  return retryConfig.shouldRetry ? retryConfig.shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse, retry) : retry;
4354}
4355function retryForHttpStatus(httpStatus) {
4356  // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error)
4357  return offlineHttpStatus(httpStatus) || !!httpStatus && (httpStatus < 400 || httpStatus > 499);
4358}
4359function offlineHttpStatus(httpStatus) {
4360  return httpStatus === 0 && navigator.onLine === false;
4361}
4362
4363var NetworkErrorAction = {
4364  DoNothing: 0,
4365  SendEndCallback: 1,
4366  SendAlternateToPenaltyBox: 2,
4367  RemoveAlternatePermanently: 3,
4368  InsertDiscontinuity: 4,
4369  RetryRequest: 5
4370};
4371var ErrorActionFlags = {
4372  None: 0,
4373  MoveAllAlternatesMatchingHost: 1,
4374  MoveAllAlternatesMatchingHDCP: 2,
4375  MoveAllAlternatesMatchingKey: 4,
4376  SwitchToSDR: 8
4377};
4378class ErrorController extends Logger {
4379  constructor(hls) {
4380    super('error-controller', hls.logger);
4381    this.hls = void 0;
4382    this.playlistError = 0;
4383    this.hls = hls;
4384    this.registerListeners();
4385  }
4386  registerListeners() {
4387    const hls = this.hls;
4388    hls.on(Events.ERROR, this.onError, this);
4389    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4390    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
4391  }
4392  unregisterListeners() {
4393    const hls = this.hls;
4394    if (!hls) {
4395      return;
4396    }
4397    hls.off(Events.ERROR, this.onError, this);
4398    hls.off(Events.ERROR, this.onErrorOut, this);
4399    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4400    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
4401  }
4402  destroy() {
4403    this.unregisterListeners();
4404    // @ts-ignore
4405    this.hls = null;
4406  }
4407  startLoad(startPosition) {}
4408  stopLoad() {
4409    this.playlistError = 0;
4410  }
4411  getVariantLevelIndex(frag) {
4412    if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.MAIN) {
4413      return frag.level;
4414    }
4415    return this.getVariantIndex();
4416  }
4417  getVariantIndex() {
4418    var _hls$loadLevelObj;
4419    const hls = this.hls;
4420    const currentLevel = hls.currentLevel;
4421    if ((_hls$loadLevelObj = hls.loadLevelObj) != null && _hls$loadLevelObj.details || currentLevel === -1) {
4422      return hls.loadLevel;
4423    }
4424    return currentLevel;
4425  }
4426  variantHasKey(level, keyInError) {
4427    if (level) {
4428      var _level$details;
4429      if ((_level$details = level.details) != null && _level$details.hasKey(keyInError)) {
4430        return true;
4431      }
4432      const audioGroupsIds = level.audioGroups;
4433      if (audioGroupsIds) {
4434        const audioTracks = this.hls.allAudioTracks.filter(track => audioGroupsIds.indexOf(track.groupId) >= 0);
4435        return audioTracks.some(track => {
4436          var _track$details;
4437          return (_track$details = track.details) == null ? void 0 : _track$details.hasKey(keyInError);
4438        });
4439      }
4440    }
4441    return false;
4442  }
4443  onManifestLoading() {
4444    this.playlistError = 0;
4445  }
4446  onLevelUpdated() {
4447    this.playlistError = 0;
4448  }
4449  onError(event, data) {
4450    var _data$frag;
4451    if (data.fatal) {
4452      return;
4453    }
4454    const hls = this.hls;
4455    const context = data.context;
4456    switch (data.details) {
4457      case ErrorDetails.FRAG_LOAD_ERROR:
4458      case ErrorDetails.FRAG_LOAD_TIMEOUT:
4459      case ErrorDetails.KEY_LOAD_ERROR:
4460      case ErrorDetails.KEY_LOAD_TIMEOUT:
4461        data.errorAction = this.getFragRetryOrSwitchAction(data);
4462        return;
4463      case ErrorDetails.FRAG_PARSING_ERROR:
4464        // ignore empty segment errors marked as gap
4465        if ((_data$frag = data.frag) != null && _data$frag.gap) {
4466          data.errorAction = createDoNothingErrorAction();
4467          return;
4468        }
4469      // falls through
4470      case ErrorDetails.FRAG_GAP:
4471      case ErrorDetails.FRAG_DECRYPT_ERROR:
4472        {
4473          // Switch level if possible, otherwise allow retry count to reach max error retries
4474          data.errorAction = this.getFragRetryOrSwitchAction(data);
4475          data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
4476          return;
4477        }
4478      case ErrorDetails.LEVEL_EMPTY_ERROR:
4479      case ErrorDetails.LEVEL_PARSING_ERROR:
4480        {
4481          var _data$context;
4482          // Only retry when empty and live
4483          const levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel;
4484          if (data.details === ErrorDetails.LEVEL_EMPTY_ERROR && !!((_data$context = data.context) != null && (_data$context = _data$context.levelDetails) != null && _data$context.live)) {
4485            data.errorAction = this.getPlaylistRetryOrSwitchAction(data, levelIndex);
4486          } else {
4487            // Escalate to fatal if not retrying or switching
4488            data.levelRetry = false;
vendor: 7,005 bytes, lines 4489-4652
4489            data.errorAction = this.getLevelSwitchAction(data, levelIndex);
4490          }
4491        }
4492        return;
4493      case ErrorDetails.LEVEL_LOAD_ERROR:
4494      case ErrorDetails.LEVEL_LOAD_TIMEOUT:
4495        if (typeof (context == null ? void 0 : context.level) === 'number') {
4496          data.errorAction = this.getPlaylistRetryOrSwitchAction(data, context.level);
4497        }
4498        return;
4499      case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
4500      case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
4501      case ErrorDetails.SUBTITLE_LOAD_ERROR:
4502      case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT:
4503        if (context) {
4504          const level = hls.loadLevelObj;
4505          if (level && (context.type === PlaylistContextType.AUDIO_TRACK && level.hasAudioGroup(context.groupId) || context.type === PlaylistContextType.SUBTITLE_TRACK && level.hasSubtitleGroup(context.groupId))) {
4506            // Perform Pathway switch or Redundant failover if possible for fastest recovery
4507            // otherwise allow playlist retry count to reach max error retries
4508            data.errorAction = this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel);
4509            data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox;
4510            data.errorAction.flags = ErrorActionFlags.MoveAllAlternatesMatchingHost;
4511            return;
4512          }
4513        }
4514        return;
4515      case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:
4516        {
4517          data.errorAction = {
4518            action: NetworkErrorAction.SendAlternateToPenaltyBox,
4519            flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP
4520          };
4521        }
4522        return;
4523      case ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED:
4524      case ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR:
4525      case ErrorDetails.KEY_SYSTEM_NO_SESSION:
4526        {
4527          data.errorAction = {
4528            action: NetworkErrorAction.SendAlternateToPenaltyBox,
4529            flags: ErrorActionFlags.MoveAllAlternatesMatchingKey
4530          };
4531        }
4532        return;
4533      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
4534      case ErrorDetails.REMUX_ALLOC_ERROR:
4535      case ErrorDetails.BUFFER_APPEND_ERROR:
4536        // Buffer-controller can set errorAction when append errors can be ignored or resolved locally
4537        if (!data.errorAction) {
4538          var _data$level;
4539          data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel);
4540        }
4541        return;
4542      case ErrorDetails.INTERNAL_EXCEPTION:
4543      case ErrorDetails.BUFFER_APPENDING_ERROR:
4544      case ErrorDetails.BUFFER_FULL_ERROR:
4545      case ErrorDetails.LEVEL_SWITCH_ERROR:
4546      case ErrorDetails.BUFFER_STALLED_ERROR:
4547      case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
4548      case ErrorDetails.BUFFER_NUDGE_ON_STALL:
4549        data.errorAction = createDoNothingErrorAction();
4550        return;
4551    }
4552    if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
4553      // Do not retry level. Should be fatal if ErrorDetails.KEY_SYSTEM_<ERROR> not handled with early return above.
4554      data.levelRetry = false;
4555      data.errorAction = createDoNothingErrorAction();
4556    }
4557  }
4558  getPlaylistRetryOrSwitchAction(data, levelIndex) {
4559    const hls = this.hls;
4560    const retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data);
4561    const retryCount = this.playlistError++;
4562    const retry = shouldRetry(retryConfig, retryCount, isTimeoutError(data), data.response);
4563    if (retry) {
4564      return {
4565        action: NetworkErrorAction.RetryRequest,
4566        flags: ErrorActionFlags.None,
4567        retryConfig,
4568        retryCount
4569      };
4570    }
4571    const errorAction = this.getLevelSwitchAction(data, levelIndex);
4572    if (retryConfig) {
4573      errorAction.retryConfig = retryConfig;
4574      errorAction.retryCount = retryCount;
4575    }
4576    return errorAction;
4577  }
4578  getFragRetryOrSwitchAction(data) {
4579    const hls = this.hls;
4580    // Share fragment error count accross media options (main, audio, subs)
4581    // This allows for level based rendition switching when media option assets fail
4582    const variantLevelIndex = this.getVariantLevelIndex(data.frag);
4583    const level = hls.levels[variantLevelIndex];
4584    const {
4585      fragLoadPolicy,
4586      keyLoadPolicy
4587    } = hls.config;
4588    const retryConfig = getRetryConfig(isKeyError(data) ? keyLoadPolicy : fragLoadPolicy, data);
4589    const fragmentErrors = hls.levels.reduce((acc, level) => acc + level.fragmentError, 0);
4590    // Switch levels when out of retried or level index out of bounds
4591    if (level) {
4592      if (data.details !== ErrorDetails.FRAG_GAP) {
4593        level.fragmentError++;
4594      }
4595      if (!isUnusableKeyError(data)) {
4596        const retry = shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), data.response);
4597        if (retry) {
4598          return {
4599            action: NetworkErrorAction.RetryRequest,
4600            flags: ErrorActionFlags.None,
4601            retryConfig,
4602            retryCount: fragmentErrors
4603          };
4604        }
4605      }
4606    }
4607    // Reach max retry count, or Missing level reference
4608    // Switch to valid index
4609    const errorAction = this.getLevelSwitchAction(data, variantLevelIndex);
4610    // Add retry details to allow skipping of FRAG_PARSING_ERROR
4611    if (retryConfig) {
4612      errorAction.retryConfig = retryConfig;
4613      errorAction.retryCount = fragmentErrors;
4614    }
4615    return errorAction;
4616  }
4617  getLevelSwitchAction(data, levelIndex) {
4618    const hls = this.hls;
4619    if (levelIndex === null || levelIndex === undefined) {
4620      levelIndex = hls.loadLevel;
4621    }
4622    const level = this.hls.levels[levelIndex];
4623    if (level) {
4624      var _data$frag2, _data$context2;
4625      const errorDetails = data.details;
4626      level.loadError++;
4627      if (errorDetails === ErrorDetails.BUFFER_APPEND_ERROR) {
4628        level.fragmentError++;
4629      }
4630      // Search for next level to retry
4631      let nextLevel = -1;
4632      const {
4633        levels,
4634        loadLevel,
4635        minAutoLevel,
4636        maxAutoLevel
4637      } = hls;
4638      if (!hls.autoLevelEnabled && !hls.config.preserveManualLevelOnError) {
4639        hls.loadLevel = -1;
4640      }
4641      const fragErrorType = (_data$frag2 = data.frag) == null ? void 0 : _data$frag2.type;
4642      // Find alternate audio codec if available on audio codec error
4643      const isAudioCodecError = fragErrorType === PlaylistLevelType.AUDIO && errorDetails === ErrorDetails.FRAG_PARSING_ERROR || data.sourceBufferName === 'audio' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR);
4644      const findAudioCodecAlternate = isAudioCodecError && levels.some(({
4645        audioCodec
4646      }) => level.audioCodec !== audioCodec);
4647      // Find alternate video codec if available on video codec error
4648      const isVideoCodecError = data.sourceBufferName === 'video' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR);
4649      const findVideoCodecAlternate = isVideoCodecError && levels.some(({
4650        codecSet,
4651        audioCodec
4652      }) =>
vendor: 6,571 bytes, lines 4652-4783
4652 level.codecSet !== codecSet && level.audioCodec === audioCodec);
4653      const {
4654        type: playlistErrorType,
4655        groupId: playlistErrorGroupId
4656      } = (_data$context2 = data.context) != null ? _data$context2 : {};
4657      for (let i = levels.length; i--;) {
4658        const candidate = (i + loadLevel) % levels.length;
4659        if (candidate !== loadLevel && candidate >= minAutoLevel && candidate <= maxAutoLevel && levels[candidate].loadError === 0) {
4660          var _level$audioGroups, _level$subtitleGroups;
4661          const levelCandidate = levels[candidate];
4662          // Skip level switch if GAP tag is found in next level at same position
4663          if (errorDetails === ErrorDetails.FRAG_GAP && fragErrorType === PlaylistLevelType.MAIN && data.frag) {
4664            const levelDetails = levels[candidate].details;
4665            if (levelDetails) {
4666              const fragCandidate = findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start);
4667              if (fragCandidate != null && fragCandidate.gap) {
4668                continue;
4669              }
4670            }
4671          } else if (playlistErrorType === PlaylistContextType.AUDIO_TRACK && levelCandidate.hasAudioGroup(playlistErrorGroupId) || playlistErrorType === PlaylistContextType.SUBTITLE_TRACK && levelCandidate.hasSubtitleGroup(playlistErrorGroupId)) {
4672            // For audio/subs playlist errors find another group ID or fallthrough to redundant fail-over
4673            continue;
4674          } 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 || findVideoCodecAlternate && level.codecSet === levelCandidate.codecSet || !findAudioCodecAlternate && level.codecSet !== levelCandidate.codecSet) {
4675            // For video/audio/subs frag errors find another group ID or fallthrough to redundant fail-over
4676            continue;
4677          }
4678          nextLevel = candidate;
4679          break;
4680        }
4681      }
4682      if (nextLevel > -1 && hls.loadLevel !== nextLevel) {
4683        data.levelRetry = true;
4684        this.playlistError = 0;
4685        return {
4686          action: NetworkErrorAction.SendAlternateToPenaltyBox,
4687          flags: ErrorActionFlags.None,
4688          nextAutoLevel: nextLevel
4689        };
4690      }
4691    }
4692    // No levels to switch / Manual level selection / Level not found
4693    // Resolve with Pathway switch, Redundant fail-over, or stay on lowest Level
4694    return {
4695      action: NetworkErrorAction.SendAlternateToPenaltyBox,
4696      flags: ErrorActionFlags.MoveAllAlternatesMatchingHost
4697    };
4698  }
4699  onErrorOut(event, data) {
4700    var _data$errorAction;
4701    switch ((_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.action) {
4702      case NetworkErrorAction.DoNothing:
4703        break;
4704      case NetworkErrorAction.SendAlternateToPenaltyBox:
4705        this.sendAlternateToPenaltyBox(data);
4706        if (!data.errorAction.resolved && data.details !== ErrorDetails.FRAG_GAP) {
4707          data.fatal = true;
4708        } else if (/MediaSource readyState: ended/.test(data.error.message)) {
4709          this.warn(`MediaSource ended after "${data.sourceBufferName}" sourceBuffer append error. Attempting to recover from media error.`);
4710          this.hls.recoverMediaError();
4711        }
4712        break;
4713      case NetworkErrorAction.RetryRequest:
4714        // handled by stream and playlist/level controllers
4715        break;
4716    }
4717    if (data.fatal) {
4718      this.hls.stopLoad();
4719      return;
4720    }
4721  }
4722  sendAlternateToPenaltyBox(data) {
4723    const hls = this.hls;
4724    const errorAction = data.errorAction;
4725    if (!errorAction) {
4726      return;
4727    }
4728    const {
4729      flags
4730    } = errorAction;
4731    const nextAutoLevel = errorAction.nextAutoLevel;
4732    switch (flags) {
4733      case ErrorActionFlags.None:
4734        this.switchLevel(data, nextAutoLevel);
4735        break;
4736      case ErrorActionFlags.MoveAllAlternatesMatchingHDCP:
4737        {
4738          const levelIndex = this.getVariantLevelIndex(data.frag);
4739          const level = hls.levels[levelIndex];
4740          const restrictedHdcpLevel = level == null ? void 0 : level.attrs['HDCP-LEVEL'];
4741          errorAction.hdcpLevel = restrictedHdcpLevel;
4742          if (restrictedHdcpLevel === 'NONE') {
4743            this.warn(`HDCP policy resticted output with HDCP-LEVEL=NONE`);
4744          } else if (restrictedHdcpLevel) {
4745            hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(restrictedHdcpLevel) - 1];
4746            errorAction.resolved = true;
4747            this.warn(`Restricting playback to HDCP-LEVEL of "${hls.maxHdcpLevel}" or lower`);
4748            break;
4749          }
4750          // Fallthrough when no HDCP-LEVEL attribute is found
4751        }
4752      // eslint-disable-next-line no-fallthrough
4753      case ErrorActionFlags.MoveAllAlternatesMatchingKey:
4754        {
4755          const levelKey = data.decryptdata;
4756          if (levelKey) {
4757            // Penalize all levels with key
4758            const levels = this.hls.levels;
4759            const levelCountWithError = levels.length;
4760            for (let i = levelCountWithError; i--;) {
4761              if (this.variantHasKey(levels[i], levelKey)) {
4762                var _levels$i$audioGroups, _data$frag3;
4763                this.log(`Banned key found in level ${i} (${levels[i].bitrate}bps) or audio group "${(_levels$i$audioGroups = levels[i].audioGroups) == null ? void 0 : _levels$i$audioGroups.join(',')}" (${(_data$frag3 = data.frag) == null ? void 0 : _data$frag3.type} fragment) ${arrayToHex(levelKey.keyId || [])}`);
4764                levels[i].fragmentError++;
4765                levels[i].loadError++;
4766                this.log(`Removing level ${i} with key error (${data.error})`);
4767                this.hls.removeLevel(i);
4768              }
4769            }
4770            const frag = data.frag;
4771            if (this.hls.levels.length < levelCountWithError) {
4772              errorAction.resolved = true;
4773            } else if (frag && frag.type !== PlaylistLevelType.MAIN) {
4774              // Ignore key error for audio track with unmatched key (main session error)
4775              const fragLevelKey = frag.decryptdata;
4776              if (fragLevelKey && !levelKey.matches(fragLevelKey)) {
4777                errorAction.resolved = true;
4778              }
4779            }
4780          }
4781          break;
4782        }
4783    }
vendor: 169,885 bytes, lines 4784-9498
4784    // If not resolved by previous actions try to switch to next level
4785    if (!errorAction.resolved) {
4786      this.switchLevel(data, nextAutoLevel);
4787    }
4788  }
4789  switchLevel(data, levelIndex) {
4790    if (levelIndex !== undefined && data.errorAction) {
4791      this.warn(`switching to level ${levelIndex} after ${data.details}`);
4792      this.hls.nextAutoLevel = levelIndex;
4793      data.errorAction.resolved = true;
4794      // Stream controller is responsible for this but won't switch on false start
4795      this.hls.nextLoadLevel = this.hls.nextAutoLevel;
4796      if (data.details === ErrorDetails.BUFFER_ADD_CODEC_ERROR && data.mimeType && data.sourceBufferName !== 'audiovideo') {
4797        const codec = getCodecsForMimeType(data.mimeType);
4798        const levels = this.hls.levels;
4799        for (let i = levels.length; i--;) {
4800          if (levels[i][`${data.sourceBufferName}Codec`] === codec) {
4801            this.log(`Removing level ${i} for ${data.details} ("${codec}" not supported)`);
4802            this.hls.removeLevel(i);
4803          }
4804        }
4805      }
4806    }
4807  }
4808}
4809function createDoNothingErrorAction(resolved) {
4810  const errorAction = {
4811    action: NetworkErrorAction.DoNothing,
4812    flags: ErrorActionFlags.None
4813  };
4814  if (resolved) {
4815    errorAction.resolved = true;
4816  }
4817  return errorAction;
4818}
4819
4820var FragmentState = {
4821  NOT_LOADED: "NOT_LOADED",
4822  APPENDING: "APPENDING",
4823  PARTIAL: "PARTIAL",
4824  OK: "OK"
4825};
4826class FragmentTracker {
4827  constructor(hls) {
4828    this.activePartLists = Object.create(null);
4829    this.endListFragments = Object.create(null);
4830    this.fragments = Object.create(null);
4831    this.timeRanges = Object.create(null);
4832    this.bufferPadding = 0.2;
4833    this.hls = void 0;
4834    this.hasGaps = false;
4835    this.hls = hls;
4836    this._registerListeners();
4837  }
4838  _registerListeners() {
4839    const {
4840      hls
4841    } = this;
4842    if (hls) {
4843      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4844      hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
4845      hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
4846      hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
4847    }
4848  }
4849  _unregisterListeners() {
4850    const {
4851      hls
4852    } = this;
4853    if (hls) {
4854      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
4855      hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
4856      hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
4857      hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
4858    }
4859  }
4860  destroy() {
4861    this._unregisterListeners();
4862    // @ts-ignore
4863    this.hls =
4864    // @ts-ignore
4865    this.fragments =
4866    // @ts-ignore
4867    this.activePartLists =
4868    // @ts-ignore
4869    this.endListFragments = this.timeRanges = null;
4870  }
4871
4872  /**
4873   * Return a Fragment or Part with an appended range that matches the position and levelType
4874   * Otherwise, return null
4875   */
4876  getAppendedFrag(position, levelType) {
4877    const activeParts = this.activePartLists[levelType];
4878    if (activeParts) {
4879      for (let i = activeParts.length; i--;) {
4880        const activePart = activeParts[i];
4881        if (!activePart) {
4882          break;
4883        }
4884        if (activePart.start <= position && position <= activePart.end && activePart.loaded) {
4885          return activePart;
4886        }
4887      }
4888    }
4889    return this.getBufferedFrag(position, levelType);
4890  }
4891
4892  /**
4893   * Return a buffered Fragment that matches the position and levelType.
4894   * A buffered Fragment is one whose loading, parsing and appending is done (completed or "partial" meaning aborted).
4895   * If not found any Fragment, return null
4896   */
4897  getBufferedFrag(position, levelType) {
4898    return this.getFragAtPos(position, levelType, true);
4899  }
4900  getFragAtPos(position, levelType, buffered) {
4901    const {
4902      fragments
4903    } = this;
4904    const keys = Object.keys(fragments);
4905    for (let i = keys.length; i--;) {
4906      const fragmentEntity = fragments[keys[i]];
4907      if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
4908        const frag = fragmentEntity.body;
4909        if (frag.start <= position && position <= frag.end) {
4910          return frag;
4911        }
4912      }
4913    }
4914    return null;
4915  }
4916
4917  /**
4918   * Partial fragments effected by coded frame eviction will be removed
4919   * The browser will unload parts of the buffer to free up memory for new buffer data
4920   * 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)
4921   */
4922  detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart, removeAppending) {
4923    if (this.timeRanges) {
4924      this.timeRanges[elementaryStream] = timeRange;
4925    }
4926    // Check if any flagged fragments have been unloaded
4927    // excluding anything newer than appendedPartSn
4928    const appendedPartSn = (appendedPart == null ? void 0 : appendedPart.fragment.sn) || -1;
4929    Object.keys(this.fragments).forEach(key => {
4930      const fragmentEntity = this.fragments[key];
4931      if (!fragmentEntity) {
4932        return;
4933      }
4934      if (appendedPartSn >= fragmentEntity.body.sn) {
4935        return;
4936      }
4937      if (!fragmentEntity.buffered && (!fragmentEntity.loaded || removeAppending)) {
4938        if (fragmentEntity.body.type === playlistType) {
4939          this.removeFragment(fragmentEntity.body);
4940        }
4941        return;
4942      }
4943      const esData = fragmentEntity.range[elementaryStream];
4944      if (!esData) {
4945        return;
4946      }
4947      if (esData.time.length === 0) {
4948        this.removeFragment(fragmentEntity.body);
4949        return;
4950      }
4951      esData.time.some(time => {
4952        const isNotBuffered = !this.isTimeBuffered(time.startPTS, time.endPTS, timeRange);
4953        if (isNotBuffered) {
4954          // Unregister partial fragment as it needs to load again to be reused
4955          this.removeFragment(fragmentEntity.body);
4956        }
4957        return isNotBuffered;
4958      });
4959    });
4960  }
4961
4962  /**
4963   * Checks if the fragment passed in is loaded in the buffer properly
4964   * Partially loaded fragments will be registered as a partial fragment
4965   */
4966  detectPartialFragments(data) {
4967    const timeRanges = this.timeRanges;
4968    if (!timeRanges || data.frag.sn === 'initSegment') {
4969      return;
4970    }
4971    const frag = data.frag;
4972    const fragKey = getFragmentKey(frag);
4973    const fragmentEntity = this.fragments[fragKey];
4974    if (!fragmentEntity || fragmentEntity.buffered && frag.gap) {
4975      return;
4976    }
4977    const isFragHint = !frag.relurl;
4978    Object.keys(timeRanges).forEach(elementaryStream => {
4979      const streamInfo = frag.elementaryStreams[elementaryStream];
4980      if (!streamInfo) {
4981        return;
4982      }
4983      const timeRange = timeRanges[elementaryStream];
4984      const partial = isFragHint || streamInfo.partial === true;
4985      fragmentEntity.range[elementaryStream] = this.getBufferedTimes(frag, data.part, partial, timeRange);
4986    });
4987    fragmentEntity.loaded = null;
4988    if (Object.keys(fragmentEntity.range).length) {
4989      this.bufferedEnd(fragmentEntity, frag);
4990      if (!isPartial(fragmentEntity)) {
4991        // Remove older fragment parts from lookup after frag is tracked as buffered
4992        this.removeParts(frag.sn - 1, frag.type);
4993      }
4994    } else {
4995      // remove fragment if nothing was appended
4996      this.removeFragment(fragmentEntity.body);
4997    }
4998  }
4999  bufferedEnd(fragmentEntity, frag) {
5000    fragmentEntity.buffered = true;
5001    const endList = fragmentEntity.body.endList = frag.endList || fragmentEntity.body.endList;
5002    if (endList) {
5003      this.endListFragments[fragmentEntity.body.type] = fragmentEntity;
5004    }
5005  }
5006  removeParts(snToKeep, levelType) {
5007    const activeParts = this.activePartLists[levelType];
5008    if (!activeParts) {
5009      return;
5010    }
5011    this.activePartLists[levelType] = filterParts(activeParts, part => part.fragment.sn >= snToKeep);
5012  }
5013  fragBuffered(frag, force) {
5014    const fragKey = getFragmentKey(frag);
5015    let fragmentEntity = this.fragments[fragKey];
5016    if (!fragmentEntity && force) {
5017      fragmentEntity = this.fragments[fragKey] = {
5018        body: frag,
5019        appendedPTS: null,
5020        loaded: null,
5021        buffered: false,
5022        range: Object.create(null)
5023      };
5024      if (frag.gap) {
5025        this.hasGaps = true;
5026      }
5027    }
5028    if (fragmentEntity) {
5029      fragmentEntity.loaded = null;
5030      this.bufferedEnd(fragmentEntity, frag);
5031    }
5032  }
5033  getBufferedTimes(fragment, part, partial, timeRange) {
5034    const buffered = {
5035      time: [],
5036      partial
5037    };
5038    const startPTS = fragment.start;
5039    const endPTS = fragment.end;
5040    const minEndPTS = fragment.minEndPTS || endPTS;
5041    const maxStartPTS = fragment.maxStartPTS || startPTS;
5042    for (let i = 0; i < timeRange.length; i++) {
5043      const startTime = timeRange.start(i) - this.bufferPadding;
5044      const endTime = timeRange.end(i) + this.bufferPadding;
5045      if (maxStartPTS >= startTime && minEndPTS <= endTime) {
5046        // Fragment is entirely contained in buffer
5047        // No need to check the other timeRange times since it's completely playable
5048        buffered.time.push({
5049          startPTS: Math.max(startPTS, timeRange.start(i)),
5050          endPTS: Math.min(endPTS, timeRange.end(i))
5051        });
5052        break;
5053      } else if (startPTS < endTime && endPTS > startTime) {
5054        const start = Math.max(startPTS, timeRange.start(i));
5055        const end = Math.min(endPTS, timeRange.end(i));
5056        if (end > start) {
5057          buffered.partial = true;
5058          // Check for intersection with buffer
5059          // Get playable sections of the fragment
5060          buffered.time.push({
5061            startPTS: start,
5062            endPTS: end
5063          });
5064        }
5065      } else if (endPTS <= startTime) {
5066        // No need to check the rest of the timeRange as it is in order
5067        break;
5068      }
5069    }
5070    return buffered;
5071  }
5072
5073  /**
5074   * Gets the partial fragment for a certain time
5075   */
5076  getPartialFragment(time) {
5077    let bestFragment = null;
5078    let timePadding;
5079    let startTime;
5080    let endTime;
5081    let bestOverlap = 0;
5082    const {
5083      bufferPadding,
5084      fragments
5085    } = this;
5086    Object.keys(fragments).forEach(key => {
5087      const fragmentEntity = fragments[key];
5088      if (!fragmentEntity) {
5089        return;
5090      }
5091      if (isPartial(fragmentEntity)) {
5092        startTime = fragmentEntity.body.start - bufferPadding;
5093        endTime = fragmentEntity.body.end + bufferPadding;
5094        if (time >= startTime && time <= endTime) {
5095          // Use the fragment that has the most padding from start and end time
5096          timePadding = Math.min(time - startTime, endTime - time);
5097          if (bestOverlap <= timePadding) {
5098            bestFragment = fragmentEntity.body;
5099            bestOverlap = timePadding;
5100          }
5101        }
5102      }
5103    });
5104    return bestFragment;
5105  }
5106  isEndListAppended(type) {
5107    const lastFragmentEntity = this.endListFragments[type];
5108    return lastFragmentEntity !== undefined && (lastFragmentEntity.buffered || isPartial(lastFragmentEntity));
5109  }
5110  getState(fragment) {
5111    const fragKey = getFragmentKey(fragment);
5112    const fragmentEntity = this.fragments[fragKey];
5113    if (fragmentEntity) {
5114      if (!fragmentEntity.buffered) {
5115        return FragmentState.APPENDING;
5116      } else if (isPartial(fragmentEntity)) {
5117        return FragmentState.PARTIAL;
5118      } else {
5119        return FragmentState.OK;
5120      }
5121    }
5122    return FragmentState.NOT_LOADED;
5123  }
5124  isTimeBuffered(startPTS, endPTS, timeRange) {
5125    let startTime;
5126    let endTime;
5127    for (let i = 0; i < timeRange.length; i++) {
5128      startTime = timeRange.start(i) - this.bufferPadding;
5129      endTime = timeRange.end(i) + this.bufferPadding;
5130      if (startPTS >= startTime && endPTS <= endTime) {
5131        return true;
5132      }
5133      if (endPTS <= startTime) {
5134        // No need to check the rest of the timeRange as it is in order
5135        return false;
5136      }
5137    }
5138    return false;
5139  }
5140  onManifestLoading() {
5141    this.removeAllFragments();
5142  }
5143  onFragLoaded(event, data) {
5144    // don't track initsegment (for which sn is not a number)
5145    // don't track frags used for bitrateTest, they're irrelevant.
5146    if (data.frag.sn === 'initSegment' || data.frag.bitrateTest) {
5147      return;
5148    }
5149    const frag = data.frag;
5150    // Fragment entity `loaded` FragLoadedData is null when loading parts
5151    const loaded = data.part ? null : data;
5152    const fragKey = getFragmentKey(frag);
5153    this.fragments[fragKey] = {
5154      body: frag,
5155      appendedPTS: null,
5156      loaded,
5157      buffered: false,
5158      range: Object.create(null)
5159    };
5160  }
5161  onBufferAppended(event, data) {
5162    const {
5163      frag,
5164      part,
5165      timeRanges,
5166      type
5167    } = data;
5168    if (frag.sn === 'initSegment') {
5169      return;
5170    }
5171    const playlistType = frag.type;
5172    if (part) {
5173      let activeParts = this.activePartLists[playlistType];
5174      if (!activeParts) {
5175        this.activePartLists[playlistType] = activeParts = [];
5176      }
5177      activeParts.push(part);
5178    }
5179    // Store the latest timeRanges loaded in the buffer
5180    this.timeRanges = timeRanges;
5181    const timeRange = timeRanges[type];
5182    this.detectEvictedFragments(type, timeRange, playlistType, part);
5183  }
5184  onFragBuffered(event, data) {
5185    this.detectPartialFragments(data);
5186  }
5187  hasFragment(fragment) {
5188    const fragKey = getFragmentKey(fragment);
5189    return !!this.fragments[fragKey];
5190  }
5191  hasFragments(type) {
5192    const {
5193      fragments
5194    } = this;
5195    const keys = Object.keys(fragments);
5196    if (!type) {
5197      return keys.length > 0;
5198    }
5199    for (let i = keys.length; i--;) {
5200      const fragmentEntity = fragments[keys[i]];
5201      if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === type) {
5202        return true;
5203      }
5204    }
5205    return false;
5206  }
5207  hasParts(type) {
5208    var _this$activePartLists;
5209    return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length);
5210  }
5211  removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly) {
5212    if (withGapOnly && !this.hasGaps) {
5213      return;
5214    }
5215    Object.keys(this.fragments).forEach(key => {
5216      const fragmentEntity = this.fragments[key];
5217      if (!fragmentEntity) {
5218        return;
5219      }
5220      const frag = fragmentEntity.body;
5221      if (frag.type !== playlistType || withGapOnly && !frag.gap) {
5222        return;
5223      }
5224      if (frag.start < end && frag.end > start && (fragmentEntity.buffered || unbufferedOnly)) {
5225        this.removeFragment(frag);
5226      }
5227    });
5228  }
5229  removeFragment(fragment) {
5230    const fragKey = getFragmentKey(fragment);
5231    fragment.clearElementaryStreamInfo();
5232    const activeParts = this.activePartLists[fragment.type];
5233    if (activeParts) {
5234      const snToRemove = fragment.sn;
5235      this.activePartLists[fragment.type] = filterParts(activeParts, part => part.fragment.sn !== snToRemove);
5236    }
5237    delete this.fragments[fragKey];
5238    if (fragment.endList) {
5239      delete this.endListFragments[fragment.type];
5240    }
5241  }
5242  removeAllFragments() {
5243    var _this$hls;
5244    this.fragments = Object.create(null);
5245    this.endListFragments = Object.create(null);
5246    this.activePartLists = Object.create(null);
5247    this.hasGaps = false;
5248    const partlist = (_this$hls = this.hls) == null || (_this$hls = _this$hls.latestLevelDetails) == null ? void 0 : _this$hls.partList;
5249    if (partlist) {
5250      partlist.forEach(part => part.clearElementaryStreamInfo());
5251    }
5252  }
5253}
5254function isPartial(fragmentEntity) {
5255  var _fragmentEntity$range, _fragmentEntity$range2, _fragmentEntity$range3;
5256  return fragmentEntity.buffered && !!(fragmentEntity.body.gap || (_fragmentEntity$range = fragmentEntity.range.video) != null && _fragmentEntity$range.partial || (_fragmentEntity$range2 = fragmentEntity.range.audio) != null && _fragmentEntity$range2.partial || (_fragmentEntity$range3 = fragmentEntity.range.audiovideo) != null && _fragmentEntity$range3.partial);
5257}
5258function getFragmentKey(fragment) {
5259  return `${fragment.type}_${fragment.level}_${fragment.sn}`;
5260}
5261function filterParts(partList, predicate) {
5262  return partList.filter(part => {
5263    const keep = predicate(part);
5264    if (!keep) {
5265      part.clearElementaryStreamInfo();
5266    }
5267    return keep;
5268  });
5269}
5270
5271var DecrypterAesMode = {
5272  cbc: 0,
5273  ctr: 1
5274};
5275
5276class AESCrypto {
5277  constructor(subtle, iv, aesMode) {
5278    this.subtle = void 0;
5279    this.aesIV = void 0;
5280    this.aesMode = void 0;
5281    this.subtle = subtle;
5282    this.aesIV = iv;
5283    this.aesMode = aesMode;
5284  }
5285  decrypt(data, key) {
5286    switch (this.aesMode) {
5287      case DecrypterAesMode.cbc:
5288        return this.subtle.decrypt({
5289          name: 'AES-CBC',
5290          iv: this.aesIV
5291        }, key, data);
5292      case DecrypterAesMode.ctr:
5293        return this.subtle.decrypt({
5294          name: 'AES-CTR',
5295          counter: this.aesIV,
5296          length: 64
5297        },
5298        //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
5299        key, data);
5300      default:
5301        throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
5302    }
5303  }
5304}
5305
5306// PKCS7
5307function removePadding(array) {
5308  const outputBytes = array.byteLength;
5309  const paddingBytes = outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1);
5310  if (paddingBytes) {
5311    return array.slice(0, outputBytes - paddingBytes);
5312  }
5313  return array;
5314}
5315class AESDecryptor {
5316  constructor() {
5317    this.rcon = [0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
5318    this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
5319    this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)];
5320    this.sBox = new Uint32Array(256);
5321    this.invSBox = new Uint32Array(256);
5322    this.key = new Uint32Array(0);
5323    this.ksRows = 0;
5324    this.keySize = 0;
5325    this.keySchedule = void 0;
5326    this.invKeySchedule = void 0;
5327    this.initTable();
5328  }
5329
5330  // Using view.getUint32() also swaps the byte order.
5331  uint8ArrayToUint32Array_(arrayBuffer) {
5332    const view = new DataView(arrayBuffer);
5333    const newArray = new Uint32Array(4);
5334    for (let i = 0; i < 4; i++) {
5335      newArray[i] = view.getUint32(i * 4);
5336    }
5337    return newArray;
5338  }
5339  initTable() {
5340    const sBox = this.sBox;
5341    const invSBox = this.invSBox;
5342    const subMix = this.subMix;
5343    const subMix0 = subMix[0];
5344    const subMix1 = subMix[1];
5345    const subMix2 = subMix[2];
5346    const subMix3 = subMix[3];
5347    const invSubMix = this.invSubMix;
5348    const invSubMix0 = invSubMix[0];
5349    const invSubMix1 = invSubMix[1];
5350    const invSubMix2 = invSubMix[2];
5351    const invSubMix3 = invSubMix[3];
5352    const d = new Uint32Array(256);
5353    let x = 0;
5354    let xi = 0;
5355    let i = 0;
5356    for (i = 0; i < 256; i++) {
5357      if (i < 128) {
5358        d[i] = i << 1;
5359      } else {
5360        d[i] = i << 1 ^ 0x11b;
5361      }
5362    }
5363    for (i = 0; i < 256; i++) {
5364      let sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4;
5365      sx = sx >>> 8 ^ sx & 0xff ^ 0x63;
5366      sBox[x] = sx;
5367      invSBox[sx] = x;
5368
5369      // Compute multiplication
5370      const x2 = d[x];
5371      const x4 = d[x2];
5372      const x8 = d[x4];
5373
5374      // Compute sub/invSub bytes, mix columns tables
5375      let t = d[sx] * 0x101 ^ sx * 0x1010100;
5376      subMix0[x] = t << 24 | t >>> 8;
5377      subMix1[x] = t << 16 | t >>> 16;
5378      subMix2[x] = t << 8 | t >>> 24;
5379      subMix3[x] = t;
5380
5381      // Compute inv sub bytes, inv mix columns tables
5382      t = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
5383      invSubMix0[sx] = t << 24 | t >>> 8;
5384      invSubMix1[sx] = t << 16 | t >>> 16;
5385      invSubMix2[sx] = t << 8 | t >>> 24;
5386      invSubMix3[sx] = t;
5387
5388      // Compute next counter
5389      if (!x) {
5390        x = xi = 1;
5391      } else {
5392        x = x2 ^ d[d[d[x8 ^ x2]]];
5393        xi ^= d[d[xi]];
5394      }
5395    }
5396  }
5397  expandKey(keyBuffer) {
5398    // convert keyBuffer to Uint32Array
5399    const key = this.uint8ArrayToUint32Array_(keyBuffer);
5400    let sameKey = true;
5401    let offset = 0;
5402    while (offset < key.length && sameKey) {
5403      sameKey = key[offset] === this.key[offset];
5404      offset++;
5405    }
5406    if (sameKey) {
5407      return;
5408    }
5409    this.key = key;
5410    const keySize = this.keySize = key.length;
5411    if (keySize !== 4 && keySize !== 6 && keySize !== 8) {
5412      throw new Error('Invalid aes key size=' + keySize);
5413    }
5414    const ksRows = this.ksRows = (keySize + 6 + 1) * 4;
5415    let ksRow;
5416    let invKsRow;
5417    const keySchedule = this.keySchedule = new Uint32Array(ksRows);
5418    const invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows);
5419    const sbox = this.sBox;
5420    const rcon = this.rcon;
5421    const invSubMix = this.invSubMix;
5422    const invSubMix0 = invSubMix[0];
5423    const invSubMix1 = invSubMix[1];
5424    const invSubMix2 = invSubMix[2];
5425    const invSubMix3 = invSubMix[3];
5426    let prev;
5427    let t;
5428    for (ksRow = 0; ksRow < ksRows; ksRow++) {
5429      if (ksRow < keySize) {
5430        prev = keySchedule[ksRow] = key[ksRow];
5431        continue;
5432      }
5433      t = prev;
5434      if (ksRow % keySize === 0) {
5435        // Rot word
5436        t = t << 8 | t >>> 24;
5437
5438        // Sub word
5439        t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];
5440
5441        // Mix Rcon
5442        t ^= rcon[ksRow / keySize | 0] << 24;
5443      } else if (keySize > 6 && ksRow % keySize === 4) {
5444        // Sub word
5445        t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff];
5446      }
5447      keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0;
5448    }
5449    for (invKsRow = 0; invKsRow < ksRows; invKsRow++) {
5450      ksRow = ksRows - invKsRow;
5451      if (invKsRow & 3) {
5452        t = keySchedule[ksRow];
5453      } else {
5454        t = keySchedule[ksRow - 4];
5455      }
5456      if (invKsRow < 4 || ksRow <= 4) {
5457        invKeySchedule[invKsRow] = t;
5458      } else {
5459        invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 0xff]] ^ invSubMix2[sbox[t >>> 8 & 0xff]] ^ invSubMix3[sbox[t & 0xff]];
5460      }
5461      invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0;
5462    }
5463  }
5464
5465  // Adding this as a method greatly improves performance.
5466  networkToHostOrderSwap(word) {
5467    return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24;
5468  }
5469  decrypt(inputArrayBuffer, offset, aesIV) {
5470    const nRounds = this.keySize + 6;
5471    const invKeySchedule = this.invKeySchedule;
5472    const invSBOX = this.invSBox;
5473    const invSubMix = this.invSubMix;
5474    const invSubMix0 = invSubMix[0];
5475    const invSubMix1 = invSubMix[1];
5476    const invSubMix2 = invSubMix[2];
5477    const invSubMix3 = invSubMix[3];
5478    const initVector = this.uint8ArrayToUint32Array_(aesIV);
5479    let initVector0 = initVector[0];
5480    let initVector1 = initVector[1];
5481    let initVector2 = initVector[2];
5482    let initVector3 = initVector[3];
5483    const inputInt32 = new Int32Array(inputArrayBuffer);
5484    const outputInt32 = new Int32Array(inputInt32.length);
5485    let t0, t1, t2, t3;
5486    let s0, s1, s2, s3;
5487    let inputWords0, inputWords1, inputWords2, inputWords3;
5488    let ksRow, i;
5489    const swapWord = this.networkToHostOrderSwap;
5490    while (offset < inputInt32.length) {
5491      inputWords0 = swapWord(inputInt32[offset]);
5492      inputWords1 = swapWord(inputInt32[offset + 1]);
5493      inputWords2 = swapWord(inputInt32[offset + 2]);
5494      inputWords3 = swapWord(inputInt32[offset + 3]);
5495      s0 = inputWords0 ^ invKeySchedule[0];
5496      s1 = inputWords3 ^ invKeySchedule[1];
5497      s2 = inputWords2 ^ invKeySchedule[2];
5498      s3 = inputWords1 ^ invKeySchedule[3];
5499      ksRow = 4;
5500
5501      // Iterate through the rounds of decryption
5502      for (i = 1; i < nRounds; i++) {
5503        t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 0xff] ^ invSubMix2[s2 >> 8 & 0xff] ^ invSubMix3[s3 & 0xff] ^ invKeySchedule[ksRow];
5504        t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 0xff] ^ invSubMix2[s3 >> 8 & 0xff] ^ invSubMix3[s0 & 0xff] ^ invKeySchedule[ksRow + 1];
5505        t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 0xff] ^ invSubMix2[s0 >> 8 & 0xff] ^ invSubMix3[s1 & 0xff] ^ invKeySchedule[ksRow + 2];
5506        t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 0xff] ^ invSubMix2[s1 >> 8 & 0xff] ^ invSubMix3[s2 & 0xff] ^ invKeySchedule[ksRow + 3];
5507        // Update state
5508        s0 = t0;
5509        s1 = t1;
5510        s2 = t2;
5511        s3 = t3;
5512        ksRow = ksRow + 4;
5513      }
5514
5515      // Shift rows, sub bytes, add round key
5516      t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 0xff] << 16 ^ invSBOX[s2 >> 8 & 0xff] << 8 ^ invSBOX[s3 & 0xff] ^ invKeySchedule[ksRow];
5517      t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 0xff] << 16 ^ invSBOX[s3 >> 8 & 0xff] << 8 ^ invSBOX[s0 & 0xff] ^ invKeySchedule[ksRow + 1];
5518      t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 0xff] << 16 ^ invSBOX[s0 >> 8 & 0xff] << 8 ^ invSBOX[s1 & 0xff] ^ invKeySchedule[ksRow + 2];
5519      t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 0xff] << 16 ^ invSBOX[s1 >> 8 & 0xff] << 8 ^ invSBOX[s2 & 0xff] ^ invKeySchedule[ksRow + 3];
5520
5521      // Write
5522      outputInt32[offset] = swapWord(t0 ^ initVector0);
5523      outputInt32[offset + 1] = swapWord(t3 ^ initVector1);
5524      outputInt32[offset + 2] = swapWord(t2 ^ initVector2);
5525      outputInt32[offset + 3] = swapWord(t1 ^ initVector3);
5526
5527      // reset initVector to last 4 unsigned int
5528      initVector0 = inputWords0;
5529      initVector1 = inputWords1;
5530      initVector2 = inputWords2;
5531      initVector3 = inputWords3;
5532      offset = offset + 4;
5533    }
5534    return outputInt32.buffer;
5535  }
5536}
5537
5538class FastAESKey {
5539  constructor(subtle, key, aesMode) {
5540    this.subtle = void 0;
5541    this.key = void 0;
5542    this.aesMode = void 0;
5543    this.subtle = subtle;
5544    this.key = key;
5545    this.aesMode = aesMode;
5546  }
5547  expandKey() {
5548    const subtleAlgoName = getSubtleAlgoName(this.aesMode);
5549    return this.subtle.importKey('raw', this.key, {
5550      name: subtleAlgoName
5551    }, false, ['encrypt', 'decrypt']);
5552  }
5553}
5554function getSubtleAlgoName(aesMode) {
5555  switch (aesMode) {
5556    case DecrypterAesMode.cbc:
5557      return 'AES-CBC';
5558    case DecrypterAesMode.ctr:
5559      return 'AES-CTR';
5560    default:
5561      throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
5562  }
5563}
5564
5565const CHUNK_SIZE = 16; // 16 bytes, 128 bits
5566
5567class Decrypter {
5568  constructor(config, {
5569    removePKCS7Padding = true
5570  } = {}) {
5571    this.logEnabled = true;
5572    this.removePKCS7Padding = void 0;
5573    this.subtle = null;
5574    this.softwareDecrypter = null;
5575    this.key = null;
5576    this.fastAesKey = null;
5577    this.remainderData = null;
5578    this.currentIV = null;
5579    this.currentResult = null;
5580    this.useSoftware = void 0;
5581    this.enableSoftwareAES = void 0;
5582    this.enableSoftwareAES = config.enableSoftwareAES;
5583    this.removePKCS7Padding = removePKCS7Padding;
5584    // built in decryptor expects PKCS7 padding
5585    if (removePKCS7Padding) {
5586      try {
5587        const browserCrypto = self.crypto;
5588        if (browserCrypto) {
5589          this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
5590        }
5591      } catch (e) {
5592        /* no-op */
5593      }
5594    }
5595    this.useSoftware = !this.subtle;
5596  }
5597  destroy() {
5598    this.subtle = null;
5599    this.softwareDecrypter = null;
5600    this.key = null;
5601    this.fastAesKey = null;
5602    this.remainderData = null;
5603    this.currentIV = null;
5604    this.currentResult = null;
5605  }
5606  isSync() {
5607    return this.useSoftware;
5608  }
5609  flush() {
5610    const {
5611      currentResult,
5612      remainderData
5613    } = this;
5614    if (!currentResult || remainderData) {
5615      this.reset();
5616      return null;
5617    }
5618    const data = new Uint8Array(currentResult);
5619    this.reset();
5620    if (this.removePKCS7Padding) {
5621      return removePadding(data);
5622    }
5623    return data;
5624  }
5625  reset() {
5626    this.currentResult = null;
5627    this.currentIV = null;
5628    this.remainderData = null;
5629    if (this.softwareDecrypter) {
5630      this.softwareDecrypter = null;
5631    }
5632  }
5633  decrypt(data, key, iv, aesMode) {
5634    if (this.useSoftware) {
5635      return new Promise((resolve, reject) => {
5636        const dataView = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
5637        this.softwareDecrypt(dataView, key, iv, aesMode);
5638        const decryptResult = this.flush();
5639        if (decryptResult) {
5640          resolve(decryptResult.buffer);
5641        } else {
5642          reject(new Error('[softwareDecrypt] Failed to decrypt data'));
5643        }
5644      });
5645    }
5646    return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
5647  }
5648
5649  // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
5650  // data is handled in the flush() call
5651  softwareDecrypt(data, key, iv, aesMode) {
5652    const {
5653      currentIV,
5654      currentResult,
5655      remainderData
5656    } = this;
5657    if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
5658      logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
5659      return null;
5660    }
5661    this.logOnce('JS AES decrypt');
5662    // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
5663    // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
5664    // the end on flush(), but by that time we have already received all bytes for the segment.
5665    // Progressive decryption does not work with WebCrypto
5666
5667    if (remainderData) {
5668      data = appendUint8Array(remainderData, data);
5669      this.remainderData = null;
5670    }
5671
5672    // Byte length must be a multiple of 16 (AES-128 = 128 bit blocks = 16 bytes)
5673    const currentChunk = this.getValidChunk(data);
5674    if (!currentChunk.length) {
5675      return null;
5676    }
5677    if (currentIV) {
5678      iv = currentIV;
5679    }
5680    let softwareDecrypter = this.softwareDecrypter;
5681    if (!softwareDecrypter) {
5682      softwareDecrypter = this.softwareDecrypter = new AESDecryptor();
5683    }
5684    softwareDecrypter.expandKey(key);
5685    const result = currentResult;
5686    this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv);
5687    this.currentIV = currentChunk.slice(-16).buffer;
5688    if (!result) {
5689      return null;
5690    }
5691    return result;
5692  }
5693  webCryptoDecrypt(data, key, iv, aesMode) {
5694    if (this.key !== key || !this.fastAesKey) {
5695      if (!this.subtle) {
5696        return Promise.resolve(this.onWebCryptoError(data, key, iv, aesMode));
5697      }
5698      this.key = key;
5699      this.fastAesKey = new FastAESKey(this.subtle, key, aesMode);
5700    }
5701    return this.fastAesKey.expandKey().then(aesKey => {
5702      // decrypt using web crypto
5703      if (!this.subtle) {
5704        return Promise.reject(new Error('web crypto not initialized'));
5705      }
5706      this.logOnce('WebCrypto AES decrypt');
5707      const crypto = new AESCrypto(this.subtle, new Uint8Array(iv), aesMode);
5708      return crypto.decrypt(data.buffer, aesKey);
5709    }).catch(err => {
5710      logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
5711      return this.onWebCryptoError(data, key, iv, aesMode);
5712    });
5713  }
5714  onWebCryptoError(data, key, iv, aesMode) {
5715    const enableSoftwareAES = this.enableSoftwareAES;
5716    if (enableSoftwareAES) {
5717      this.useSoftware = true;
5718      this.logEnabled = true;
5719      this.softwareDecrypt(data, key, iv, aesMode);
5720      const decryptResult = this.flush();
5721      if (decryptResult) {
5722        return decryptResult.buffer;
5723      }
5724    }
5725    throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
5726  }
5727  getValidChunk(data) {
5728    let currentChunk = data;
5729    const splitPoint = data.length - data.length % CHUNK_SIZE;
5730    if (splitPoint !== data.length) {
5731      currentChunk = data.slice(0, splitPoint);
5732      this.remainderData = data.slice(splitPoint);
5733    }
5734    return currentChunk;
5735  }
5736  logOnce(msg) {
5737    if (!this.logEnabled) {
5738      return;
5739    }
5740    logger.log(`[decrypter]: ${msg}`);
5741    this.logEnabled = false;
5742  }
5743}
5744
5745const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb
5746
5747class FragmentLoader {
5748  constructor(config) {
5749    this.config = void 0;
5750    this.loader = null;
5751    this.partLoadTimeout = -1;
5752    this.config = config;
5753  }
5754  destroy() {
5755    if (this.loader) {
5756      this.loader.destroy();
5757      this.loader = null;
5758    }
5759  }
5760  abort() {
5761    if (this.loader) {
5762      // Abort the loader for current fragment. Only one may load at any given time
5763      this.loader.abort();
5764    }
5765  }
5766  load(frag, onProgress) {
5767    const url = frag.url;
5768    if (!url) {
5769      return Promise.reject(new LoadError({
5770        type: ErrorTypes.NETWORK_ERROR,
5771        details: ErrorDetails.FRAG_LOAD_ERROR,
5772        fatal: false,
5773        frag,
5774        error: new Error(`Fragment does not have a ${url ? 'part list' : 'url'}`),
5775        networkDetails: null
5776      }));
5777    }
5778    this.abort();
5779    const config = this.config;
5780    const FragmentILoader = config.fLoader;
5781    const DefaultILoader = config.loader;
5782    return new Promise((resolve, reject) => {
5783      if (this.loader) {
5784        this.loader.destroy();
5785      }
5786      if (frag.gap) {
5787        if (frag.tagList.some(tags => tags[0] === 'GAP')) {
5788          reject(createGapLoadError(frag));
5789          return;
5790        } else {
5791          // Reset temporary treatment as GAP tag
5792          frag.gap = false;
5793        }
5794      }
5795      const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
5796      const loaderContext = createLoaderContext(frag);
5797      frag.loader = loader;
5798      const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
5799      const loaderConfig = {
5800        loadPolicy,
5801        timeout: loadPolicy.maxLoadTimeMs,
5802        maxRetry: 0,
5803        retryDelay: 0,
5804        maxRetryDelay: 0,
5805        highWaterMark: frag.sn === 'initSegment' ? Infinity : MIN_CHUNK_SIZE
5806      };
5807      // Assign frag stats to the loader's stats reference
5808      frag.stats = loader.stats;
5809      const callbacks = {
5810        onSuccess: (response, stats, context, networkDetails) => {
5811          this.resetLoader(frag, loader);
5812          let payload = response.data;
5813          if (context.resetIV && frag.decryptdata) {
5814            frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16));
5815            payload = payload.slice(16);
5816          }
5817          resolve({
5818            frag,
5819            part: null,
5820            payload,
5821            networkDetails
5822          });
5823        },
5824        onError: (response, context, networkDetails, stats) => {
5825          this.resetLoader(frag, loader);
5826          reject(new LoadError({
5827            type: ErrorTypes.NETWORK_ERROR,
5828            details: ErrorDetails.FRAG_LOAD_ERROR,
5829            fatal: false,
5830            frag,
5831            response: _objectSpread2({
5832              url,
5833              data: undefined
5834            }, response),
5835            error: new Error(`HTTP Error ${response.code} ${response.text}`),
5836            networkDetails,
5837            stats
5838          }));
5839        },
5840        onAbort: (stats, context, networkDetails) => {
5841          this.resetLoader(frag, loader);
5842          reject(new LoadError({
5843            type: ErrorTypes.NETWORK_ERROR,
5844            details: ErrorDetails.INTERNAL_ABORTED,
5845            fatal: false,
5846            frag,
5847            error: new Error('Aborted'),
5848            networkDetails,
5849            stats
5850          }));
5851        },
5852        onTimeout: (stats, context, networkDetails) => {
5853          this.resetLoader(frag, loader);
5854          reject(new LoadError({
5855            type: ErrorTypes.NETWORK_ERROR,
5856            details: ErrorDetails.FRAG_LOAD_TIMEOUT,
5857            fatal: false,
5858            frag,
5859            error: new Error(`Timeout after ${loaderConfig.timeout}ms`),
5860            networkDetails,
5861            stats
5862          }));
5863        }
5864      };
5865      if (onProgress) {
5866        callbacks.onProgress = (stats, context, data, networkDetails) => onProgress({
5867          frag,
5868          part: null,
5869          payload: data,
5870          networkDetails
5871        });
5872      }
5873      loader.load(loaderContext, loaderConfig, callbacks);
5874    });
5875  }
5876  loadPart(frag, part, onProgress) {
5877    this.abort();
5878    const config = this.config;
5879    const FragmentILoader = config.fLoader;
5880    const DefaultILoader = config.loader;
5881    return new Promise((resolve, reject) => {
5882      if (this.loader) {
5883        this.loader.destroy();
5884      }
5885      if (frag.gap || part.gap) {
5886        reject(createGapLoadError(frag, part));
5887        return;
5888      }
5889      const loader = this.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config);
5890      const loaderContext = createLoaderContext(frag, part);
5891      frag.loader = loader;
5892      // Should we define another load policy for parts?
5893      const loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default);
5894      const loaderConfig = {
5895        loadPolicy,
5896        timeout: loadPolicy.maxLoadTimeMs,
5897        maxRetry: 0,
5898        retryDelay: 0,
5899        maxRetryDelay: 0,
5900        highWaterMark: MIN_CHUNK_SIZE
5901      };
5902      // Assign part stats to the loader's stats reference
5903      part.stats = loader.stats;
5904      loader.load(loaderContext, loaderConfig, {
5905        onSuccess: (response, stats, context, networkDetails) => {
5906          this.resetLoader(frag, loader);
5907          this.updateStatsFromPart(frag, part);
5908          const partLoadedData = {
5909            frag,
5910            part,
5911            payload: response.data,
5912            networkDetails
5913          };
5914          onProgress(partLoadedData);
5915          resolve(partLoadedData);
5916        },
5917        onError: (response, context, networkDetails, stats) => {
5918          this.resetLoader(frag, loader);
5919          reject(new LoadError({
5920            type: ErrorTypes.NETWORK_ERROR,
5921            details: ErrorDetails.FRAG_LOAD_ERROR,
5922            fatal: false,
5923            frag,
5924            part,
5925            response: _objectSpread2({
5926              url: loaderContext.url,
5927              data: undefined
5928            }, response),
5929            error: new Error(`HTTP Error ${response.code} ${response.text}`),
5930            networkDetails,
5931            stats
5932          }));
5933        },
5934        onAbort: (stats, context, networkDetails) => {
5935          frag.stats.aborted = part.stats.aborted;
5936          this.resetLoader(frag, loader);
5937          reject(new LoadError({
5938            type: ErrorTypes.NETWORK_ERROR,
5939            details: ErrorDetails.INTERNAL_ABORTED,
5940            fatal: false,
5941            frag,
5942            part,
5943            error: new Error('Aborted'),
5944            networkDetails,
5945            stats
5946          }));
5947        },
5948        onTimeout: (stats, context, networkDetails) => {
5949          this.resetLoader(frag, loader);
5950          reject(new LoadError({
5951            type: ErrorTypes.NETWORK_ERROR,
5952            details: ErrorDetails.FRAG_LOAD_TIMEOUT,
5953            fatal: false,
5954            frag,
5955            part,
5956            error: new Error(`Timeout after ${loaderConfig.timeout}ms`),
5957            networkDetails,
5958            stats
5959          }));
5960        }
5961      });
5962    });
5963  }
5964  updateStatsFromPart(frag, part) {
5965    const fragStats = frag.stats;
5966    const partStats = part.stats;
5967    const partTotal = partStats.total;
5968    fragStats.loaded += partStats.loaded;
5969    if (partTotal) {
5970      const estTotalParts = Math.round(frag.duration / part.duration);
5971      const estLoadedParts = Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts);
5972      const estRemainingParts = estTotalParts - estLoadedParts;
5973      const estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts);
5974      fragStats.total = fragStats.loaded + estRemainingBytes;
5975    } else {
5976      fragStats.total = Math.max(fragStats.loaded, fragStats.total);
5977    }
5978    const fragLoading = fragStats.loading;
5979    const partLoading = partStats.loading;
5980    if (fragLoading.start) {
5981      // add to fragment loader latency
5982      fragLoading.first += partLoading.first - partLoading.start;
5983    } else {
5984      fragLoading.start = partLoading.start;
5985      fragLoading.first = partLoading.first;
5986    }
5987    fragLoading.end = partLoading.end;
5988  }
5989  resetLoader(frag, loader) {
5990    frag.loader = null;
5991    if (this.loader === loader) {
5992      self.clearTimeout(this.partLoadTimeout);
5993      this.loader = null;
5994    }
5995    loader.destroy();
5996  }
5997}
5998function createLoaderContext(frag, part = null) {
5999  const segment = part || frag;
6000  const loaderContext = {
6001    frag,
6002    part,
6003    responseType: 'arraybuffer',
6004    url: segment.url,
6005    headers: {},
6006    rangeStart: 0,
6007    rangeEnd: 0
6008  };
6009  const start = segment.byteRangeStartOffset;
6010  const end = segment.byteRangeEndOffset;
6011  if (isFiniteNumber(start) && isFiniteNumber(end)) {
6012    var _frag$decryptdata;
6013    let byteRangeStart = start;
6014    let byteRangeEnd = end;
6015    if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
6016      // MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
6017      // has the unencrypted size specified in the range.
6018      // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
6019      const fragmentLen = end - start;
6020      if (fragmentLen % 16) {
6021        byteRangeEnd = end + (16 - fragmentLen % 16);
6022      }
6023      if (start !== 0) {
6024        loaderContext.resetIV = true;
6025        byteRangeStart = start - 16;
6026      }
6027    }
6028    loaderContext.rangeStart = byteRangeStart;
6029    loaderContext.rangeEnd = byteRangeEnd;
6030  }
6031  return loaderContext;
6032}
6033function createGapLoadError(frag, part) {
6034  const error = new Error(`GAP ${frag.gap ? 'tag' : 'attribute'} found`);
6035  const errorData = {
6036    type: ErrorTypes.MEDIA_ERROR,
6037    details: ErrorDetails.FRAG_GAP,
6038    fatal: false,
6039    frag,
6040    error,
6041    networkDetails: null
6042  };
6043  if (part) {
6044    errorData.part = part;
6045  }
6046  (part ? part : frag).stats.aborted = true;
6047  return new LoadError(errorData);
6048}
6049function isMethodFullSegmentAesCbc(method) {
6050  return method === 'AES-128' || method === 'AES-256';
6051}
6052class LoadError extends Error {
6053  constructor(data) {
6054    super(data.error.message);
6055    this.data = void 0;
6056    this.data = data;
6057  }
6058}
6059
6060/**
6061 * @ignore
6062 * Sub-class specialization of EventHandler base class.
6063 *
6064 * TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop,
6065 * scheduled asynchroneously, avoiding recursive calls in the same tick.
6066 *
6067 * The task itself is implemented in `doTick`. It can be requested and called for single execution
6068 * using the `tick` method.
6069 *
6070 * It will be assured that the task execution method (`tick`) only gets called once per main loop "tick",
6071 * no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly.
6072 *
6073 * If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`,
6074 * and cancelled with `clearNextTick`.
6075 *
6076 * The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`).
6077 *
6078 * Sub-classes need to implement the `doTick` method which will effectively have the task execution routine.
6079 *
6080 * Further explanations:
6081 *
6082 * The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously
6083 * only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks.
6084 *
6085 * When the task execution (`tick` method) is called in re-entrant way this is detected and
6086 * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
6087 * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
6088 */
6089class TaskLoop extends Logger {
6090  constructor(label, logger) {
6091    super(label, logger);
6092    this._boundTick = void 0;
6093    this._tickTimer = null;
6094    this._tickInterval = null;
6095    this._tickCallCount = 0;
6096    this._boundTick = this.tick.bind(this);
6097  }
6098  destroy() {
6099    this.onHandlerDestroying();
6100    this.onHandlerDestroyed();
6101  }
6102  onHandlerDestroying() {
6103    // clear all timers before unregistering from event bus
6104    this.clearNextTick();
6105    this.clearInterval();
6106  }
6107  onHandlerDestroyed() {}
6108  hasInterval() {
6109    return !!this._tickInterval;
6110  }
6111  hasNextTick() {
6112    return !!this._tickTimer;
6113  }
6114
6115  /**
6116   * @param millis - Interval time (ms)
6117   * @eturns True when interval has been scheduled, false when already scheduled (no effect)
6118   */
6119  setInterval(millis) {
6120    if (!this._tickInterval) {
6121      this._tickCallCount = 0;
6122      this._tickInterval = self.setInterval(this._boundTick, millis);
6123      return true;
6124    }
6125    return false;
6126  }
6127
6128  /**
6129   * @returns True when interval was cleared, false when none was set (no effect)
6130   */
6131  clearInterval() {
6132    if (this._tickInterval) {
6133      self.clearInterval(this._tickInterval);
6134      this._tickInterval = null;
6135      return true;
6136    }
6137    return false;
6138  }
6139
6140  /**
6141   * @returns True when timeout was cleared, false when none was set (no effect)
6142   */
6143  clearNextTick() {
6144    if (this._tickTimer) {
6145      self.clearTimeout(this._tickTimer);
6146      this._tickTimer = null;
6147      return true;
6148    }
6149    return false;
6150  }
6151
6152  /**
6153   * Will call the subclass doTick implementation in this main loop tick
6154   * or in the next one (via setTimeout(,0)) in case it has already been called
6155   * in this tick (in case this is a re-entrant call).
6156   */
6157  tick() {
6158    this._tickCallCount++;
6159    if (this._tickCallCount === 1) {
6160      this.doTick();
6161      // re-entrant call to tick from previous doTick call stack
6162      // -> schedule a call on the next main loop iteration to process this task processing request
6163      if (this._tickCallCount > 1) {
6164        // make sure only one timer exists at any time at max
6165        this.tickImmediate();
6166      }
6167      this._tickCallCount = 0;
6168    }
6169  }
6170  tickImmediate() {
6171    this.clearNextTick();
6172    this._tickTimer = self.setTimeout(this._boundTick, 0);
6173  }
6174
6175  /**
6176   * For subclass to implement task logic
6177   * @abstract
6178   */
6179  doTick() {}
6180}
6181
6182class ChunkMetadata {
6183  constructor(level, sn, id, size = 0, part = -1, partial = false) {
6184    this.level = void 0;
6185    this.sn = void 0;
6186    this.part = void 0;
6187    this.id = void 0;
6188    this.size = void 0;
6189    this.partial = void 0;
6190    this.transmuxing = getNewPerformanceTiming();
6191    this.buffering = {
6192      audio: getNewPerformanceTiming(),
6193      video: getNewPerformanceTiming(),
6194      audiovideo: getNewPerformanceTiming()
6195    };
6196    this.level = level;
6197    this.sn = sn;
6198    this.id = id;
6199    this.size = size;
6200    this.part = part;
6201    this.partial = partial;
6202  }
6203}
6204function getNewPerformanceTiming() {
6205  return {
6206    start: 0,
6207    executeStart: 0,
6208    executeEnd: 0,
6209    end: 0
6210  };
6211}
6212
6213/**
6214 * Provides methods dealing with buffer length retrieval for example.
6215 *
6216 * In general, a helper around HTML5 MediaElement TimeRanges gathered from `buffered` property.
6217 *
6218 * Also @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered
6219 */
6220
6221const noopBuffered = {
6222  length: 0,
6223  start: () => 0,
6224  end: () => 0
6225};
6226class BufferHelper {
6227  /**
6228   * Return true if `media`'s buffered include `position`
6229   */
6230  static isBuffered(media, position) {
6231    if (media) {
6232      const buffered = BufferHelper.getBuffered(media);
6233      for (let i = buffered.length; i--;) {
6234        if (position >= buffered.start(i) && position <= buffered.end(i)) {
6235          return true;
6236        }
6237      }
6238    }
6239    return false;
6240  }
6241  static bufferedRanges(media) {
6242    if (media) {
6243      const timeRanges = BufferHelper.getBuffered(media);
6244      return BufferHelper.timeRangesToArray(timeRanges);
6245    }
6246    return [];
6247  }
6248  static timeRangesToArray(timeRanges) {
6249    const buffered = [];
6250    for (let i = 0; i < timeRanges.length; i++) {
6251      buffered.push({
6252        start: timeRanges.start(i),
6253        end: timeRanges.end(i)
6254      });
6255    }
6256    return buffered;
6257  }
6258  static bufferInfo(media, pos, maxHoleDuration) {
6259    if (media) {
6260      const buffered = BufferHelper.bufferedRanges(media);
6261      if (buffered.length) {
6262        return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
6263      }
6264    }
6265    return {
6266      len: 0,
6267      start: pos,
6268      end: pos,
6269      bufferedIndex: -1
6270    };
6271  }
6272  static bufferedInfo(buffered, pos, maxHoleDuration) {
6273    pos = Math.max(0, pos);
6274    // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
6275    if (buffered.length > 1) {
6276      buffered.sort((a, b) => a.start - b.start || b.end - a.end);
6277    }
6278    let bufferedIndex = -1;
6279    let buffered2 = [];
6280    if (maxHoleDuration) {
6281      // there might be some small holes between buffer time range
6282      // consider that holes smaller than maxHoleDuration are irrelevant and build another
6283      // buffer time range representations that discards those holes
6284      for (let i = 0; i < buffered.length; i++) {
6285        if (pos >= buffered[i].start && pos <= buffered[i].end) {
6286          bufferedIndex = i;
6287        }
6288        const buf2len = buffered2.length;
6289        if (buf2len) {
6290          const buf2end = buffered2[buf2len - 1].end;
6291          // if small hole (value between 0 or maxHoleDuration ) or overlapping (negative)
6292          if (buffered[i].start - buf2end < maxHoleDuration) {
6293            // merge overlapping time ranges
6294            // update lastRange.end only if smaller than item.end
6295            // e.g.  [ 1, 15] with  [ 2,8] => [ 1,15] (no need to modify lastRange.end)
6296            // whereas [ 1, 8] with  [ 2,15] => [ 1,15] ( lastRange should switch from [1,8] to [1,15])
6297            if (buffered[i].end > buf2end) {
6298              buffered2[buf2len - 1].end = buffered[i].end;
6299            }
6300          } else {
6301            // big hole
6302            buffered2.push(buffered[i]);
6303          }
6304        } else {
6305          // first value
6306          buffered2.push(buffered[i]);
6307        }
6308      }
6309    } else {
6310      buffered2 = buffered;
6311    }
6312    let bufferLen = 0;
6313    let nextStart;
6314
6315    // bufferStart and bufferEnd are buffer boundaries around current playback position (pos)
6316    let bufferStart = pos;
6317    let bufferEnd = pos;
6318    for (let i = 0; i < buffered2.length; i++) {
6319      const start = buffered2[i].start;
6320      const end = buffered2[i].end;
6321      // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i));
6322      if (bufferedIndex === -1 && pos >= start && pos <= end) {
6323        bufferedIndex = i;
6324      }
6325      if (pos + maxHoleDuration >= start && pos < end) {
6326        // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length
6327        bufferStart = start;
6328        bufferEnd = end;
6329        bufferLen = bufferEnd - pos;
6330      } else if (pos + maxHoleDuration < start) {
6331        nextStart = start;
6332        break;
6333      }
6334    }
6335    return {
6336      len: bufferLen,
6337      start: bufferStart || 0,
6338      end: bufferEnd || 0,
6339      nextStart,
6340      buffered,
6341      bufferedIndex
6342    };
6343  }
6344
6345  /**
6346   * Safe method to get buffered property.
6347   * SourceBuffer.buffered may throw if SourceBuffer is removed from it's MediaSource
6348   */
6349  static getBuffered(media) {
6350    try {
6351      return media.buffered || noopBuffered;
6352    } catch (e) {
6353      logger.log('failed to get media.buffered', e);
6354      return noopBuffered;
6355    }
6356  }
6357}
6358
6359const VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g;
6360function hasVariableReferences(str) {
6361  return VARIABLE_REPLACEMENT_REGEX.test(str);
6362}
6363function substituteVariables(parsed, value) {
6364  if (parsed.variableList !== null || parsed.hasVariableRefs) {
6365    const variableList = parsed.variableList;
6366    return value.replace(VARIABLE_REPLACEMENT_REGEX, variableReference => {
6367      const variableName = variableReference.substring(2, variableReference.length - 1);
6368      const variableValue = variableList == null ? void 0 : variableList[variableName];
6369      if (variableValue === undefined) {
6370        parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`Missing preceding EXT-X-DEFINE tag for Variable Reference: "${variableName}"`));
6371        return variableReference;
6372      }
6373      return variableValue;
6374    });
6375  }
6376  return value;
6377}
6378function addVariableDefinition(parsed, attr, parentUrl) {
6379  let variableList = parsed.variableList;
6380  if (!variableList) {
6381    parsed.variableList = variableList = {};
6382  }
6383  let NAME;
6384  let VALUE;
6385  if ('QUERYPARAM' in attr) {
6386    NAME = attr.QUERYPARAM;
6387    try {
6388      const searchParams = new self.URL(parentUrl).searchParams;
6389      if (searchParams.has(NAME)) {
6390        VALUE = searchParams.get(NAME);
6391      } else {
6392        throw new Error(`"${NAME}" does not match any query parameter in URI: "${parentUrl}"`);
6393      }
6394    } catch (error) {
6395      parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE QUERYPARAM: ${error.message}`));
6396    }
6397  } else {
6398    NAME = attr.NAME;
6399    VALUE = attr.VALUE;
6400  }
6401  if (NAME in variableList) {
6402    parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE duplicate Variable Name declarations: "${NAME}"`));
6403  } else {
6404    variableList[NAME] = VALUE || '';
6405  }
6406}
6407function importVariableDefinition(parsed, attr, sourceVariableList) {
6408  const IMPORT = attr.IMPORT;
6409  if (sourceVariableList && IMPORT in sourceVariableList) {
6410    let variableList = parsed.variableList;
6411    if (!variableList) {
6412      parsed.variableList = variableList = {};
6413    }
6414    variableList[IMPORT] = sourceVariableList[IMPORT];
6415  } else {
6416    parsed.playlistParsingError || (parsed.playlistParsingError = new Error(`EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "${IMPORT}"`));
6417  }
6418}
6419
6420const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
6421const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
6422
6423// adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js
6424class AttrList {
6425  constructor(attrs, parsed) {
6426    if (typeof attrs === 'string') {
6427      attrs = AttrList.parseAttrList(attrs, parsed);
6428    }
6429    _extends(this, attrs);
6430  }
6431  get clientAttrs() {
6432    return Object.keys(this).filter(attr => attr.substring(0, 2) === 'X-');
6433  }
6434  decimalInteger(attrName) {
6435    const intValue = parseInt(this[attrName], 10);
6436    if (intValue > Number.MAX_SAFE_INTEGER) {
6437      return Infinity;
6438    }
6439    return intValue;
6440  }
6441  hexadecimalInteger(attrName) {
6442    if (this[attrName]) {
6443      let stringValue = (this[attrName] || '0x').slice(2);
6444      stringValue = (stringValue.length & 1 ? '0' : '') + stringValue;
6445      const value = new Uint8Array(stringValue.length / 2);
6446      for (let i = 0; i < stringValue.length / 2; i++) {
6447        value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
6448      }
6449      return value;
6450    }
6451    return null;
6452  }
6453  hexadecimalIntegerAsNumber(attrName) {
6454    const intValue = parseInt(this[attrName], 16);
6455    if (intValue > Number.MAX_SAFE_INTEGER) {
6456      return Infinity;
6457    }
6458    return intValue;
6459  }
6460  decimalFloatingPoint(attrName) {
6461    return parseFloat(this[attrName]);
6462  }
6463  optionalFloat(attrName, defaultValue) {
6464    const value = this[attrName];
6465    return value ? parseFloat(value) : defaultValue;
6466  }
6467  enumeratedString(attrName) {
6468    return this[attrName];
6469  }
6470  enumeratedStringList(attrName, dict) {
6471    const attrValue = this[attrName];
6472    return (attrValue ? attrValue.split(/[ ,]+/) : []).reduce((result, identifier) => {
6473      result[identifier.toLowerCase()] = true;
6474      return result;
6475    }, dict);
6476  }
6477  bool(attrName) {
6478    return this[attrName] === 'YES';
6479  }
6480  decimalResolution(attrName) {
6481    const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
6482    if (res === null) {
6483      return undefined;
6484    }
6485    return {
6486      width: parseInt(res[1], 10),
6487      height: parseInt(res[2], 10)
6488    };
6489  }
6490  static parseAttrList(input, parsed) {
6491    let match;
6492    const attrs = {};
6493    const quote = '"';
6494    ATTR_LIST_REGEX.lastIndex = 0;
6495    while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
6496      const name = match[1].trim();
6497      let value = match[2];
6498      const quotedString = value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1;
6499      let hexadecimalSequence = false;
6500      if (quotedString) {
6501        value = value.slice(1, -1);
6502      } else {
6503        switch (name) {
6504          case 'IV':
6505          case 'SCTE35-CMD':
6506          case 'SCTE35-IN':
6507          case 'SCTE35-OUT':
6508            hexadecimalSequence = true;
6509        }
6510      }
6511      if (parsed && (quotedString || hexadecimalSequence)) {
6512        {
6513          value = substituteVariables(parsed, value);
6514        }
6515      } else if (!hexadecimalSequence && !quotedString) {
6516        switch (name) {
6517          case 'CLOSED-CAPTIONS':
6518            if (value === 'NONE') {
6519              break;
6520            }
6521          // falls through
6522          case 'ALLOWED-CPC':
6523          case 'CLASS':
6524          case 'ASSOC-LANGUAGE':
6525          case 'AUDIO':
6526          case 'BYTERANGE':
6527          case 'CHANNELS':
6528          case 'CHARACTERISTICS':
6529          case 'CODECS':
6530          case 'DATA-ID':
6531          case 'END-DATE':
6532          case 'GROUP-ID':
6533          case 'ID':
6534          case 'IMPORT':
6535          case 'INSTREAM-ID':
6536          case 'KEYFORMAT':
6537          case 'KEYFORMATVERSIONS':
6538          case 'LANGUAGE':
6539          case 'NAME':
6540          case 'PATHWAY-ID':
6541          case 'QUERYPARAM':
6542          case 'RECENTLY-REMOVED-DATERANGES':
6543          case 'SERVER-URI':
6544          case 'STABLE-RENDITION-ID':
6545          case 'STABLE-VARIANT-ID':
6546          case 'START-DATE':
6547          case 'SUBTITLES':
6548          case 'SUPPLEMENTAL-CODECS':
6549          case 'URI':
6550          case 'VALUE':
6551          case 'VIDEO':
6552          case 'X-ASSET-LIST':
6553          case 'X-ASSET-URI':
6554            // Since we are not checking tag:attribute combination, just warn rather than ignoring attribute
6555            logger.warn(`${input}: attribute ${name} is missing quotes`);
6556          // continue;
6557        }
6558      }
6559      attrs[name] = value;
6560    }
6561    return attrs;
6562  }
6563}
6564
6565// Avoid exporting const enum so that these values can be inlined
6566
6567const CLASS_INTERSTITIAL = 'com.apple.hls.interstitial';
6568function isDateRangeCueAttribute(attrName) {
6569  return attrName !== "ID" && attrName !== "CLASS" && attrName !== "CUE" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT";
6570}
6571function isSCTE35Attribute(attrName) {
6572  return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN" || attrName === "SCTE35-CMD";
6573}
6574class DateRange {
6575  constructor(dateRangeAttr, dateRangeWithSameId, tagCount = 0) {
6576    var _dateRangeWithSameId$;
6577    this.attr = void 0;
6578    this.tagAnchor = void 0;
6579    this.tagOrder = void 0;
6580    this._startDate = void 0;
6581    this._endDate = void 0;
6582    this._dateAtEnd = void 0;
6583    this._cue = void 0;
6584    this._badValueForSameId = void 0;
6585    this.tagAnchor = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagAnchor) || null;
6586    this.tagOrder = (_dateRangeWithSameId$ = dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.tagOrder) != null ? _dateRangeWithSameId$ : tagCount;
6587    if (dateRangeWithSameId) {
6588      const previousAttr = dateRangeWithSameId.attr;
6589      for (const key in previousAttr) {
6590        if (Object.prototype.hasOwnProperty.call(dateRangeAttr, key) && dateRangeAttr[key] !== previousAttr[key]) {
6591          logger.warn(`DATERANGE tag attribute: "${key}" does not match for tags with ID: "${dateRangeAttr.ID}"`);
6592          this._badValueForSameId = key;
6593          break;
6594        }
6595      }
6596      // Merge DateRange tags with the same ID
6597      dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr);
6598    }
6599    this.attr = dateRangeAttr;
6600    if (dateRangeWithSameId) {
6601      this._startDate = dateRangeWithSameId._startDate;
6602      this._cue = dateRangeWithSameId._cue;
6603      this._endDate = dateRangeWithSameId._endDate;
6604      this._dateAtEnd = dateRangeWithSameId._dateAtEnd;
6605    } else {
6606      this._startDate = new Date(dateRangeAttr["START-DATE"]);
6607    }
6608    if ("END-DATE" in this.attr) {
6609      const endDate = (dateRangeWithSameId == null ? void 0 : dateRangeWithSameId.endDate) || new Date(this.attr["END-DATE"]);
6610      if (isFiniteNumber(endDate.getTime())) {
6611        this._endDate = endDate;
6612      }
6613    }
6614  }
6615  get id() {
6616    return this.attr.ID;
6617  }
6618  get class() {
6619    return this.attr.CLASS;
6620  }
6621  get cue() {
6622    const _cue = this._cue;
6623    if (_cue === undefined) {
6624      return this._cue = this.attr.enumeratedStringList(this.attr.CUE ? 'CUE' : 'X-CUE', {
6625        pre: false,
6626        post: false,
6627        once: false
6628      });
6629    }
6630    return _cue;
6631  }
6632  get startTime() {
6633    const {
6634      tagAnchor
6635    } = this;
6636    // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
6637    if (tagAnchor === null || tagAnchor.programDateTime === null) {
6638      logger.warn(`Expected tagAnchor Fragment with PDT set for DateRange "${this.id}": ${tagAnchor}`);
6639      return NaN;
6640    }
6641    return tagAnchor.start + (this.startDate.getTime() - tagAnchor.programDateTime) / 1000;
6642  }
6643  get startDate() {
6644    return this._startDate;
6645  }
6646  get endDate() {
6647    const dateAtEnd = this._endDate || this._dateAtEnd;
6648    if (dateAtEnd) {
6649      return dateAtEnd;
6650    }
6651    const duration = this.duration;
6652    if (duration !== null) {
6653      return this._dateAtEnd = new Date(this._startDate.getTime() + duration * 1000);
6654    }
6655    return null;
6656  }
6657  get duration() {
6658    if ("DURATION" in this.attr) {
6659      const duration = this.attr.decimalFloatingPoint("DURATION");
6660      if (isFiniteNumber(duration)) {
6661        return duration;
6662      }
6663    } else if (this._endDate) {
6664      return (this._endDate.getTime() - this._startDate.getTime()) / 1000;
6665    }
6666    return null;
6667  }
6668  get plannedDuration() {
6669    if ("PLANNED-DURATION" in this.attr) {
6670      return this.attr.decimalFloatingPoint("PLANNED-DURATION");
6671    }
6672    return null;
6673  }
6674  get endOnNext() {
6675    return this.attr.bool("END-ON-NEXT");
6676  }
6677  get isInterstitial() {
6678    return this.class === CLASS_INTERSTITIAL;
6679  }
6680  get isValid() {
6681    return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class) && (!this.attr.CUE || !this.cue.pre && !this.cue.post || this.cue.pre !== this.cue.post) && (!this.isInterstitial || 'X-ASSET-URI' in this.attr || 'X-ASSET-LIST' in this.attr);
6682  }
6683}
6684
6685const DEFAULT_TARGET_DURATION = 10;
6686
6687/**
6688 * Object representing parsed data from an HLS Media Playlist. Found in {@link hls.js#Level.details}.
6689 */
6690class LevelDetails {
6691  constructor(baseUrl) {
6692    this.PTSKnown = false;
6693    this.alignedSliding = false;
6694    this.averagetargetduration = void 0;
6695    this.endCC = 0;
6696    this.endSN = 0;
6697    this.fragments = void 0;
6698    this.fragmentHint = void 0;
6699    this.partList = null;
6700    this.dateRanges = void 0;
6701    this.dateRangeTagCount = 0;
6702    this.live = true;
6703    this.requestScheduled = -1;
6704    this.ageHeader = 0;
6705    this.advancedDateTime = void 0;
6706    this.updated = true;
6707    this.advanced = true;
6708    this.misses = 0;
6709    this.startCC = 0;
6710    this.startSN = 0;
6711    this.startTimeOffset = null;
6712    this.targetduration = 0;
6713    this.totalduration = 0;
6714    this.type = null;
6715    this.url = void 0;
6716    this.m3u8 = '';
6717    this.version = null;
6718    this.canBlockReload = false;
6719    this.canSkipUntil = 0;
6720    this.canSkipDateRanges = false;
6721    this.skippedSegments = 0;
6722    this.recentlyRemovedDateranges = void 0;
6723    this.partHoldBack = 0;
6724    this.holdBack = 0;
6725    this.partTarget = 0;
6726    this.preloadHint = void 0;
6727    this.renditionReports = void 0;
6728    this.tuneInGoal = 0;
6729    this.deltaUpdateFailed = void 0;
6730    this.driftStartTime = 0;
6731    this.driftEndTime = 0;
6732    this.driftStart = 0;
6733    this.driftEnd = 0;
6734    this.encryptedFragments = void 0;
6735    this.playlistParsingError = null;
6736    this.variableList = null;
6737    this.hasVariableRefs = false;
6738    this.appliedTimelineOffset = void 0;
6739    this.fragments = [];
6740    this.encryptedFragments = [];
6741    this.dateRanges = {};
6742    this.url = baseUrl;
6743  }
6744  reloaded(previous) {
6745    if (!previous) {
6746      this.advanced = true;
6747      this.updated = true;
6748      return;
6749    }
6750    const partSnDiff = this.lastPartSn - previous.lastPartSn;
6751    const partIndexDiff = this.lastPartIndex - previous.lastPartIndex;
6752    this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff || !this.live;
6753    this.advanced = this.endSN > previous.endSN || partSnDiff > 0 || partSnDiff === 0 && partIndexDiff > 0;
6754    if (this.updated || this.advanced) {
6755      this.misses = Math.floor(previous.misses * 0.6);
6756    } else {
6757      this.misses = previous.misses + 1;
6758    }
6759  }
6760  hasKey(levelKey) {
6761    return this.encryptedFragments.some(frag => {
6762      let decryptdata = frag.decryptdata;
6763      if (!decryptdata) {
6764        frag.setKeyFormat(levelKey.keyFormat);
6765        decryptdata = frag.decryptdata;
6766      }
6767      return !!decryptdata && levelKey.matches(decryptdata);
6768    });
6769  }
6770  get hasProgramDateTime() {
6771    if (this.fragments.length) {
6772      return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime);
6773    }
6774    return false;
6775  }
6776  get levelTargetDuration() {
6777    return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION;
6778  }
6779  get drift() {
6780    const runTime = this.driftEndTime - this.driftStartTime;
6781    if (runTime > 0) {
6782      const runDuration = this.driftEnd - this.driftStart;
6783      return runDuration * 1000 / runTime;
6784    }
6785    return 1;
6786  }
6787  get edge() {
6788    return this.partEnd || this.fragmentEnd;
6789  }
6790  get partEnd() {
6791    var _this$partList;
6792    if ((_this$partList = this.partList) != null && _this$partList.length) {
6793      return this.partList[this.partList.length - 1].end;
6794    }
6795    return this.fragmentEnd;
6796  }
6797  get fragmentEnd() {
6798    if (this.fragments.length) {
6799      return this.fragments[this.fragments.length - 1].end;
6800    }
6801    return 0;
6802  }
6803  get fragmentStart() {
6804    if (this.fragments.length) {
6805      return this.fragments[0].start;
6806    }
6807    return 0;
6808  }
6809  get age() {
6810    if (this.advancedDateTime) {
6811      return Math.max(Date.now() - this.advancedDateTime, 0) / 1000;
6812    }
6813    return 0;
6814  }
6815  get lastPartIndex() {
6816    var _this$partList2;
6817    if ((_this$partList2 = this.partList) != null && _this$partList2.length) {
6818      return this.partList[this.partList.length - 1].index;
6819    }
6820    return -1;
6821  }
6822  get maxPartIndex() {
6823    const partList = this.partList;
6824    if (partList) {
6825      const lastIndex = this.lastPartIndex;
6826      if (lastIndex !== -1) {
6827        for (let i = partList.length; i--;) {
6828          if (partList[i].index > lastIndex) {
6829            return partList[i].index;
6830          }
6831        }
6832        return lastIndex;
6833      }
6834    }
6835    return 0;
6836  }
6837  get lastPartSn() {
6838    var _this$partList3;
6839    if ((_this$partList3 = this.partList) != null && _this$partList3.length) {
6840      return this.partList[this.partList.length - 1].fragment.sn;
6841    }
6842    return this.endSN;
6843  }
6844  get expired() {
6845    if (this.live && this.age && this.misses < 3) {
6846      const playlistWindowDuration = this.partEnd - this.fragmentStart;
6847      return this.age > Math.max(playlistWindowDuration, this.totalduration) + this.levelTargetDuration;
6848    }
6849    return false;
6850  }
6851}
6852
6853function arrayValuesMatch(a, b) {
6854  if (a.length === b.length) {
6855    return !a.some((value, i) => value !== b[i]);
6856  }
6857  return false;
6858}
6859function optionalArrayValuesMatch(a, b) {
6860  if (!a && !b) {
6861    return true;
6862  }
6863  if (!a || !b) {
6864    return false;
6865  }
6866  return arrayValuesMatch(a, b);
6867}
6868
6869function isFullSegmentEncryption(method) {
6870  return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
6871}
6872function getAesModeFromFullSegmentMethod(method) {
6873  switch (method) {
6874    case 'AES-128':
6875    case 'AES-256':
6876      return DecrypterAesMode.cbc;
6877    case 'AES-256-CTR':
6878      return DecrypterAesMode.ctr;
6879    default:
6880      throw new Error(`invalid full segment method ${method}`);
6881  }
6882}
6883
6884function base64Decode(base64encodedStr) {
6885  return Uint8Array.from(atob(base64encodedStr), c => c.charCodeAt(0));
6886}
6887
6888// breaking up those two types in order to clarify what is happening in the decoding path.
6889
6890// http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197
6891// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
6892/* utf.js - UTF-8 <=> UTF-16 convertion
6893 *
6894 * Copyright (C) 1999 Masanao Izumo <[email protected]>
6895 * Version: 1.0
6896 * LastModified: Dec 25 1999
6897 * This library is free.  You can redistribute it and/or modify it.
6898 */
6899
6900function strToUtf8array(str) {
6901  return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
6902}
6903
6904function getKeyIdBytes(str) {
6905  const keyIdbytes = strToUtf8array(str).subarray(0, 16);
6906  const paddedkeyIdbytes = new Uint8Array(16);
6907  paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length);
6908  return paddedkeyIdbytes;
6909}
6910function changeEndianness(keyId) {
6911  const swap = function swap(array, from, to) {
6912    const cur = array[from];
6913    array[from] = array[to];
6914    array[to] = cur;
6915  };
6916  swap(keyId, 0, 3);
6917  swap(keyId, 1, 2);
6918  swap(keyId, 4, 5);
6919  swap(keyId, 6, 7);
6920}
6921function convertDataUriToArrayBytes(uri) {
6922  // data:[<media type][;attribute=value][;base64],<data>
6923  const colonsplit = uri.split(':');
6924  let keydata = null;
6925  if (colonsplit[0] === 'data' && colonsplit.length === 2) {
6926    const semicolonsplit = colonsplit[1].split(';');
6927    const commasplit = semicolonsplit[semicolonsplit.length - 1].split(',');
6928    if (commasplit.length === 2) {
6929      const isbase64 = commasplit[0] === 'base64';
6930      const data = commasplit[1];
6931      if (isbase64) {
6932        semicolonsplit.splice(-1, 1); // remove from processing
6933        keydata = base64Decode(data);
6934      } else {
6935        keydata = getKeyIdBytes(data);
6936      }
6937    }
6938  }
6939  return keydata;
6940}
6941
6942/** returns `undefined` is `self` is missing, e.g. in node */
6943const optionalSelf = typeof self !== 'undefined' ? self : undefined;
6944
6945/**
6946 * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess
6947 */
6948var KeySystems = {
6949  CLEARKEY: "org.w3.clearkey",
6950  FAIRPLAY: "com.apple.fps",
6951  PLAYREADY: "com.microsoft.playready",
6952  WIDEVINE: "com.widevine.alpha"
6953};
6954
6955// Playlist #EXT-X-KEY KEYFORMAT values
6956var KeySystemFormats = {
6957  CLEARKEY: "org.w3.clearkey",
6958  FAIRPLAY: "com.apple.streamingkeydelivery",
6959  PLAYREADY: "com.microsoft.playready",
6960  WIDEVINE: "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"
6961};
6962function keySystemFormatToKeySystemDomain(format) {
6963  switch (format) {
6964    case KeySystemFormats.FAIRPLAY:
6965      return KeySystems.FAIRPLAY;
6966    case KeySystemFormats.PLAYREADY:
6967      return KeySystems.PLAYREADY;
6968    case KeySystemFormats.WIDEVINE:
6969      return KeySystems.WIDEVINE;
6970    case KeySystemFormats.CLEARKEY:
6971      return KeySystems.CLEARKEY;
6972  }
6973}
6974function keySystemDomainToKeySystemFormat(keySystem) {
6975  switch (keySystem) {
6976    case KeySystems.FAIRPLAY:
6977      return KeySystemFormats.FAIRPLAY;
6978    case KeySystems.PLAYREADY:
6979      return KeySystemFormats.PLAYREADY;
6980    case KeySystems.WIDEVINE:
6981      return KeySystemFormats.WIDEVINE;
6982    case KeySystems.CLEARKEY:
6983      return KeySystemFormats.CLEARKEY;
6984  }
6985}
6986function getKeySystemsForConfig(config) {
6987  const {
6988    drmSystems,
6989    widevineLicenseUrl
6990  } = config;
6991  const keySystemsToAttempt = drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter(keySystem => !!drmSystems[keySystem]) : [];
6992  if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) {
6993    keySystemsToAttempt.push(KeySystems.WIDEVINE);
6994  }
6995  return keySystemsToAttempt;
6996}
6997const requestMediaKeySystemAccess = function (_optionalSelf$navigat) {
6998  if (optionalSelf != null && (_optionalSelf$navigat = optionalSelf.navigator) != null && _optionalSelf$navigat.requestMediaKeySystemAccess) {
6999    return self.navigator.requestMediaKeySystemAccess.bind(self.navigator);
7000  } else {
7001    return null;
7002  }
7003}();
7004
7005/**
7006 * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaKeySystemConfiguration
7007 */
7008function getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, drmSystemOptions) {
7009  let initDataTypes;
7010  switch (keySystem) {
7011    case KeySystems.FAIRPLAY:
7012      initDataTypes = ['cenc', 'sinf'];
7013      break;
7014    case KeySystems.WIDEVINE:
7015    case KeySystems.PLAYREADY:
7016      initDataTypes = ['cenc'];
7017      break;
7018    case KeySystems.CLEARKEY:
7019      initDataTypes = ['cenc', 'keyids'];
7020      break;
7021    default:
7022      throw new Error(`Unknown key-system: ${keySystem}`);
7023  }
7024  return createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions);
7025}
7026function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions) {
7027  const baseConfig = {
7028    initDataTypes: initDataTypes,
7029    persistentState: drmSystemOptions.persistentState || 'optional',
7030    distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'optional',
7031    sessionTypes: drmSystemOptions.sessionTypes || [drmSystemOptions.sessionType || 'temporary'],
7032    audioCapabilities: audioCodecs.map(codec => ({
7033      contentType: `audio/mp4; codecs=${codec}`,
7034      robustness: drmSystemOptions.audioRobustness || '',
7035      encryptionScheme: drmSystemOptions.audioEncryptionScheme || null
7036    })),
7037    videoCapabilities: videoCodecs.map(codec => ({
7038      contentType: `video/mp4; codecs=${codec}`,
7039      robustness: drmSystemOptions.videoRobustness || '',
7040      encryptionScheme: drmSystemOptions.videoEncryptionScheme || null
7041    }))
7042  };
7043  return [baseConfig];
7044}
7045function isPersistentSessionType(drmSystemOptions) {
7046  var _drmSystemOptions$ses;
7047  return !!drmSystemOptions && (drmSystemOptions.sessionType === 'persistent-license' || !!((_drmSystemOptions$ses = drmSystemOptions.sessionTypes) != null && _drmSystemOptions$ses.some(type => type === 'persistent-license')));
7048}
7049function parsePlayReadyWRM(keyBytes) {
7050  const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
7051  const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
7052
7053  // Parse Playready WRMHeader XML
7054  const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
7055  const parser = new DOMParser();
7056  const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
7057  const keyData = xmlDoc.getElementsByTagName('KID')[0];
7058  if (keyData) {
7059    const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
7060    if (keyId) {
7061      const keyIdArray = base64Decode(keyId).subarray(0, 16);
7062      // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
7063      // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
7064      changeEndianness(keyIdArray);
7065      return keyIdArray;
7066    }
7067  }
7068  return null;
7069}
7070
7071let keyUriToKeyIdMap = {};
7072class LevelKey {
7073  static clearKeyUriToKeyIdMap() {
7074    keyUriToKeyIdMap = {};
7075  }
7076  static setKeyIdForUri(uri, keyId) {
7077    keyUriToKeyIdMap[uri] = keyId;
7078  }
7079  static addKeyIdForUri(uri) {
7080    const val = Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER;
7081    const keyId = new Uint8Array(16);
7082    const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes
7083    dv.setUint32(0, val);
7084    keyUriToKeyIdMap[uri] = keyId;
7085    return keyId;
7086  }
7087  constructor(method, uri, format, formatversions = [1], iv = null, keyId) {
7088    this.uri = void 0;
7089    this.method = void 0;
7090    this.keyFormat = void 0;
7091    this.keyFormatVersions = void 0;
7092    this.encrypted = void 0;
7093    this.isCommonEncryption = void 0;
7094    this.iv = null;
7095    this.key = null;
7096    this.keyId = null;
7097    this.pssh = null;
7098    this.method = method;
7099    this.uri = uri;
7100    this.keyFormat = format;
7101    this.keyFormatVersions = formatversions;
7102    this.iv = iv;
7103    this.encrypted = method ? method !== 'NONE' : false;
7104    this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
7105    if (keyId != null && keyId.startsWith('0x')) {
7106      this.keyId = new Uint8Array(hexToArrayBuffer(keyId));
7107    }
7108  }
7109  matches(key) {
7110    return key.uri === this.uri && key.method === this.method && key.encrypted === this.encrypted && key.keyFormat === this.keyFormat && arrayValuesMatch(key.keyFormatVersions, this.keyFormatVersions) && optionalArrayValuesMatch(key.iv, this.iv) && optionalArrayValuesMatch(key.keyId, this.keyId);
7111  }
7112  isSupported() {
7113    // If it's Segment encryption or No encryption, just select that key system
7114    if (this.method) {
7115      if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
7116        return true;
7117      }
7118      if (this.keyFormat === 'identity') {
7119        // Maintain support for clear SAMPLE-AES with MPEG-3 TS
7120        return this.method === 'SAMPLE-AES';
7121      } else {
7122        switch (this.keyFormat) {
7123          case KeySystemFormats.FAIRPLAY:
7124          case KeySystemFormats.WIDEVINE:
7125          case KeySystemFormats.PLAYREADY:
7126          case KeySystemFormats.CLEARKEY:
7127            return ['SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method) !== -1;
7128        }
7129      }
7130    }
7131    return false;
7132  }
7133  getDecryptData(sn, levelKeys) {
7134    if (!this.encrypted || !this.uri) {
7135      return null;
7136    }
7137    if (isFullSegmentEncryption(this.method)) {
7138      let iv = this.iv;
7139      if (!iv) {
7140        if (typeof sn !== 'number') {
7141          // We are fetching decryption data for a initialization segment
7142          // If the segment was encrypted with AES-128/256
7143          // It must have an IV defined. We cannot substitute the Segment Number in.
7144          logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
7145
7146          // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
7147          sn = 0;
7148        }
7149        iv = createInitializationVector(sn);
7150      }
7151      const decryptdata = new LevelKey(this.method, this.uri, 'identity', this.keyFormatVersions, iv);
7152      return decryptdata;
7153    }
7154    if (this.keyId) {
7155      // Handle case where key id is changed in KEY_LOADING event handler #7542#issuecomment-3305203929
7156      const assignedKeyId = keyUriToKeyIdMap[this.uri];
7157      if (assignedKeyId && !arrayValuesMatch(this.keyId, assignedKeyId)) {
7158        LevelKey.setKeyIdForUri(this.uri, this.keyId);
7159      }
7160      if (this.pssh) {
7161        return this;
7162      }
7163    }
7164
7165    // Key bytes are signalled the KEYID attribute, typically only found on WideVine KEY tags
7166    // Initialize keyId if possible
7167    const keyBytes = convertDataUriToArrayBytes(this.uri);
7168    if (keyBytes) {
7169      switch (this.keyFormat) {
7170        case KeySystemFormats.WIDEVINE:
7171          // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
7172          // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
7173          this.pssh = keyBytes;
7174          // In case of Widevine, if KEYID is not in the playlist, assume only two fields in the pssh KEY tag URI.
7175          if (!this.keyId) {
7176            const results = parseMultiPssh(keyBytes.buffer);
7177            if (results.length) {
7178              var _psshData$kids;
7179              const psshData = results[0];
7180              this.keyId = (_psshData$kids = psshData.kids) != null && _psshData$kids.length ? psshData.kids[0] : null;
7181            }
7182          }
7183          if (!this.keyId) {
7184            this.keyId = getKeyIdFromPlayReadyKey(levelKeys);
7185          }
7186          break;
7187        case KeySystemFormats.PLAYREADY:
7188          {
7189            const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
7190
7191            // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
7192            // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
7193            this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
7194            this.keyId = parsePlayReadyWRM(keyBytes);
7195            break;
7196          }
7197        default:
7198          {
7199            let keydata = keyBytes.subarray(0, 16);
7200            if (keydata.length !== 16) {
7201              const padded = new Uint8Array(16);
7202              padded.set(keydata, 16 - keydata.length);
7203              keydata = padded;
7204            }
7205            this.keyId = keydata;
7206            break;
7207          }
7208      }
7209    }
7210
7211    // Default behavior: get keyId from other KEY tag or URI lookup
7212    if (!this.keyId || this.keyId.byteLength !== 16) {
7213      let keyId;
7214      keyId = getKeyIdFromWidevineKey(levelKeys);
7215      if (!keyId) {
7216        keyId = getKeyIdFromPlayReadyKey(levelKeys);
7217        if (!keyId) {
7218          keyId = keyUriToKeyIdMap[this.uri];
7219        }
7220      }
7221      if (keyId) {
7222        this.keyId = keyId;
7223        LevelKey.setKeyIdForUri(this.uri, keyId);
7224      }
7225    }
7226    return this;
7227  }
7228}
7229function getKeyIdFromWidevineKey(levelKeys) {
7230  const widevineKey = levelKeys == null ? void 0 : levelKeys[KeySystemFormats.WIDEVINE];
7231  if (widevineKey) {
7232    return widevineKey.keyId;
7233  }
7234  return null;
7235}
7236function getKeyIdFromPlayReadyKey(levelKeys) {
7237  const playReadyKey = levelKeys == null ? void 0 : levelKeys[KeySystemFormats.PLAYREADY];
7238  if (playReadyKey) {
7239    const playReadyKeyBytes = convertDataUriToArrayBytes(playReadyKey.uri);
7240    if (playReadyKeyBytes) {
7241      return parsePlayReadyWRM(playReadyKeyBytes);
7242    }
7243  }
7244  return null;
7245}
7246function createInitializationVector(segmentNumber) {
7247  const uint8View = new Uint8Array(16);
7248  for (let i = 12; i < 16; i++) {
7249    uint8View[i] = segmentNumber >> 8 * (15 - i) & 0xff;
7250  }
7251  return uint8View;
7252}
7253
7254const 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;
7255const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
7256const IS_MEDIA_PLAYLIST = /^#EXT(?:INF|-X-TARGETDURATION):/m; // Handle empty Media Playlist (first EXTINF not signaled, but TARGETDURATION present)
7257
7258const LEVEL_PLAYLIST_REGEX_FAST = new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source,
7259// duration (#EXTINF:<duration>,<title>), group 1 => duration, group 2 => title
7260/(?!#) *(\S[^\r\n]*)/.source,
7261// segment URI, group 3 => the URI (note newline is not eaten)
7262/#.*/.source // All other non-segment oriented tags will match with all groups empty
7263].join('|'), 'g');
7264const LEVEL_PLAYLIST_REGEX_SLOW = new RegExp([/#EXT-X-(PROGRAM-DATE-TIME|BYTERANGE|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('|'));
7265class M3U8Parser {
7266  static findGroup(groups, mediaGroupId) {
7267    for (let i = 0; i < groups.length; i++) {
7268      const group = groups[i];
7269      if (group.id === mediaGroupId) {
7270        return group;
7271      }
7272    }
7273  }
7274  static resolve(url, baseUrl) {
7275    return urlToolkitExports.buildAbsoluteURL(baseUrl, url, {
7276      alwaysNormalize: true
7277    });
7278  }
7279  static isMediaPlaylist(str) {
7280    return IS_MEDIA_PLAYLIST.test(str);
7281  }
7282  static parseMasterPlaylist(string, baseurl) {
7283    const hasVariableRefs = hasVariableReferences(string) ;
7284    const parsed = {
7285      contentSteering: null,
7286      levels: [],
7287      playlistParsingError: null,
7288      sessionData: null,
7289      sessionKeys: null,
7290      startTimeOffset: null,
7291      variableList: null,
7292      hasVariableRefs
7293    };
7294    const levelsWithKnownCodecs = [];
7295    MASTER_PLAYLIST_REGEX.lastIndex = 0;
7296    if (!string.startsWith('#EXTM3U')) {
7297      parsed.playlistParsingError = new Error('no EXTM3U delimiter');
7298      return parsed;
7299    }
7300    let result;
7301    while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) {
7302      if (result[1]) {
7303        var _level$unknownCodecs;
7304        // '#EXT-X-STREAM-INF' is found, parse level tag  in group 1
7305        const attrs = new AttrList(result[1], parsed);
7306        const uri = substituteVariables(parsed, result[2]) ;
7307        const level = {
7308          attrs,
7309          bitrate: attrs.decimalInteger('BANDWIDTH') || attrs.decimalInteger('AVERAGE-BANDWIDTH'),
7310          name: attrs.NAME,
7311          url: M3U8Parser.resolve(uri, baseurl)
7312        };
7313        const resolution = attrs.decimalResolution('RESOLUTION');
7314        if (resolution) {
7315          level.width = resolution.width;
7316          level.height = resolution.height;
7317        }
7318        setCodecs(attrs.CODECS, level);
7319        const supplementalCodecs = attrs['SUPPLEMENTAL-CODECS'];
7320        if (supplementalCodecs) {
7321          level.supplemental = {};
7322          setCodecs(supplementalCodecs, level.supplemental);
7323        }
7324        if (!((_level$unknownCodecs = level.unknownCodecs) != null && _level$unknownCodecs.length)) {
7325          levelsWithKnownCodecs.push(level);
7326        }
7327        parsed.levels.push(level);
7328      } else if (result[3]) {
7329        const tag = result[3];
7330        const attributes = result[4];
7331        switch (tag) {
7332          case 'SESSION-DATA':
7333            {
7334              // #EXT-X-SESSION-DATA
7335              const sessionAttrs = new AttrList(attributes, parsed);
7336              const dataId = sessionAttrs['DATA-ID'];
7337              if (dataId) {
7338                if (parsed.sessionData === null) {
7339                  parsed.sessionData = {};
7340                }
7341                parsed.sessionData[dataId] = sessionAttrs;
7342              }
7343              break;
7344            }
7345          case 'SESSION-KEY':
7346            {
7347              // #EXT-X-SESSION-KEY
7348              const sessionKey = parseKey(attributes, baseurl, parsed);
7349              if (sessionKey.encrypted && sessionKey.isSupported()) {
7350                if (parsed.sessionKeys === null) {
7351                  parsed.sessionKeys = [];
7352                }
7353                parsed.sessionKeys.push(sessionKey);
7354              } else {
7355                logger.warn(`[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "${attributes}"`);
7356              }
7357              break;
7358            }
7359          case 'DEFINE':
7360            {
7361              // #EXT-X-DEFINE
7362              {
7363                const variableAttributes = new AttrList(attributes, parsed);
7364                addVariableDefinition(parsed, variableAttributes, baseurl);
7365              }
7366              break;
7367            }
7368          case 'CONTENT-STEERING':
7369            {
7370              // #EXT-X-CONTENT-STEERING
7371              const contentSteeringAttributes = new AttrList(attributes, parsed);
7372              parsed.contentSteering = {
7373                uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl),
7374                pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.'
7375              };
7376              break;
7377            }
7378          case 'START':
7379            {
7380              // #EXT-X-START
7381              parsed.startTimeOffset = parseStartTimeOffset(attributes);
7382              break;
7383            }
7384        }
7385      }
7386    }
7387    // Filter out levels with unknown codecs if it does not remove all levels
7388    const stripUnknownCodecLevels = levelsWithKnownCodecs.length > 0 && levelsWithKnownCodecs.length < parsed.levels.length;
7389    parsed.levels = stripUnknownCodecLevels ? levelsWithKnownCodecs : parsed.levels;
7390    if (parsed.levels.length === 0) {
7391      parsed.playlistParsingError = new Error('no levels found in manifest');
7392    }
7393    return parsed;
7394  }
7395  static parseMasterPlaylistMedia(string, baseurl, parsed) {
7396    let result;
7397    const results = {};
7398    const levels = parsed.levels;
7399    const groupsByType = {
7400      AUDIO: levels.map(level => ({
7401        id: level.attrs.AUDIO,
7402        audioCodec: level.audioCodec
7403      })),
7404      SUBTITLES: levels.map(level => ({
7405        id: level.attrs.SUBTITLES,
7406        textCodec: level.textCodec
7407      })),
7408      'CLOSED-CAPTIONS': []
7409    };
7410    let id = 0;
7411    MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0;
7412    while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) {
7413      const attrs = new AttrList(result[1], parsed);
7414      const type = attrs.TYPE;
7415      if (type) {
7416        const groups = groupsByType[type];
7417        const medias = results[type] || [];
7418        results[type] = medias;
7419        const lang = attrs.LANGUAGE;
7420        const assocLang = attrs['ASSOC-LANGUAGE'];
7421        const channels = attrs.CHANNELS;
7422        const characteristics = attrs.CHARACTERISTICS;
7423        const instreamId = attrs['INSTREAM-ID'];
7424        const media = {
7425          attrs,
7426          bitrate: 0,
7427          id: id++,
7428          groupId: attrs['GROUP-ID'] || '',
7429          name: attrs.NAME || lang || '',
7430          type,
7431          default: attrs.bool('DEFAULT'),
7432          autoselect: attrs.bool('AUTOSELECT'),
7433          forced: attrs.bool('FORCED'),
7434          lang,
7435          url: attrs.URI ? M3U8Parser.resolve(attrs.URI, baseurl) : ''
7436        };
7437        if (assocLang) {
7438          media.assocLang = assocLang;
7439        }
7440        if (channels) {
7441          media.channels = channels;
7442        }
7443        if (characteristics) {
7444          media.characteristics = characteristics;
7445        }
7446        if (instreamId) {
7447          media.instreamId = instreamId;
7448        }
7449        if (groups != null && groups.length) {
7450          // If there are audio or text groups signalled in the manifest, let's look for a matching codec string for this track
7451          // If we don't find the track signalled, lets use the first audio groups codec we have
7452          // Acting as a best guess
7453          const groupCodec = M3U8Parser.findGroup(groups, media.groupId) || groups[0];
7454          assignCodec(media, groupCodec, 'audioCodec');
7455          assignCodec(media, groupCodec, 'textCodec');
7456        }
7457        medias.push(media);
7458      }
7459    }
7460    return results;
7461  }
7462  static parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) {
7463    var _LEVEL_PLAYLIST_REGEX;
7464    const base = {
7465      url: baseurl
7466    };
7467    const level = new LevelDetails(baseurl);
7468    const fragments = level.fragments;
7469    const programDateTimes = [];
7470    // The most recent init segment seen (applies to all subsequent segments)
7471    let currentInitSegment = null;
7472    let currentSN = 0;
7473    let currentPart = 0;
7474    let totalduration = 0;
7475    let discontinuityCounter = 0;
7476    let currentBitrate = 0;
7477    let prevFrag = null;
7478    let frag = new Fragment(type, base);
7479    let result;
7480    let i;
7481    let levelkeys;
7482    let firstPdtIndex = -1;
7483    let createNextFrag = false;
7484    let nextByteRange = null;
7485    let serverControlAttrs;
7486    LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0;
7487    level.m3u8 = string;
7488    level.hasVariableRefs = hasVariableReferences(string) ;
7489    if (((_LEVEL_PLAYLIST_REGEX = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) == null ? void 0 : _LEVEL_PLAYLIST_REGEX[0]) !== '#EXTM3U') {
7490      level.playlistParsingError = new Error('Missing format identifier #EXTM3U');
7491      return level;
7492    }
7493    while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) {
7494      if (createNextFrag) {
7495        createNextFrag = false;
7496        frag = new Fragment(type, base);
7497        // setup the next fragment for part loading
7498        frag.playlistOffset = totalduration;
7499        frag.setStart(totalduration);
7500        frag.sn = currentSN;
7501        frag.cc = discontinuityCounter;
7502        if (currentBitrate) {
7503          frag.bitrate = currentBitrate;
7504        }
7505        frag.level = id;
7506        if (currentInitSegment) {
7507          frag.initSegment = currentInitSegment;
7508          if (currentInitSegment.rawProgramDateTime) {
7509            frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
7510            currentInitSegment.rawProgramDateTime = null;
7511          }
7512          if (nextByteRange) {
7513            frag.setByteRange(nextByteRange);
7514            nextByteRange = null;
7515          }
7516        }
7517      }
7518      const duration = result[1];
7519      if (duration) {
7520        // INF
7521        frag.duration = parseFloat(duration);
7522        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
7523        const title = (' ' + result[2]).slice(1);
7524        frag.title = title || null;
7525        frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]);
7526      } else if (result[3]) {
7527        // url
7528        if (isFiniteNumber(frag.duration)) {
7529          frag.playlistOffset = totalduration;
7530          frag.setStart(totalduration);
7531          if (levelkeys) {
7532            setFragLevelKeys(frag, levelkeys, level);
7533          }
7534          frag.sn = currentSN;
7535          frag.level = id;
7536          frag.cc = discontinuityCounter;
7537          fragments.push(frag);
7538          // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
7539          const uri = (' ' + result[3]).slice(1);
7540          frag.relurl = substituteVariables(level, uri) ;
7541          assignProgramDateTime(frag, prevFrag, programDateTimes);
7542          prevFrag = frag;
7543          totalduration += frag.duration;
7544          currentSN++;
7545          currentPart = 0;
7546          createNextFrag = true;
7547        }
7548      } else {
7549        result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW);
7550        if (!result) {
7551          logger.warn('No matches on slow regex match for level playlist!');
7552          continue;
7553        }
7554        for (i = 1; i < result.length; i++) {
7555          if (result[i] !== undefined) {
7556            break;
7557          }
7558        }
7559
7560        // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
7561        const tag = (' ' + result[i]).slice(1);
7562        const value1 = (' ' + result[i + 1]).slice(1);
7563        const value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : null;
7564        switch (tag) {
7565          case 'BYTERANGE':
7566            if (prevFrag) {
7567              frag.setByteRange(value1, prevFrag);
7568            } else {
7569              frag.setByteRange(value1);
7570            }
7571            break;
7572          case 'PROGRAM-DATE-TIME':
7573            // avoid sliced strings    https://github.com/video-dev/hls.js/issues/939
7574            frag.rawProgramDateTime = value1;
7575            frag.tagList.push(['PROGRAM-DATE-TIME', value1]);
7576            if (firstPdtIndex === -1) {
7577              firstPdtIndex = fragments.length;
7578            }
7579            break;
7580          case 'PLAYLIST-TYPE':
7581            if (level.type) {
7582              assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7583            }
7584            level.type = value1.toUpperCase();
7585            break;
7586          case 'MEDIA-SEQUENCE':
7587            if (level.startSN !== 0) {
7588              assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7589            } else if (fragments.length > 0) {
7590              assignMustAppearBeforeSegmentsError(level, tag, result);
7591            }
7592            currentSN = level.startSN = parseInt(value1);
7593            break;
7594          case 'SKIP':
7595            {
7596              if (level.skippedSegments) {
7597                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7598              }
7599              const skipAttrs = new AttrList(value1, level);
7600              const skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS');
7601              if (isFiniteNumber(skippedSegments)) {
7602                level.skippedSegments += skippedSegments;
7603                // This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails`
7604                for (let _i = skippedSegments; _i--;) {
7605                  fragments.push(null);
7606                }
7607                currentSN += skippedSegments;
7608              }
7609              const recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES');
7610              if (recentlyRemovedDateranges) {
7611                level.recentlyRemovedDateranges = (level.recentlyRemovedDateranges || []).concat(recentlyRemovedDateranges.split('\t'));
7612              }
7613              break;
7614            }
7615          case 'TARGETDURATION':
7616            if (level.targetduration !== 0) {
7617              assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7618            }
7619            level.targetduration = Math.max(parseInt(value1), 1);
7620            break;
7621          case 'VERSION':
7622            if (level.version !== null) {
7623              assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7624            }
7625            level.version = parseInt(value1);
7626            break;
7627          case 'INDEPENDENT-SEGMENTS':
7628            break;
7629          case 'ENDLIST':
7630            if (!level.live) {
7631              assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7632            }
7633            level.live = false;
7634            break;
7635          case '#':
7636            if (value1 || value2) {
7637              frag.tagList.push(value2 ? [value1, value2] : [value1]);
7638            }
7639            break;
7640          case 'DISCONTINUITY':
7641            discontinuityCounter++;
7642            frag.tagList.push(['DIS']);
7643            break;
7644          case 'GAP':
7645            frag.gap = true;
7646            frag.tagList.push([tag]);
7647            break;
7648          case 'BITRATE':
7649            frag.tagList.push([tag, value1]);
7650            currentBitrate = parseInt(value1) * 1000;
7651            if (isFiniteNumber(currentBitrate)) {
7652              frag.bitrate = currentBitrate;
7653            } else {
7654              currentBitrate = 0;
7655            }
7656            break;
7657          case 'DATERANGE':
7658            {
7659              const dateRangeAttr = new AttrList(value1, level);
7660              const dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID], level.dateRangeTagCount);
7661              level.dateRangeTagCount++;
7662              if (dateRange.isValid || level.skippedSegments) {
7663                level.dateRanges[dateRange.id] = dateRange;
7664              } else {
7665                logger.warn(`Ignoring invalid DATERANGE tag: "${value1}"`);
7666              }
7667              // Add to fragment tag list for backwards compatibility (< v1.2.0)
7668              frag.tagList.push(['EXT-X-DATERANGE', value1]);
7669              break;
7670            }
7671          case 'DEFINE':
7672            {
7673              {
7674                const variableAttributes = new AttrList(value1, level);
7675                if ('IMPORT' in variableAttributes) {
7676                  importVariableDefinition(level, variableAttributes, multivariantVariableList);
7677                } else {
7678                  addVariableDefinition(level, variableAttributes, baseurl);
7679                }
7680              }
7681              break;
7682            }
7683          case 'DISCONTINUITY-SEQUENCE':
7684            if (level.startCC !== 0) {
7685              assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7686            } else if (fragments.length > 0) {
7687              assignMustAppearBeforeSegmentsError(level, tag, result);
7688            }
7689            level.startCC = discontinuityCounter = parseInt(value1);
7690            break;
7691          case 'KEY':
7692            {
7693              const levelKey = parseKey(value1, baseurl, level);
7694              if (levelKey.isSupported()) {
7695                if (levelKey.method === 'NONE') {
7696                  levelkeys = undefined;
7697                  break;
7698                }
7699                if (!levelkeys) {
7700                  levelkeys = {};
7701                }
7702                const currentKey = levelkeys[levelKey.keyFormat];
7703                // Ignore duplicate playlist KEY tags
7704                if (!(currentKey != null && currentKey.matches(levelKey))) {
7705                  if (currentKey) {
7706                    levelkeys = _extends({}, levelkeys);
7707                  }
7708                  levelkeys[levelKey.keyFormat] = levelKey;
7709                }
7710              } else {
7711                logger.warn(`[Keys] Ignoring unsupported EXT-X-KEY tag: "${value1}"${'' }`);
7712              }
7713              break;
7714            }
7715          case 'START':
7716            level.startTimeOffset = parseStartTimeOffset(value1);
7717            break;
7718          case 'MAP':
7719            {
7720              const mapAttrs = new AttrList(value1, level);
7721              if (frag.duration) {
7722                // Initial segment tag is after segment duration tag.
7723                //   #EXTINF: 6.0
7724                //   #EXT-X-MAP:URI="init.mp4
7725                const init = new Fragment(type, base);
7726                setInitSegment(init, mapAttrs, id, levelkeys);
7727                currentInitSegment = init;
7728                frag.initSegment = currentInitSegment;
7729                if (currentInitSegment.rawProgramDateTime && !frag.rawProgramDateTime) {
7730                  frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime;
7731                }
7732              } else {
7733                // Initial segment tag is before segment duration tag
7734                // Handle case where EXT-X-MAP is declared after EXT-X-BYTERANGE
7735                const end = frag.byteRangeEndOffset;
7736                if (end) {
7737                  const start = frag.byteRangeStartOffset;
7738                  nextByteRange = `${end - start}@${start}`;
7739                } else {
7740                  nextByteRange = null;
7741                }
7742                setInitSegment(frag, mapAttrs, id, levelkeys);
7743                currentInitSegment = frag;
7744                createNextFrag = true;
7745              }
7746              currentInitSegment.cc = discontinuityCounter;
7747              break;
7748            }
7749          case 'SERVER-CONTROL':
7750            {
7751              if (serverControlAttrs) {
7752                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7753              }
7754              serverControlAttrs = new AttrList(value1);
7755              level.canBlockReload = serverControlAttrs.bool('CAN-BLOCK-RELOAD');
7756              level.canSkipUntil = serverControlAttrs.optionalFloat('CAN-SKIP-UNTIL', 0);
7757              level.canSkipDateRanges = level.canSkipUntil > 0 && serverControlAttrs.bool('CAN-SKIP-DATERANGES');
7758              level.partHoldBack = serverControlAttrs.optionalFloat('PART-HOLD-BACK', 0);
7759              level.holdBack = serverControlAttrs.optionalFloat('HOLD-BACK', 0);
7760              break;
7761            }
7762          case 'PART-INF':
7763            {
7764              if (level.partTarget) {
7765                assignMultipleMediaPlaylistTagOccuranceError(level, tag, result);
7766              }
7767              const partInfAttrs = new AttrList(value1);
7768              level.partTarget = partInfAttrs.decimalFloatingPoint('PART-TARGET');
7769              break;
7770            }
7771          case 'PART':
7772            {
7773              let partList = level.partList;
7774              if (!partList) {
7775                partList = level.partList = [];
7776              }
7777              const previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : undefined;
7778              const index = currentPart++;
7779              const partAttrs = new AttrList(value1, level);
7780              const part = new Part(partAttrs, frag, base, index, previousFragmentPart);
7781              partList.push(part);
7782              frag.duration += part.duration;
7783              break;
7784            }
7785          case 'PRELOAD-HINT':
7786            {
7787              const preloadHintAttrs = new AttrList(value1, level);
7788              level.preloadHint = preloadHintAttrs;
7789              break;
7790            }
7791          case 'RENDITION-REPORT':
7792            {
7793              const renditionReportAttrs = new AttrList(value1, level);
7794              level.renditionReports = level.renditionReports || [];
7795              level.renditionReports.push(renditionReportAttrs);
7796              break;
7797            }
7798          default:
7799            logger.warn(`line parsed but not handled: ${result}`);
7800            break;
7801        }
7802      }
7803    }
7804    if (prevFrag && !prevFrag.relurl) {
7805      fragments.pop();
7806      totalduration -= prevFrag.duration;
7807      if (level.partList) {
7808        level.fragmentHint = prevFrag;
7809      }
7810    } else if (level.partList) {
7811      assignProgramDateTime(frag, prevFrag, programDateTimes);
7812      frag.cc = discontinuityCounter;
7813      level.fragmentHint = frag;
7814      if (levelkeys) {
7815        setFragLevelKeys(frag, levelkeys, level);
7816      }
7817    }
7818    if (!level.targetduration) {
7819      level.playlistParsingError = new Error(`Missing Target Duration`);
7820    }
7821    const fragmentLength = fragments.length;
7822    const firstFragment = fragments[0];
7823    const lastFragment = fragments[fragmentLength - 1];
7824    totalduration += level.skippedSegments * level.targetduration;
7825    if (totalduration > 0 && fragmentLength && lastFragment) {
7826      level.averagetargetduration = totalduration / fragmentLength;
7827      const lastSn = lastFragment.sn;
7828      level.endSN = lastSn !== 'initSegment' ? lastSn : 0;
7829      if (!level.live) {
7830        lastFragment.endList = true;
7831      }
7832      /**
7833       * Backfill any missing PDT values
7834       * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
7835       * one or more Media Segment URIs, the client SHOULD extrapolate
7836       * backward from that tag (using EXTINF durations and/or media
7837       * timestamps) to associate dates with those segments."
7838       * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs
7839       * computed.
7840       */
7841      if (firstPdtIndex > 0) {
7842        backfillProgramDateTimes(fragments, firstPdtIndex);
7843        if (firstFragment) {
7844          programDateTimes.unshift(firstFragment);
7845        }
7846      }
7847    }
7848    if (level.fragmentHint) {
7849      totalduration += level.fragmentHint.duration;
7850    }
7851    level.totalduration = totalduration;
7852    if (programDateTimes.length && level.dateRangeTagCount && firstFragment) {
7853      mapDateRanges(programDateTimes, level);
7854    }
7855    level.endCC = discontinuityCounter;
7856    return level;
7857  }
7858}
7859function mapDateRanges(programDateTimes, details) {
7860  // Make sure DateRanges are mapped to a ProgramDateTime tag that applies a date to a segment that overlaps with its start date
7861  let programDateTimeCount = programDateTimes.length;
7862  if (!programDateTimeCount) {
7863    if (details.hasProgramDateTime) {
7864      const lastFragment = details.fragments[details.fragments.length - 1];
7865      programDateTimes.push(lastFragment);
7866      programDateTimeCount++;
7867    } else {
7868      // no segments with EXT-X-PROGRAM-DATE-TIME references in playlist history
7869      return;
7870    }
7871  }
7872  const lastProgramDateTime = programDateTimes[programDateTimeCount - 1];
7873  const playlistEnd = details.live ? Infinity : details.totalduration;
7874  const dateRangeIds = Object.keys(details.dateRanges);
7875  for (let i = dateRangeIds.length; i--;) {
7876    const dateRange = details.dateRanges[dateRangeIds[i]];
7877    const startDateTime = dateRange.startDate.getTime();
7878    dateRange.tagAnchor = lastProgramDateTime.ref;
7879    for (let j = programDateTimeCount; j--;) {
7880      var _programDateTimes$j;
7881      if (((_programDateTimes$j = programDateTimes[j]) == null ? void 0 : _programDateTimes$j.sn) < details.startSN) {
7882        break;
7883      }
7884      const fragIndex = findFragmentWithStartDate(details, startDateTime, programDateTimes, j, playlistEnd);
7885      if (fragIndex !== -1) {
7886        dateRange.tagAnchor = details.fragments[fragIndex].ref;
7887        break;
7888      }
7889    }
7890  }
7891}
7892function findFragmentWithStartDate(details, startDateTime, programDateTimes, index, endTime) {
7893  const pdtFragment = programDateTimes[index];
7894  if (pdtFragment) {
7895    // find matching range between PDT tags
7896    const pdtStart = pdtFragment.programDateTime;
7897    if (startDateTime >= pdtStart || index === 0) {
7898      var _programDateTimes;
7899      const durationBetweenPdt = (((_programDateTimes = programDateTimes[index + 1]) == null ? void 0 : _programDateTimes.start) || endTime) - pdtFragment.start;
7900      if (startDateTime <= pdtStart + durationBetweenPdt * 1000) {
7901        // map to fragment with date-time range
7902        const startIndex = programDateTimes[index].sn - details.startSN;
7903        if (startIndex < 0) {
7904          return -1;
7905        }
7906        const fragments = details.fragments;
7907        if (fragments.length > programDateTimes.length) {
7908          const endSegment = programDateTimes[index + 1] || fragments[fragments.length - 1];
7909          const endIndex = endSegment.sn - details.startSN;
7910          for (let i = endIndex; i > startIndex; i--) {
7911            const fragStartDateTime = fragments[i].programDateTime;
7912            if (startDateTime >= fragStartDateTime && startDateTime < fragStartDateTime + fragments[i].duration * 1000) {
7913              return i;
7914            }
7915          }
7916        }
7917        return startIndex;
7918      }
7919    }
7920  }
7921  return -1;
7922}
7923function parseKey(keyTagAttributes, baseurl, parsed) {
7924  var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT;
7925  // https://tools.ietf.org/html/rfc8216#section-4.3.2.4
7926  const keyAttrs = new AttrList(keyTagAttributes, parsed);
7927  const decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : '';
7928  const decrypturi = keyAttrs.URI;
7929  const decryptiv = keyAttrs.hexadecimalInteger('IV');
7930  const decryptkeyformatversions = keyAttrs.KEYFORMATVERSIONS;
7931  // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity".
7932  const decryptkeyformat = (_keyAttrs$KEYFORMAT = keyAttrs.KEYFORMAT) != null ? _keyAttrs$KEYFORMAT : 'identity';
7933  if (decrypturi && keyAttrs.IV && !decryptiv) {
7934    logger.error(`Invalid IV: ${keyAttrs.IV}`);
7935  }
7936  // If decrypturi is a URI with a scheme, then baseurl will be ignored
7937  // No uri is allowed when METHOD is NONE
7938  const resolvedUri = decrypturi ? M3U8Parser.resolve(decrypturi, baseurl) : '';
7939  const keyFormatVersions = (decryptkeyformatversions ? decryptkeyformatversions : '1').split('/').map(Number).filter(Number.isFinite);
7940  return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv, keyAttrs.KEYID);
7941}
7942function parseStartTimeOffset(startAttributes) {
7943  const startAttrs = new AttrList(startAttributes);
7944  const startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET');
7945  if (isFiniteNumber(startTimeOffset)) {
7946    return startTimeOffset;
7947  }
7948  return null;
7949}
7950function setCodecs(codecsAttributeValue, level) {
7951  let codecs = (codecsAttributeValue || '').split(/[ ,]+/).filter(c => c);
7952  ['video', 'audio', 'text'].forEach(type => {
7953    const filtered = codecs.filter(codec => isCodecType(codec, type));
7954    if (filtered.length) {
7955      // Comma separated list of all codecs for type
7956      level[`${type}Codec`] = filtered.map(c => c.split('/')[0]).join(',');
7957      // Remove known codecs so that only unknownCodecs are left after iterating through each type
7958      codecs = codecs.filter(codec => filtered.indexOf(codec) === -1);
7959    }
7960  });
7961  level.unknownCodecs = codecs;
7962}
7963function assignCodec(media, groupItem, codecProperty) {
7964  const codecValue = groupItem[codecProperty];
7965  if (codecValue) {
7966    media[codecProperty] = codecValue;
7967  }
7968}
7969function backfillProgramDateTimes(fragments, firstPdtIndex) {
7970  let fragPrev = fragments[firstPdtIndex];
7971  for (let i = firstPdtIndex; i--;) {
7972    const frag = fragments[i];
7973    // Exit on delta-playlist skipped segments
7974    if (!frag) {
7975      return;
7976    }
7977    frag.programDateTime = fragPrev.programDateTime - frag.duration * 1000;
7978    fragPrev = frag;
7979  }
7980}
7981function assignProgramDateTime(frag, prevFrag, programDateTimes) {
7982  if (frag.rawProgramDateTime) {
7983    programDateTimes.push(frag);
7984  } else if (prevFrag != null && prevFrag.programDateTime) {
7985    frag.programDateTime = prevFrag.endProgramDateTime;
7986  }
7987}
7988function setInitSegment(frag, mapAttrs, id, levelkeys) {
7989  frag.relurl = mapAttrs.URI;
7990  if (mapAttrs.BYTERANGE) {
7991    frag.setByteRange(mapAttrs.BYTERANGE);
7992  }
7993  frag.level = id;
7994  frag.sn = 'initSegment';
7995  if (levelkeys) {
7996    frag.levelkeys = levelkeys;
7997  }
7998  frag.initSegment = null;
7999}
8000function setFragLevelKeys(frag, levelkeys, level) {
8001  frag.levelkeys = levelkeys;
8002  const {
8003    encryptedFragments
8004  } = level;
8005  if ((!encryptedFragments.length || encryptedFragments[encryptedFragments.length - 1].levelkeys !== levelkeys) && Object.keys(levelkeys).some(format => levelkeys[format].isCommonEncryption)) {
8006    encryptedFragments.push(frag);
8007  }
8008}
8009function assignMultipleMediaPlaylistTagOccuranceError(level, tag, result) {
8010  level.playlistParsingError = new Error(`#EXT-X-${tag} must not appear more than once (${result[0]})`);
8011}
8012function assignMustAppearBeforeSegmentsError(level, tag, result) {
8013  level.playlistParsingError = new Error(`#EXT-X-${tag} must appear before the first Media Segment (${result[0]})`);
8014}
8015
8016function updateFromToPTS(fragFrom, fragTo) {
8017  const fragToPTS = fragTo.startPTS;
8018  // if we know startPTS[toIdx]
8019  if (isFiniteNumber(fragToPTS)) {
8020    // update fragment duration.
8021    // it helps to fix drifts between playlist reported duration and fragment real duration
8022    let duration = 0;
8023    let frag;
8024    if (fragTo.sn > fragFrom.sn) {
8025      duration = fragToPTS - fragFrom.start;
8026      frag = fragFrom;
8027    } else {
8028      duration = fragFrom.start - fragToPTS;
8029      frag = fragTo;
8030    }
8031    if (frag.duration !== duration) {
8032      frag.setDuration(duration);
8033    }
8034    // we dont know startPTS[toIdx]
8035  } else if (fragTo.sn > fragFrom.sn) {
8036    const contiguous = fragFrom.cc === fragTo.cc;
8037    // TODO: With part-loading end/durations we need to confirm the whole fragment is loaded before using (or setting) minEndPTS
8038    if (contiguous && fragFrom.minEndPTS) {
8039      fragTo.setStart(fragFrom.start + (fragFrom.minEndPTS - fragFrom.start));
8040    } else {
8041      fragTo.setStart(fragFrom.start + fragFrom.duration);
8042    }
8043  } else {
8044    fragTo.setStart(Math.max(fragFrom.start - fragTo.duration, 0));
8045  }
8046}
8047function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS, logger) {
8048  const parsedMediaDuration = endPTS - startPTS;
8049  if (parsedMediaDuration <= 0) {
8050    logger.warn('Fragment should have a positive duration', frag);
8051    endPTS = startPTS + frag.duration;
8052    endDTS = startDTS + frag.duration;
8053  }
8054  let maxStartPTS = startPTS;
8055  let minEndPTS = endPTS;
8056  const fragStartPts = frag.startPTS;
8057  const fragEndPts = frag.endPTS;
8058  if (isFiniteNumber(fragStartPts)) {
8059    // delta PTS between audio and video
8060    const deltaPTS = Math.abs(fragStartPts - startPTS);
8061    if (details && deltaPTS > details.totalduration) {
8062      logger.warn(`media timestamps and playlist times differ by ${deltaPTS}s for level ${frag.level} ${details.url}`);
8063    } else if (!isFiniteNumber(frag.deltaPTS)) {
8064      frag.deltaPTS = deltaPTS;
8065    } else {
8066      frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS);
8067    }
8068    maxStartPTS = Math.max(startPTS, fragStartPts);
8069    startPTS = Math.min(startPTS, fragStartPts);
8070    startDTS = frag.startDTS !== undefined ? Math.min(startDTS, frag.startDTS) : startDTS;
8071    minEndPTS = Math.min(endPTS, fragEndPts);
8072    endPTS = Math.max(endPTS, fragEndPts);
8073    endDTS = frag.endDTS !== undefined ? Math.max(endDTS, frag.endDTS) : endDTS;
8074  }
8075  const drift = startPTS - frag.start;
8076  if (frag.start !== 0) {
8077    frag.setStart(startPTS);
8078  }
8079  frag.setDuration(endPTS - frag.start);
8080  frag.startPTS = startPTS;
8081  frag.maxStartPTS = maxStartPTS;
8082  frag.startDTS = startDTS;
8083  frag.endPTS = endPTS;
8084  frag.minEndPTS = minEndPTS;
8085  frag.endDTS = endDTS;
8086  const sn = frag.sn;
8087  // exit if sn out of range
8088  if (!details || sn < details.startSN || sn > details.endSN) {
8089    return 0;
8090  }
8091  let i;
8092  const fragIdx = sn - details.startSN;
8093  const fragments = details.fragments;
8094  // update frag reference in fragments array
8095  // rationale is that fragments array might not contain this frag object.
8096  // this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS()
8097  // if we don't update frag, we won't be able to propagate PTS info on the playlist
8098  // resulting in invalid sliding computation
8099  fragments[fragIdx] = frag;
8100  // adjust fragment PTS/duration from seqnum-1 to frag 0
8101  for (i = fragIdx; i > 0; i--) {
8102    updateFromToPTS(fragments[i], fragments[i - 1]);
8103  }
8104
8105  // adjust fragment PTS/duration from seqnum to last frag
8106  for (i = fragIdx; i < fragments.length - 1; i++) {
8107    updateFromToPTS(fragments[i], fragments[i + 1]);
8108  }
8109  if (details.fragmentHint) {
8110    updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint);
8111  }
8112  details.PTSKnown = details.alignedSliding = true;
8113  return drift;
8114}
8115function mergeDetails(oldDetails, newDetails, logger) {
8116  if (oldDetails === newDetails) {
8117    return;
8118  }
8119  // Track the last initSegment processed. Initialize it to the last one on the timeline.
8120  let currentInitSegment = null;
8121  const oldFragments = oldDetails.fragments;
8122  for (let i = oldFragments.length - 1; i >= 0; i--) {
8123    const oldInit = oldFragments[i].initSegment;
8124    if (oldInit) {
8125      currentInitSegment = oldInit;
8126      break;
8127    }
8128  }
8129  if (oldDetails.fragmentHint) {
8130    // prevent PTS and duration from being adjusted on the next hint
8131    delete oldDetails.fragmentHint.endPTS;
8132  }
8133  // check if old/new playlists have fragments in common
8134  // loop through overlapping SN and update startPTS, cc, and duration if any found
8135  let PTSFrag;
8136  mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments) => {
8137    if ((!newDetails.startCC || newDetails.skippedSegments) && newFrag.cc !== oldFrag.cc) {
8138      const ccOffset = oldFrag.cc - newFrag.cc;
8139      for (let i = newFragIndex; i < newFragments.length; i++) {
8140        newFragments[i].cc += ccOffset;
8141      }
8142      newDetails.endCC = newFragments[newFragments.length - 1].cc;
8143    }
8144    if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
8145      newFrag.setStart(newFrag.startPTS = oldFrag.startPTS);
8146      newFrag.startDTS = oldFrag.startDTS;
8147      newFrag.maxStartPTS = oldFrag.maxStartPTS;
8148      newFrag.endPTS = oldFrag.endPTS;
8149      newFrag.endDTS = oldFrag.endDTS;
8150      newFrag.minEndPTS = oldFrag.minEndPTS;
8151      newFrag.setDuration(oldFrag.endPTS - oldFrag.startPTS);
8152      if (newFrag.duration) {
8153        PTSFrag = newFrag;
8154      }
8155
8156      // PTS is known when any segment has startPTS and endPTS
8157      newDetails.PTSKnown = newDetails.alignedSliding = true;
8158    }
8159    if (oldFrag.hasStreams) {
8160      newFrag.elementaryStreams = oldFrag.elementaryStreams;
8161    }
8162    newFrag.loader = oldFrag.loader;
8163    if (oldFrag.hasStats) {
8164      newFrag.stats = oldFrag.stats;
8165    }
8166    if (oldFrag.initSegment) {
8167      newFrag.initSegment = oldFrag.initSegment;
8168      currentInitSegment = oldFrag.initSegment;
8169    }
8170  });
8171  const newFragments = newDetails.fragments;
8172  const fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
8173  if (currentInitSegment) {
8174    fragmentsToCheck.forEach(frag => {
8175      var _currentInitSegment;
8176      if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
8177        frag.initSegment = currentInitSegment;
8178      }
8179    });
8180  }
8181  if (newDetails.skippedSegments) {
8182    newDetails.deltaUpdateFailed = newFragments.some(frag => !frag);
8183    if (newDetails.deltaUpdateFailed) {
8184      logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');
8185      for (let i = newDetails.skippedSegments; i--;) {
8186        newFragments.shift();
8187      }
8188      newDetails.startSN = newFragments[0].sn;
8189    } else {
8190      if (newDetails.canSkipDateRanges) {
8191        newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails, logger);
8192      }
8193      const programDateTimes = oldDetails.fragments.filter(frag => frag.rawProgramDateTime);
8194      if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
8195        for (let i = 1; i < fragmentsToCheck.length; i++) {
8196          if (fragmentsToCheck[i].programDateTime === null) {
8197            assignProgramDateTime(fragmentsToCheck[i], fragmentsToCheck[i - 1], programDateTimes);
8198          }
8199        }
8200      }
8201      mapDateRanges(programDateTimes, newDetails);
8202    }
8203    newDetails.endCC = newFragments[newFragments.length - 1].cc;
8204  }
8205  if (!newDetails.startCC) {
8206    var _fragPriorToNewStart$;
8207    const fragPriorToNewStart = getFragmentWithSN(oldDetails, newDetails.startSN - 1);
8208    newDetails.startCC = (_fragPriorToNewStart$ = fragPriorToNewStart == null ? void 0 : fragPriorToNewStart.cc) != null ? _fragPriorToNewStart$ : newFragments[0].cc;
8209  }
8210
8211  // Merge parts
8212  mapPartIntersection(oldDetails.partList, newDetails.partList, (oldPart, newPart) => {
8213    newPart.elementaryStreams = oldPart.elementaryStreams;
8214    newPart.stats = oldPart.stats;
8215  });
8216
8217  // if at least one fragment contains PTS info, recompute PTS information for all fragments
8218  if (PTSFrag) {
8219    updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS, logger);
8220  } else {
8221    // ensure that delta is within oldFragments range
8222    // also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61])
8223    // in that case we also need to adjust start offset of all fragments
8224    adjustSliding(oldDetails, newDetails);
8225  }
8226  if (newFragments.length) {
8227    newDetails.totalduration = newDetails.edge - newFragments[0].start;
8228  }
8229  newDetails.driftStartTime = oldDetails.driftStartTime;
8230  newDetails.driftStart = oldDetails.driftStart;
8231  const advancedDateTime = newDetails.advancedDateTime;
8232  if (newDetails.advanced && advancedDateTime) {
8233    const edge = newDetails.edge;
8234    if (!newDetails.driftStart) {
8235      newDetails.driftStartTime = advancedDateTime;
8236      newDetails.driftStart = edge;
8237    }
8238    newDetails.driftEndTime = advancedDateTime;
8239    newDetails.driftEnd = edge;
8240  } else {
8241    newDetails.driftEndTime = oldDetails.driftEndTime;
8242    newDetails.driftEnd = oldDetails.driftEnd;
8243    newDetails.advancedDateTime = oldDetails.advancedDateTime;
8244  }
8245  if (newDetails.requestScheduled === -1) {
8246    newDetails.requestScheduled = oldDetails.requestScheduled;
8247  }
8248}
8249function mergeDateRanges(oldDateRanges, newDetails, logger) {
8250  const {
8251    dateRanges: deltaDateRanges,
8252    recentlyRemovedDateranges
8253  } = newDetails;
8254  const dateRanges = _extends({}, oldDateRanges);
8255  if (recentlyRemovedDateranges) {
8256    recentlyRemovedDateranges.forEach(id => {
8257      delete dateRanges[id];
8258    });
8259  }
8260  const mergeIds = Object.keys(dateRanges);
8261  const mergeCount = mergeIds.length;
8262  if (!mergeCount) {
8263    return deltaDateRanges;
8264  }
8265  Object.keys(deltaDateRanges).forEach(id => {
8266    const mergedDateRange = dateRanges[id];
8267    const dateRange = new DateRange(deltaDateRanges[id].attr, mergedDateRange);
8268    if (dateRange.isValid) {
8269      dateRanges[id] = dateRange;
8270      if (!mergedDateRange) {
8271        dateRange.tagOrder += mergeCount;
8272      }
8273    } else {
8274      logger.warn(`Ignoring invalid Playlist Delta Update DATERANGE tag: "${stringify(deltaDateRanges[id].attr)}"`);
8275    }
8276  });
8277  return dateRanges;
8278}
8279function mapPartIntersection(oldParts, newParts, intersectionFn) {
8280  if (oldParts && newParts) {
8281    let delta = 0;
8282    for (let i = 0, len = oldParts.length; i <= len; i++) {
8283      const oldPart = oldParts[i];
8284      const newPart = newParts[i + delta];
8285      if (oldPart && newPart && oldPart.index === newPart.index && oldPart.fragment.sn === newPart.fragment.sn) {
8286        intersectionFn(oldPart, newPart);
8287      } else {
8288        delta--;
8289      }
8290    }
8291  }
8292}
8293function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
8294  const skippedSegments = newDetails.skippedSegments;
8295  const start = Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN;
8296  const end = (oldDetails.fragmentHint ? 1 : 0) + (skippedSegments ? newDetails.endSN : Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN;
8297  const delta = newDetails.startSN - oldDetails.startSN;
8298  const newFrags = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments;
8299  const oldFrags = oldDetails.fragmentHint ? oldDetails.fragments.concat(oldDetails.fragmentHint) : oldDetails.fragments;
8300  for (let i = start; i <= end; i++) {
8301    const oldFrag = oldFrags[delta + i];
8302    let newFrag = newFrags[i];
8303    if (skippedSegments && !newFrag && oldFrag) {
8304      // Fill in skipped segments in delta playlist
8305      newFrag = newDetails.fragments[i] = oldFrag;
8306    }
8307    if (oldFrag && newFrag) {
8308      intersectionFn(oldFrag, newFrag, i, newFrags);
8309      const uriBefore = oldFrag.relurl;
8310      const uriAfter = newFrag.relurl;
8311      if (uriBefore && notEqualAfterStrippingQueries(uriBefore, uriAfter)) {
8312        newDetails.playlistParsingError = getSequenceError(`media sequence mismatch ${newFrag.sn}:`, oldDetails, newDetails, oldFrag, newFrag);
8313        return;
8314      } else if (oldFrag.cc !== newFrag.cc) {
8315        newDetails.playlistParsingError = getSequenceError(`discontinuity sequence mismatch (${oldFrag.cc}!=${newFrag.cc})`, oldDetails, newDetails, oldFrag, newFrag);
8316        return;
8317      }
8318    }
8319  }
8320}
8321function getSequenceError(message, oldDetails, newDetails, oldFrag, newFrag) {
8322  return new Error(`${message} ${newFrag.url}
8323Playlist starting @${oldDetails.startSN}
8324${oldDetails.m3u8}
8325
8326Playlist starting @${newDetails.startSN}
8327${newDetails.m3u8}`);
8328}
8329function adjustSliding(oldDetails, newDetails, matchingStableVariantOrRendition = true) {
8330  const delta = newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN;
8331  const oldFragments = oldDetails.fragments;
8332  const advancedOrStable = delta >= 0;
8333  let sliding = 0;
8334  if (advancedOrStable && delta < oldFragments.length) {
8335    sliding = oldFragments[delta].start;
8336  } else if (advancedOrStable && newDetails.startSN === oldDetails.endSN + 1) {
8337    sliding = oldDetails.fragmentEnd;
8338  } else if (advancedOrStable && matchingStableVariantOrRendition) {
8339    // align with expected position (updated playlist start sequence is past end sequence of last update)
8340    sliding = oldDetails.fragmentStart + delta * newDetails.levelTargetDuration;
8341  } else if (!newDetails.skippedSegments && newDetails.fragmentStart === 0) {
8342    // align new start with old (playlist switch has a sequence with no overlap and should not be used for alignment)
8343    sliding = oldDetails.fragmentStart;
8344  } else {
8345    // new details already has a sliding offset or has skipped segments
8346    return;
8347  }
8348  addSliding(newDetails, sliding);
8349}
8350function addSliding(details, sliding) {
8351  if (sliding) {
8352    const fragments = details.fragments;
8353    for (let i = details.skippedSegments; i < fragments.length; i++) {
8354      fragments[i].addStart(sliding);
8355    }
8356    if (details.fragmentHint) {
8357      details.fragmentHint.addStart(sliding);
8358    }
8359  }
8360}
8361function computeReloadInterval(newDetails, distanceToLiveEdgeMs = Infinity) {
8362  let reloadInterval = 1000 * newDetails.targetduration;
8363  if (newDetails.updated) {
8364    // Use last segment duration when shorter than target duration and near live edge
8365    const fragments = newDetails.fragments;
8366    const liveEdgeMaxTargetDurations = 4;
8367    if (fragments.length && reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs) {
8368      const lastSegmentDuration = fragments[fragments.length - 1].duration * 1000;
8369      if (lastSegmentDuration < reloadInterval) {
8370        reloadInterval = lastSegmentDuration;
8371      }
8372    }
8373  } else {
8374    // estimate = 'miss half average';
8375    // follow HLS Spec, If the client reloads a Playlist file and finds that it has not
8376    // changed then it MUST wait for a period of one-half the target
8377    // duration before retrying.
8378    reloadInterval /= 2;
8379  }
8380  return Math.round(reloadInterval);
8381}
8382function getFragmentWithSN(details, sn, fragCurrent) {
8383  if (!details) {
8384    return null;
8385  }
8386  let fragment = details.fragments[sn - details.startSN];
8387  if (fragment) {
8388    return fragment;
8389  }
8390  fragment = details.fragmentHint;
8391  if (fragment && fragment.sn === sn) {
8392    return fragment;
8393  }
8394  if (sn < details.startSN && fragCurrent && fragCurrent.sn === sn) {
8395    return fragCurrent;
8396  }
8397  return null;
8398}
8399function getPartWith(details, sn, partIndex) {
8400  if (!details) {
8401    return null;
8402  }
8403  return findPart(details.partList, sn, partIndex);
8404}
8405function findPart(partList, sn, partIndex) {
8406  if (partList) {
8407    for (let i = partList.length; i--;) {
8408      const part = partList[i];
8409      if (part.index === partIndex && part.fragment.sn === sn) {
8410        return part;
8411      }
8412    }
8413  }
8414  return null;
8415}
8416function reassignFragmentLevelIndexes(levels) {
8417  levels.forEach((level, index) => {
8418    var _level$details;
8419    (_level$details = level.details) == null || _level$details.fragments.forEach(fragment => {
8420      fragment.level = index;
8421      if (fragment.initSegment) {
8422        fragment.initSegment.level = index;
8423      }
8424    });
8425  });
8426}
8427function notEqualAfterStrippingQueries(uriBefore, uriAfter) {
8428  if (uriBefore !== uriAfter && uriAfter) {
8429    return stripQuery(uriBefore) !== stripQuery(uriAfter);
8430  }
8431  return false;
8432}
8433function stripQuery(uri) {
8434  return uri.replace(/\?[^?]*$/, '');
8435}
8436
8437function findFirstFragWithCC(fragments, cc) {
8438  for (let i = 0, len = fragments.length; i < len; i++) {
8439    var _fragments$i;
8440    if (((_fragments$i = fragments[i]) == null ? void 0 : _fragments$i.cc) === cc) {
8441      return fragments[i];
8442    }
8443  }
8444  return null;
8445}
8446function shouldAlignOnDiscontinuities(refDetails, details) {
8447  if (refDetails) {
8448    if (details.startCC < refDetails.endCC && details.endCC > refDetails.startCC) {
8449      return true;
8450    }
8451  }
8452  return false;
8453}
8454function adjustFragmentStart(frag, sliding) {
8455  const start = frag.start + sliding;
8456  frag.startPTS = start;
8457  frag.setStart(start);
8458  frag.endPTS = start + frag.duration;
8459}
8460function adjustSlidingStart(sliding, details) {
8461  // Update segments
8462  const fragments = details.fragments;
8463  for (let i = 0, len = fragments.length; i < len; i++) {
8464    adjustFragmentStart(fragments[i], sliding);
8465  }
8466  // Update LL-HLS parts at the end of the playlist
8467  if (details.fragmentHint) {
8468    adjustFragmentStart(details.fragmentHint, sliding);
8469  }
8470  details.alignedSliding = true;
8471}
8472
8473/**
8474 * Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a
8475 * contiguous stream with the last fragments.
8476 * The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to
8477 * download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time
8478 * and an extra download.
8479 * @param lastLevel
8480 * @param details
8481 */
8482function alignStream(switchDetails, details) {
8483  if (!switchDetails) {
8484    return;
8485  }
8486  alignDiscontinuities(details, switchDetails);
8487  if (!details.alignedSliding) {
8488    // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level.
8489    // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same
8490    // discontinuity sequence.
8491    alignMediaPlaylistByPDT(details, switchDetails);
8492  }
8493  if (!details.alignedSliding && !details.skippedSegments) {
8494    // Try to align on sn so that we pick a better start fragment.
8495    // Do not perform this on playlists with delta updates as this is only to align levels on switch
8496    // and adjustSliding only adjusts fragments after skippedSegments.
8497    adjustSliding(switchDetails, details, false);
8498  }
8499}
8500
8501/**
8502 * Ajust the start of fragments in `details` by the difference in time between fragments of the latest
8503 * shared discontinuity sequence change.
8504 * @param lastLevel - The details of the last loaded level
8505 * @param details - The details of the new level
8506 */
8507function alignDiscontinuities(details, refDetails) {
8508  if (!shouldAlignOnDiscontinuities(refDetails, details)) {
8509    return;
8510  }
8511  const targetCC = Math.min(refDetails.endCC, details.endCC);
8512  const refFrag = findFirstFragWithCC(refDetails.fragments, targetCC);
8513  const frag = findFirstFragWithCC(details.fragments, targetCC);
8514  if (!refFrag || !frag) {
8515    return;
8516  }
8517  logger.log(`Aligning playlist at start of dicontinuity sequence ${targetCC}`);
8518  const delta = refFrag.start - frag.start;
8519  adjustSlidingStart(delta, details);
8520}
8521
8522/**
8523 * Ensures appropriate time-alignment between renditions based on PDT.
8524 * This function assumes the timelines represented in `refDetails` are accurate, including the PDTs
8525 * for the last discontinuity sequence number shared by both playlists when present,
8526 * and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation
8527 * times/timelines of `details` accordingly.
8528 * Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks,
8529 * the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks
8530 * are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should
8531 * be consistent across playlists, per the HLS spec.
8532 * @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition).
8533 * @param refDetails - The details of the reference rendition with start and PDT times for alignment.
8534 */
8535function alignMediaPlaylistByPDT(details, refDetails) {
8536  if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) {
8537    return;
8538  }
8539  const fragments = details.fragments;
8540  const refFragments = refDetails.fragments;
8541  if (!fragments.length || !refFragments.length) {
8542    return;
8543  }
8544
8545  // Calculate a delta to apply to all fragments according to the delta in PDT times and start times
8546  // of a fragment in the reference details, and a fragment in the target details of the same discontinuity.
8547  // If a fragment of the same discontinuity was not found use the middle fragment of both.
8548  let refFrag;
8549  let frag;
8550  const targetCC = Math.min(refDetails.endCC, details.endCC);
8551  if (refDetails.startCC < targetCC && details.startCC < targetCC) {
8552    refFrag = findFirstFragWithCC(refFragments, targetCC);
8553    frag = findFirstFragWithCC(fragments, targetCC);
8554  }
8555  if (!refFrag || !frag) {
8556    refFrag = refFragments[Math.floor(refFragments.length / 2)];
8557    frag = findFirstFragWithCC(fragments, refFrag.cc) || fragments[Math.floor(fragments.length / 2)];
8558  }
8559  const refPDT = refFrag.programDateTime;
8560  const targetPDT = frag.programDateTime;
8561  if (!refPDT || !targetPDT) {
8562    return;
8563  }
8564  const delta = (targetPDT - refPDT) / 1000 - (frag.start - refFrag.start);
8565  adjustSlidingStart(delta, details);
8566}
8567
8568function addEventListener(el, type, listener) {
8569  removeEventListener(el, type, listener);
8570  el.addEventListener(type, listener);
8571}
8572function removeEventListener(el, type, listener) {
8573  el.removeEventListener(type, listener);
8574}
8575
8576/**
8577 *  TimeRanges to string helper
8578 */
8579
8580const TimeRanges = {
8581  toString: function (r) {
8582    let log = '';
8583    const len = r.length;
8584    for (let i = 0; i < len; i++) {
8585      log += `[${r.start(i).toFixed(3)}-${r.end(i).toFixed(3)}]`;
8586    }
8587    return log;
8588  }
8589};
8590
8591const State = {
8592  STOPPED: 'STOPPED',
8593  IDLE: 'IDLE',
8594  KEY_LOADING: 'KEY_LOADING',
8595  FRAG_LOADING: 'FRAG_LOADING',
8596  FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY',
8597  WAITING_TRACK: 'WAITING_TRACK',
8598  PARSING: 'PARSING',
8599  PARSED: 'PARSED',
8600  ENDED: 'ENDED',
8601  ERROR: 'ERROR',
8602  WAITING_INIT_PTS: 'WAITING_INIT_PTS',
8603  WAITING_LEVEL: 'WAITING_LEVEL'
8604};
8605class BaseStreamController extends TaskLoop {
8606  constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
8607    super(logPrefix, hls.logger);
8608    this.hls = void 0;
8609    this.fragPrevious = null;
8610    this.fragCurrent = null;
8611    this.fragmentTracker = void 0;
8612    this.transmuxer = null;
8613    this._state = State.STOPPED;
8614    this.playlistType = void 0;
8615    this.media = null;
8616    this.mediaBuffer = null;
8617    this.config = void 0;
8618    this.bitrateTest = false;
8619    this.lastCurrentTime = 0;
8620    this.nextLoadPosition = 0;
8621    this.startPosition = 0;
8622    this.startTimeOffset = null;
8623    this.retryDate = 0;
8624    this.levels = null;
8625    this.fragmentLoader = void 0;
8626    this.keyLoader = void 0;
8627    this.levelLastLoaded = null;
8628    this.startFragRequested = false;
8629    this.decrypter = void 0;
8630    this.initPTS = [];
8631    this.buffering = true;
8632    this.loadingParts = false;
8633    this.loopSn = void 0;
8634    this.onMediaSeeking = () => {
8635      const {
8636        config,
8637        fragCurrent,
8638        media,
8639        mediaBuffer,
8640        state
8641      } = this;
8642      const currentTime = media ? media.currentTime : 0;
8643      const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
8644      const noFowardBuffer = !bufferInfo.len;
8645      this.log(`Media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}, ${noFowardBuffer ? 'out of' : 'in'} buffer`);
8646      if (this.state === State.ENDED) {
8647        this.resetLoadingState();
8648      } else if (fragCurrent) {
8649        // Seeking while frag load is in progress
8650        const tolerance = config.maxFragLookUpTolerance;
8651        const fragStartOffset = fragCurrent.start - tolerance;
8652        const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
8653        // if seeking out of buffered range or into new one
8654        if (noFowardBuffer || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
8655          const pastFragment = currentTime > fragEndOffset;
8656          // if the seek position is outside the current fragment range
8657          if (currentTime < fragStartOffset || pastFragment) {
8658            if (pastFragment && fragCurrent.loader) {
8659              this.log(`Cancelling fragment load for seek (sn: ${fragCurrent.sn})`);
8660              fragCurrent.abortRequests();
8661              this.resetLoadingState();
8662            }
8663            this.fragPrevious = null;
8664          }
8665        }
8666      }
8667      if (media) {
8668        // Remove gap fragments
8669        this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
8670
8671        // Don't set lastCurrentTime with backward seeks (allows for frag selection with strict tolerances)
8672        const lastCurrentTime = this.lastCurrentTime;
8673        if (currentTime > lastCurrentTime) {
8674          this.lastCurrentTime = currentTime;
8675        }
8676        if (!this.loadingParts) {
8677          const bufferEnd = Math.max(bufferInfo.end, currentTime);
8678          const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
8679          if (shouldLoadParts) {
8680            this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
8681            this.loadingParts = shouldLoadParts;
8682          }
8683        }
8684      }
8685
8686      // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
8687      if (!this.hls.hasEnoughToStart) {
8688        this.log(`Setting ${noFowardBuffer ? 'startPosition' : 'nextLoadPosition'} to ${currentTime} for seek without enough to start`);
8689        this.nextLoadPosition = currentTime;
8690        if (noFowardBuffer) {
8691          this.startPosition = currentTime;
8692        }
8693      }
8694      if (noFowardBuffer && this.state === State.IDLE) {
8695        // Async tick to speed up processing
8696        this.tickImmediate();
8697      }
8698    };
8699    this.onMediaEnded = () => {
8700      // reset startPosition and lastCurrentTime to restart playback @ stream beginning
8701      this.log(`setting startPosition to 0 because media ended`);
8702      this.startPosition = this.lastCurrentTime = 0;
8703    };
8704    this.playlistType = playlistType;
8705    this.hls = hls;
8706    this.fragmentLoader = new FragmentLoader(hls.config);
8707    this.keyLoader = keyLoader;
8708    this.fragmentTracker = fragmentTracker;
8709    this.config = hls.config;
8710    this.decrypter = new Decrypter(hls.config);
8711  }
8712  registerListeners() {
8713    const {
8714      hls
8715    } = this;
8716    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
8717    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
8718    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
8719    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
8720    hls.on(Events.ERROR, this.onError, this);
8721  }
8722  unregisterListeners() {
8723    const {
8724      hls
8725    } = this;
8726    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
8727    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
8728    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
8729    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
8730    hls.off(Events.ERROR, this.onError, this);
8731  }
8732  doTick() {
8733    this.onTickEnd();
8734  }
8735  onTickEnd() {}
8736  startLoad(startPosition) {}
8737  stopLoad() {
8738    if (this.state === State.STOPPED) {
8739      return;
8740    }
8741    this.fragmentLoader.abort();
8742    this.keyLoader.abort(this.playlistType);
8743    const frag = this.fragCurrent;
8744    if (frag != null && frag.loader) {
8745      frag.abortRequests();
8746      this.fragmentTracker.removeFragment(frag);
8747    }
8748    this.resetTransmuxer();
8749    this.fragCurrent = null;
8750    this.fragPrevious = null;
8751    this.clearInterval();
8752    this.clearNextTick();
8753    this.state = State.STOPPED;
8754  }
8755  get startPositionValue() {
8756    const {
8757      nextLoadPosition,
8758      startPosition
8759    } = this;
8760    if (startPosition === -1 && nextLoadPosition) {
8761      return nextLoadPosition;
8762    }
8763    return startPosition;
8764  }
8765  get bufferingEnabled() {
8766    return this.buffering;
8767  }
8768  pauseBuffering() {
8769    this.buffering = false;
8770  }
8771  resumeBuffering() {
8772    this.buffering = true;
8773  }
8774  get inFlightFrag() {
8775    return {
8776      frag: this.fragCurrent,
8777      state: this.state
8778    };
8779  }
8780  _streamEnded(bufferInfo, levelDetails) {
8781    // Stream is never "ended" when playlist is live or media is detached
8782    if (levelDetails.live || !this.media) {
8783      return false;
8784    }
8785    // Stream is not "ended" when nothing is buffered past the start
8786    const bufferEnd = bufferInfo.end || 0;
8787    const timelineStart = this.config.timelineOffset || 0;
8788    if (bufferEnd <= timelineStart) {
8789      return false;
8790    }
8791    // Stream is not "ended" when there is a second buffered range starting before the end of the playlist
8792    const bufferedRanges = bufferInfo.buffered;
8793    if (this.config.maxBufferHole && bufferedRanges && bufferedRanges.length > 1) {
8794      // make sure bufferInfo accounts for any gaps
8795      bufferInfo = BufferHelper.bufferedInfo(bufferedRanges, bufferInfo.start, 0);
8796    }
8797    const nextStart = bufferInfo.nextStart;
8798    const hasSecondBufferedRange = nextStart && nextStart > timelineStart && nextStart < levelDetails.edge;
8799    if (hasSecondBufferedRange) {
8800      return false;
8801    }
8802    // Playhead is in unbuffered region. Marking EoS now could result in Safari failing to dispatch "ended" event following seek on start.
8803    if (this.media.currentTime < bufferInfo.start) {
8804      return false;
8805    }
8806    const partList = levelDetails.partList;
8807    // Since the last part isn't guaranteed to correspond to the last playlist segment for Low-Latency HLS,
8808    // check instead if the last part is buffered.
8809    if (partList != null && partList.length) {
8810      const lastPart = partList[partList.length - 1];
8811
8812      // Checking the midpoint of the part for potential margin of error and related issues.
8813      // NOTE: Technically I believe parts could yield content that is < the computed duration (including potential a duration of 0)
8814      // and still be spec-compliant, so there may still be edge cases here. Likewise, there could be issues in end of stream
8815      // part mismatches for independent audio and video playlists/segments.
8816      const lastPartBuffered = BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2);
8817      return lastPartBuffered;
8818    }
8819    const playlistType = levelDetails.fragments[levelDetails.fragments.length - 1].type;
8820    return this.fragmentTracker.isEndListAppended(playlistType);
8821  }
8822  getLevelDetails() {
8823    if (this.levels && this.levelLastLoaded !== null) {
8824      return this.levelLastLoaded.details;
8825    }
8826  }
8827  get timelineOffset() {
8828    const configuredTimelineOffset = this.config.timelineOffset;
8829    if (configuredTimelineOffset) {
8830      var _this$getLevelDetails;
8831      return ((_this$getLevelDetails = this.getLevelDetails()) == null ? void 0 : _this$getLevelDetails.appliedTimelineOffset) || configuredTimelineOffset;
8832    }
8833    return 0;
8834  }
8835  onMediaAttached(event, data) {
8836    const media = this.media = this.mediaBuffer = data.media;
8837    addEventListener(media, 'seeking', this.onMediaSeeking);
8838    addEventListener(media, 'ended', this.onMediaEnded);
8839    const config = this.config;
8840    if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
8841      this.startLoad(config.startPosition);
8842    }
8843  }
8844  onMediaDetaching(event, data) {
8845    const transferringMedia = !!data.transferMedia;
8846    const media = this.media;
8847    if (media === null) {
8848      return;
8849    }
8850    if (media.ended) {
8851      this.log('MSE detaching and video ended, reset startPosition');
8852      this.startPosition = this.lastCurrentTime = 0;
8853    }
8854
8855    // remove video listeners
8856    removeEventListener(media, 'seeking', this.onMediaSeeking);
8857    removeEventListener(media, 'ended', this.onMediaEnded);
8858    if (this.keyLoader && !transferringMedia) {
8859      this.keyLoader.detach();
8860    }
8861    this.media = this.mediaBuffer = null;
8862    this.loopSn = undefined;
8863    if (transferringMedia) {
8864      this.resetLoadingState();
8865      this.resetTransmuxer();
8866      return;
8867    }
8868    this.loadingParts = false;
8869    this.fragmentTracker.removeAllFragments();
8870    this.stopLoad();
8871  }
8872  onManifestLoading() {
8873    this.initPTS = [];
8874    this.levels = this.levelLastLoaded = this.fragCurrent = null;
8875    this.lastCurrentTime = this.startPosition = 0;
8876    this.startFragRequested = false;
8877  }
8878  onError(event, data) {}
8879  onManifestLoaded(event, data) {
8880    this.startTimeOffset = data.startTimeOffset;
8881  }
8882  onHandlerDestroying() {
8883    this.stopLoad();
8884    if (this.transmuxer) {
8885      this.transmuxer.destroy();
8886      this.transmuxer = null;
8887    }
8888    super.onHandlerDestroying();
8889    // @ts-ignore
8890    this.hls = this.onMediaSeeking = this.onMediaEnded = null;
8891  }
8892  onHandlerDestroyed() {
8893    this.state = State.STOPPED;
8894    if (this.fragmentLoader) {
8895      this.fragmentLoader.destroy();
8896    }
8897    if (this.keyLoader) {
8898      this.keyLoader.destroy();
8899    }
8900    if (this.decrypter) {
8901      this.decrypter.destroy();
8902    }
8903    this.hls = this.log = this.warn = this.decrypter = this.keyLoader = this.fragmentLoader = this.fragmentTracker = null;
8904    super.onHandlerDestroyed();
8905  }
8906  loadFragment(frag, level, targetBufferTime) {
8907    this.startFragRequested = true;
8908    this._loadFragForPlayback(frag, level, targetBufferTime);
8909  }
8910  _loadFragForPlayback(fragment, level, targetBufferTime) {
8911    const progressCallback = data => {
8912      const frag = data.frag;
8913      if (this.fragContextChanged(frag)) {
8914        this.warn(`${frag.type} sn: ${frag.sn}${data.part ? ' part: ' + data.part.index : ''} of ${this.fragInfo(frag, false, data.part)}) was dropped during download.`);
8915        this.fragmentTracker.removeFragment(frag);
8916        return;
8917      }
8918      frag.stats.chunkCount++;
8919      this._handleFragmentLoadProgress(data);
8920    };
8921    this._doFragLoad(fragment, level, targetBufferTime, progressCallback).then(data => {
8922      if (!data) {
8923        // if we're here we probably needed to backtrack or are waiting for more parts
8924        return;
8925      }
8926      const state = this.state;
8927      const frag = data.frag;
8928      if (this.fragContextChanged(frag)) {
8929        if (state === State.FRAG_LOADING || !this.fragCurrent && state === State.PARSING) {
8930          this.fragmentTracker.removeFragment(frag);
8931          this.state = State.IDLE;
8932        }
8933        return;
8934      }
8935      if ('payload' in data) {
8936        this.log(`Loaded ${frag.type} sn: ${frag.sn} of ${this.playlistLabel()} ${frag.level}`);
8937        this.hls.trigger(Events.FRAG_LOADED, data);
8938      }
8939
8940      // Pass through the whole payload; controllers not implementing progressive loading receive data from this callback
8941      this._handleFragmentLoadComplete(data);
8942    }).catch(reason => {
8943      if (this.state === State.STOPPED || this.state === State.ERROR) {
8944        return;
8945      }
8946      this.warn(`Frag error: ${(reason == null ? void 0 : reason.message) || reason}`);
8947      this.resetFragmentLoading(fragment);
8948    });
8949  }
8950  clearTrackerIfNeeded(frag) {
8951    var _this$mediaBuffer;
8952    const {
8953      fragmentTracker
8954    } = this;
8955    const fragState = fragmentTracker.getState(frag);
8956    if (fragState === FragmentState.APPENDING) {
8957      // Lower the max buffer length and try again
8958      const playlistType = frag.type;
8959      const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
8960      const minForwardBufferLength = Math.max(frag.duration, bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength);
8961      // If backtracking, always remove from the tracker without reducing max buffer length
8962      const backtrackFragment = this.backtrackFragment;
8963      const backtracked = backtrackFragment ? frag.sn - backtrackFragment.sn : 0;
8964      if (backtracked === 1 || this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)) {
8965        fragmentTracker.removeFragment(frag);
8966      }
8967    } else if (((_this$mediaBuffer = this.mediaBuffer) == null ? void 0 : _this$mediaBuffer.buffered.length) === 0) {
8968      // Stop gap for bad tracker / buffer flush behavior
8969      fragmentTracker.removeAllFragments();
8970    } else if (fragmentTracker.hasParts(frag.type)) {
8971      // In low latency mode, remove fragments for which only some parts were buffered
8972      fragmentTracker.detectPartialFragments({
8973        frag,
8974        part: null,
8975        stats: frag.stats,
8976        id: frag.type
8977      });
8978      if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) {
8979        fragmentTracker.removeFragment(frag);
8980      }
8981    }
8982  }
8983  checkLiveUpdate(details) {
8984    if (details.updated && !details.live) {
8985      // Live stream ended, update fragment tracker
8986      const lastFragment = details.fragments[details.fragments.length - 1];
8987      this.fragmentTracker.detectPartialFragments({
8988        frag: lastFragment,
8989        part: null,
8990        stats: lastFragment.stats,
8991        id: lastFragment.type
8992      });
8993    }
8994    if (!details.fragments[0]) {
8995      details.deltaUpdateFailed = true;
8996    }
8997  }
8998  waitForLive(levelInfo) {
8999    const details = levelInfo.details;
9000    return (details == null ? void 0 : details.live) && details.type !== 'EVENT' && (this.levelLastLoaded !== levelInfo || details.expired);
9001  }
9002  flushMainBuffer(startOffset, endOffset, type = null) {
9003    if (!(startOffset - endOffset)) {
9004      return;
9005    }
9006    // When alternate audio is playing, the audio-stream-controller is responsible for the audio buffer. Otherwise,
9007    // passing a null type flushes both buffers
9008    const flushScope = {
9009      startOffset,
9010      endOffset,
9011      type
9012    };
9013    this.hls.trigger(Events.BUFFER_FLUSHING, flushScope);
9014  }
9015  _loadInitSegment(fragment, level) {
9016    this._doFragLoad(fragment, level).then(data => {
9017      const frag = data == null ? void 0 : data.frag;
9018      if (!frag || this.fragContextChanged(frag) || !this.levels) {
9019        throw new Error('init load aborted');
9020      }
9021      return data;
9022    }).then(data => {
9023      const {
9024        hls
9025      } = this;
9026      const {
9027        frag,
9028        payload
9029      } = data;
9030      const decryptData = frag.decryptdata;
9031
9032      // check to see if the payload needs to be decrypted
9033      if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
9034        const startTime = self.performance.now();
9035        // decrypt init segment data
9036        return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
9037          hls.trigger(Events.ERROR, {
9038            type: ErrorTypes.MEDIA_ERROR,
9039            details: ErrorDetails.FRAG_DECRYPT_ERROR,
9040            fatal: false,
9041            error: err,
9042            reason: err.message,
9043            frag
9044          });
9045          throw err;
9046        }).then(decryptedData => {
9047          const endTime = self.performance.now();
9048          hls.trigger(Events.FRAG_DECRYPTED, {
9049            frag,
9050            payload: decryptedData,
9051            stats: {
9052              tstart: startTime,
9053              tdecrypt: endTime
9054            }
9055          });
9056          data.payload = decryptedData;
9057          return this.completeInitSegmentLoad(data);
9058        });
9059      }
9060      return this.completeInitSegmentLoad(data);
9061    }).catch(reason => {
9062      if (this.state === State.STOPPED || this.state === State.ERROR) {
9063        return;
9064      }
9065      this.warn(reason);
9066      this.resetFragmentLoading(fragment);
9067    });
9068  }
9069  completeInitSegmentLoad(data) {
9070    const {
9071      levels
9072    } = this;
9073    if (!levels) {
9074      throw new Error('init load aborted, missing levels');
9075    }
9076    const stats = data.frag.stats;
9077    if (this.state !== State.STOPPED) {
9078      this.state = State.IDLE;
9079    }
9080    data.frag.data = new Uint8Array(data.payload);
9081    stats.parsing.start = stats.buffering.start = self.performance.now();
9082    stats.parsing.end = stats.buffering.end = self.performance.now();
9083    this.tick();
9084  }
9085  unhandledEncryptionError(initSegment, frag) {
9086    var _tracks$audio, _tracks$video;
9087    const tracks = initSegment.tracks;
9088    if (tracks && !frag.encrypted && ((_tracks$audio = tracks.audio) != null && _tracks$audio.encrypted || (_tracks$video = tracks.video) != null && _tracks$video.encrypted) && (!this.config.emeEnabled || !this.keyLoader.emeController)) {
9089      const media = this.media;
9090      const error = new Error(`Encrypted track with no key in ${this.fragInfo(frag)} (media ${media ? 'attached mediaKeys: ' + media.mediaKeys : 'detached'})` );
9091      this.warn(error.message);
9092      // Ignore if media is detached or mediaKeys are set
9093      if (!media || media.mediaKeys) {
9094        return false;
9095      }
9096      this.hls.trigger(Events.ERROR, {
9097        type: ErrorTypes.KEY_SYSTEM_ERROR,
9098        details: ErrorDetails.KEY_SYSTEM_NO_KEYS,
9099        fatal: false,
9100        error,
9101        frag
9102      });
9103      this.resetTransmuxer();
9104      return true;
9105    }
9106    return false;
9107  }
9108  fragContextChanged(frag) {
9109    const {
9110      fragCurrent
9111    } = this;
9112    return !frag || !fragCurrent || frag.sn !== fragCurrent.sn || frag.level !== fragCurrent.level;
9113  }
9114  fragBufferedComplete(frag, part) {
9115    const media = this.mediaBuffer ? this.mediaBuffer : this.media;
9116    this.log(`Buffered ${frag.type} sn: ${frag.sn}${part ? ' part: ' + part.index : ''} of ${this.fragInfo(frag, false, part)} > buffer:${media ? TimeRanges.toString(BufferHelper.getBuffered(media)) : '(detached)'})`);
9117    if (isMediaFragment(frag)) {
9118      var _this$levels;
9119      if (frag.type !== PlaylistLevelType.SUBTITLE) {
9120        const el = frag.elementaryStreams;
9121        if (!Object.keys(el).some(type => !!el[type])) {
9122          // empty segment
9123          this.state = State.IDLE;
9124          return;
9125        }
9126      }
9127      const level = (_this$levels = this.levels) == null ? void 0 : _this$levels[frag.level];
9128      if (level != null && level.fragmentError) {
9129        this.log(`Resetting level fragment error count of ${level.fragmentError} on frag buffered`);
9130        level.fragmentError = 0;
9131      }
9132    }
9133    this.state = State.IDLE;
9134  }
9135  _handleFragmentLoadComplete(fragLoadedEndData) {
9136    const {
9137      transmuxer
9138    } = this;
9139    if (!transmuxer) {
9140      return;
9141    }
9142    const {
9143      frag,
9144      part,
9145      partsLoaded
9146    } = fragLoadedEndData;
9147    // If we did not load parts, or loaded all parts, we have complete (not partial) fragment data
9148    const complete = !partsLoaded || partsLoaded.length === 0 || partsLoaded.some(fragLoaded => !fragLoaded);
9149    const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index : -1, !complete);
9150    transmuxer.flush(chunkMeta);
9151  }
9152  _handleFragmentLoadProgress(frag) {}
9153  _doFragLoad(frag, level, targetBufferTime = null, progressCallback) {
9154    var _frag$decryptdata;
9155    this.fragCurrent = frag;
9156    const details = level.details;
9157    if (!this.levels || !details) {
9158      throw new Error(`frag load aborted, missing level${details ? '' : ' detail'}s`);
9159    }
9160    let keyLoadingPromise = null;
9161    if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
9162      this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistLabel()} ${frag.level}`);
9163      this.state = State.KEY_LOADING;
9164      this.fragCurrent = frag;
9165      keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
9166        if (!this.fragContextChanged(keyLoadedData.frag)) {
9167          this.hls.trigger(Events.KEY_LOADED, keyLoadedData);
9168          if (this.state === State.KEY_LOADING) {
9169            this.state = State.IDLE;
9170          }
9171          return keyLoadedData;
9172        }
9173      });
9174      this.hls.trigger(Events.KEY_LOADING, {
9175        frag
9176      });
9177      if (this.fragCurrent === null) {
9178        this.log(`context changed in KEY_LOADING`);
9179        return Promise.resolve(null);
9180      }
9181    } else if (!frag.encrypted) {
9182      keyLoadingPromise = this.keyLoader.loadClear(frag, details.encryptedFragments, this.startFragRequested);
9183      if (keyLoadingPromise) {
9184        this.log(`[eme] blocking frag load until media-keys acquired`);
9185      }
9186    }
9187    const fragPrevious = this.fragPrevious;
9188    if (isMediaFragment(frag) && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
9189      const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
9190      if (shouldLoadParts !== this.loadingParts) {
9191        this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
9192        this.loadingParts = shouldLoadParts;
9193      }
9194    }
9195    targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
9196    if (this.loadingParts && isMediaFragment(frag)) {
9197      const partList = details.partList;
9198      if (partList && progressCallback) {
9199        if (targetBufferTime > details.fragmentEnd && details.fragmentHint) {
9200          frag = details.fragmentHint;
9201        }
9202        const partIndex = this.getNextPart(partList, frag, targetBufferTime);
9203        if (partIndex > -1) {
9204          const part = partList[partIndex];
9205          frag = this.fragCurrent = part.fragment;
9206          this.log(`Loading ${frag.type} sn: ${frag.sn} part: ${part.index} (${partIndex}/${partList.length - 1}) of ${this.fragInfo(frag, false, part)}) cc: ${frag.cc} [${details.startSN}-${details.endSN}], target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9207          this.nextLoadPosition = part.start + part.duration;
9208          this.state = State.FRAG_LOADING;
9209          let _result;
9210          if (keyLoadingPromise) {
9211            _result = keyLoadingPromise.then(keyLoadedData => {
9212              if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) {
9213                return null;
9214              }
9215              return this.doFragPartsLoad(frag, part, level, progressCallback);
9216            }).catch(error => this.handleFragLoadError(error));
9217          } else {
9218            _result = this.doFragPartsLoad(frag, part, level, progressCallback).catch(error => this.handleFragLoadError(error));
9219          }
9220          this.hls.trigger(Events.FRAG_LOADING, {
9221            frag,
9222            part,
9223            targetBufferTime
9224          });
9225          if (this.fragCurrent === null) {
9226            return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING parts`));
9227          }
9228          return _result;
9229        } else if (!frag.url || this.loadedEndOfParts(partList, targetBufferTime)) {
9230          // Fragment hint has no parts
9231          return Promise.resolve(null);
9232        }
9233      }
9234    }
9235    if (isMediaFragment(frag) && this.loadingParts) {
9236      var _details$partList;
9237      this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)} Check buffer at sn: ${frag.sn} loaded parts: ${(_details$partList = details.partList) == null ? void 0 : _details$partList.filter(p => p.loaded).map(p => `[${p.start}-${p.end}]`)}`);
9238      this.loadingParts = false;
9239    } else if (!frag.url) {
9240      // Selected fragment hint for part but not loading parts
9241      return Promise.resolve(null);
9242    }
9243    this.log(`Loading ${frag.type} sn: ${frag.sn} of ${this.fragInfo(frag, false)}) cc: ${frag.cc} ${'[' + details.startSN + '-' + details.endSN + ']'}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
9244    // Don't update nextLoadPosition for fragments which are not buffered
9245    if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
9246      this.nextLoadPosition = frag.start + frag.duration;
9247    }
9248    this.state = State.FRAG_LOADING;
9249
9250    // Load key before streaming fragment data
9251    const dataOnProgress = this.config.progressive && frag.type !== PlaylistLevelType.SUBTITLE;
9252    let result;
9253    if (dataOnProgress && keyLoadingPromise) {
9254      result = keyLoadingPromise.then(keyLoadedData => {
9255        if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) {
9256          return null;
9257        }
9258        return this.fragmentLoader.load(frag, progressCallback);
9259      }).catch(error => this.handleFragLoadError(error));
9260    } else {
9261      // load unencrypted fragment data with progress event,
9262      // or handle fragment result after key and fragment are finished loading
9263      result = Promise.all([this.fragmentLoader.load(frag, dataOnProgress ? progressCallback : undefined), keyLoadingPromise]).then(([fragLoadedData]) => {
9264        if (!dataOnProgress && progressCallback) {
9265          progressCallback(fragLoadedData);
9266        }
9267        return fragLoadedData;
9268      }).catch(error => this.handleFragLoadError(error));
9269    }
9270    this.hls.trigger(Events.FRAG_LOADING, {
9271      frag,
9272      targetBufferTime
9273    });
9274    if (this.fragCurrent === null) {
9275      return Promise.reject(new Error(`frag load aborted, context changed in FRAG_LOADING`));
9276    }
9277    return result;
9278  }
9279  doFragPartsLoad(frag, fromPart, level, progressCallback) {
9280    return new Promise((resolve, reject) => {
9281      var _level$details;
9282      const partsLoaded = [];
9283      const initialPartList = (_level$details = level.details) == null ? void 0 : _level$details.partList;
9284      const loadPart = part => {
9285        this.fragmentLoader.loadPart(frag, part, progressCallback).then(partLoadedData => {
9286          partsLoaded[part.index] = partLoadedData;
9287          const loadedPart = partLoadedData.part;
9288          this.hls.trigger(Events.FRAG_LOADED, partLoadedData);
9289          const nextPart = getPartWith(level.details, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1);
9290          if (nextPart) {
9291            loadPart(nextPart);
9292          } else {
9293            return resolve({
9294              frag,
9295              part: loadedPart,
9296              partsLoaded
9297            });
9298          }
9299        }).catch(reject);
9300      };
9301      loadPart(fromPart);
9302    });
9303  }
9304  handleFragLoadError(error) {
9305    if ('data' in error) {
9306      const data = error.data;
9307      if (data.frag && data.details === ErrorDetails.INTERNAL_ABORTED) {
9308        this.handleFragLoadAborted(data.frag, data.part);
9309      } else if (data.frag && data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
9310        data.frag.abortRequests();
9311        this.resetStartWhenNotLoaded();
9312        this.resetFragmentLoading(data.frag);
9313      } else {
9314        this.hls.trigger(Events.ERROR, data);
9315      }
9316    } else {
9317      this.hls.trigger(Events.ERROR, {
9318        type: ErrorTypes.OTHER_ERROR,
9319        details: ErrorDetails.INTERNAL_EXCEPTION,
9320        err: error,
9321        error,
9322        fatal: true
9323      });
9324    }
9325    return null;
9326  }
9327  _handleTransmuxerFlush(chunkMeta) {
9328    const context = this.getCurrentContext(chunkMeta);
9329    if (!context || this.state !== State.PARSING) {
9330      if (!this.fragCurrent && this.state !== State.STOPPED && this.state !== State.ERROR) {
9331        this.state = State.IDLE;
9332      }
9333      return;
9334    }
9335    const {
9336      frag,
9337      part,
9338      level
9339    } = context;
9340    const now = self.performance.now();
9341    frag.stats.parsing.end = now;
9342    if (part) {
9343      part.stats.parsing.end = now;
9344    }
9345    // See if part loading should be disabled/enabled based on buffer and playback position.
9346    const levelDetails = this.getLevelDetails();
9347    const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
9348    const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
9349    if (shouldLoadParts !== this.loadingParts) {
9350      this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
9351      this.loadingParts = shouldLoadParts;
9352    }
9353    this.updateLevelTiming(frag, part, level, chunkMeta.partial);
9354  }
9355  shouldLoadParts(details, bufferEnd) {
9356    if (this.config.lowLatencyMode) {
9357      if (!details) {
9358        return this.loadingParts;
9359      }
9360      if (details.partList) {
9361        var _details$fragmentHint;
9362        // Buffer must be ahead of first part + duration of parts after last segment
9363        // and playback must be at or past segment adjacent to part list
9364        const firstPart = details.partList[0];
9365        // Loading of VTT subtitle parts is not implemented in subtitle-stream-controller (#7460)
9366        if (firstPart.fragment.type === PlaylistLevelType.SUBTITLE) {
9367          return false;
9368        }
9369        const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
9370        if (bufferEnd >= safePartStart) {
9371          var _this$media;
9372          const playhead = this.hls.hasEnoughToStart ? ((_this$media = this.media) == null ? void 0 : _this$media.currentTime) || this.lastCurrentTime : this.getLoadPosition();
9373          if (playhead > firstPart.start - firstPart.fragment.duration) {
9374            return true;
9375          }
9376        }
9377      }
9378    }
9379    return false;
9380  }
9381  getCurrentContext(chunkMeta) {
9382    const {
9383      levels,
9384      fragCurrent
9385    } = this;
9386    const {
9387      level: levelIndex,
9388      sn,
9389      part: partIndex
9390    } = chunkMeta;
9391    if (!(levels != null && levels[levelIndex])) {
9392      this.warn(`Levels object was unset while buffering fragment ${sn} of ${this.playlistLabel()} ${levelIndex}. The current chunk will not be buffered.`);
9393      return null;
9394    }
9395    const level = levels[levelIndex];
9396    const levelDetails = level.details;
9397    const part = partIndex > -1 ? getPartWith(levelDetails, sn, partIndex) : null;
9398    const frag = part ? part.fragment : getFragmentWithSN(levelDetails, sn, fragCurrent);
9399    if (!frag) {
9400      return null;
9401    }
9402    if (fragCurrent && fragCurrent !== frag) {
9403      frag.stats = fragCurrent.stats;
9404    }
9405    return {
9406      frag,
9407      part,
9408      level
9409    };
9410  }
9411  bufferFragmentData(data, frag, part, chunkMeta, noBacktracking) {
9412    if (this.state !== State.PARSING) {
9413      return;
9414    }
9415    const {
9416      data1,
9417      data2
9418    } = data;
9419    let buffer = data1;
9420    if (data2) {
9421      // Combine the moof + mdat so that we buffer with a single append
9422      buffer = appendUint8Array(data1, data2);
9423    }
9424    if (!buffer.length) {
9425      return;
9426    }
9427    const offsetTimestamp = this.initPTS[frag.cc];
9428    const offset = offsetTimestamp ? -offsetTimestamp.baseTime / offsetTimestamp.timescale : undefined;
9429    const segment = {
9430      type: data.type,
9431      frag,
9432      part,
9433      chunkMeta,
9434      offset,
9435      parent: frag.type,
9436      data: buffer
9437    };
9438    this.hls.trigger(Events.BUFFER_APPENDING, segment);
9439    if (data.dropped && data.independent && !part) {
9440      if (noBacktracking) {
9441        return;
9442      }
9443      // Clear buffer so that we reload previous segments sequentially if required
9444      this.flushBufferGap(frag);
9445    }
9446  }
9447  flushBufferGap(frag) {
9448    const media = this.media;
9449    if (!media) {
9450      return;
9451    }
9452    // If currentTime is not buffered, clear the back buffer so that we can backtrack as much as needed
9453    if (!BufferHelper.isBuffered(media, media.currentTime)) {
9454      this.flushMainBuffer(0, frag.start);
9455      return;
9456    }
9457    // Remove back-buffer without interrupting playback to allow back tracking
9458    const currentTime = media.currentTime;
9459    const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
9460    const fragDuration = frag.duration;
9461    const segmentFraction = Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25);
9462    const start = Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction);
9463    if (frag.start - start > segmentFraction) {
9464      this.flushMainBuffer(start, frag.start);
9465    }
9466  }
9467  getFwdBufferInfo(bufferable, type) {
9468    var _this$media2;
9469    const pos = this.getLoadPosition();
9470    if (!isFiniteNumber(pos)) {
9471      return null;
9472    }
9473    const backwardSeek = this.lastCurrentTime > pos;
9474    const maxBufferHole = backwardSeek || (_this$media2 = this.media) != null && _this$media2.paused ? 0 : this.config.maxBufferHole;
9475    return this.getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole);
9476  }
9477  getFwdBufferInfoAtPos(bufferable, pos, type, maxBufferHole) {
9478    const bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole);
9479    // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
9480    if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
9481      const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
9482      if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
9483        const gapDuration = Math.max(Math.min(bufferInfo.nextStart, bufferedFragAtPos.end) - pos, maxBufferHole);
9484        return BufferHelper.bufferInfo(bufferable, pos, gapDuration);
9485      }
9486    }
9487    return bufferInfo;
9488  }
9489  getMaxBufferLength(levelBitrate) {
9490    const {
9491      config
9492    } = this;
9493    let maxBufLen;
9494    if (levelBitrate) {
9495      maxBufLen = Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength);
9496    } else {
9497      maxBufLen = config.maxBufferLength;
9498    }
vendor: 21,189 bytes, lines 9499-9977
9499    return Math.min(maxBufLen, config.maxMaxBufferLength);
9500  }
9501  exceedsMaxBuffer(bufferInfo, maxBufLen, selected) {
9502    const nextStart = bufferInfo.nextStart;
9503    if (nextStart && selected.start > nextStart) {
9504      const bufferedRanges = bufferInfo.buffered;
9505      if (bufferedRanges) {
9506        let fullBufferLength = bufferInfo.len;
9507        const bufferedIndex = bufferInfo.bufferedIndex;
9508        for (let i = bufferedRanges.length - 1; i > bufferedIndex; i--) {
9509          if (bufferedRanges[i].start < selected.start) {
9510            fullBufferLength += bufferedRanges[i].end - bufferedRanges[i].start;
9511          }
9512        }
9513        return fullBufferLength >= maxBufLen;
9514      }
9515    }
9516    return false;
9517  }
9518  reduceMaxBufferLength(threshold, fragDuration) {
9519    const config = this.config;
9520    const minLength = Math.max(Math.min(threshold - fragDuration, config.maxBufferLength), fragDuration);
9521    const reducedLength = Math.max(threshold - fragDuration * 3, config.maxMaxBufferLength / 2, minLength);
9522    if (reducedLength >= minLength) {
9523      // reduce max buffer length as it might be too high. we do this to avoid loop flushing ...
9524      config.maxMaxBufferLength = reducedLength;
9525      this.warn(`Reduce max buffer length to ${reducedLength}s`);
9526      return true;
9527    }
9528    return false;
9529  }
9530  getAppendedFrag(position, playlistType = PlaylistLevelType.MAIN) {
9531    const fragOrPart = this.fragmentTracker ? this.fragmentTracker.getAppendedFrag(position, playlistType) : null;
9532    if (fragOrPart && 'fragment' in fragOrPart) {
9533      return fragOrPart.fragment;
9534    }
9535    return fragOrPart;
9536  }
9537  getNextFragment(pos, levelDetails) {
9538    const fragments = levelDetails.fragments;
9539    const fragLen = fragments.length;
9540    if (!fragLen) {
9541      return null;
9542    }
9543
9544    // find fragment index, contiguous with end of buffer position
9545    const {
9546      config
9547    } = this;
9548    const playlistStart = levelDetails.fragmentStart;
9549    const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
9550    let frag = null;
9551    if (levelDetails.live) {
9552      const initialLiveManifestSize = config.initialLiveManifestSize;
9553      if (fragLen < initialLiveManifestSize) {
9554        this.warn(`Not enough fragments to start playback (have: ${fragLen}, need: ${initialLiveManifestSize})`);
9555        return null;
9556      }
9557      // The real fragment start times for a live stream are only known after the PTS range for that level is known.
9558      // In order to discover the range, we load the best matching fragment for that level and demux it.
9559      // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
9560      // we get the fragment matching that start time
9561      if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < playlistStart) {
9562        var _frag;
9563        if (canLoadParts && !this.loadingParts) {
9564          this.log(`LL-Part loading ON for initial live fragment`);
9565          this.loadingParts = true;
9566        }
9567        frag = this.getInitialLiveFragment(levelDetails);
9568        const configValue = this.config.startPosition;
9569        const mainStart = this.hls.startPosition;
9570        const liveSyncPosition = this.hls.liveSyncPosition;
9571        const fragStart = ((_frag = frag) == null ? void 0 : _frag.start) || 0;
9572        let startPosition;
9573        let reason;
9574        if (mainStart !== -1 && mainStart >= playlistStart) {
9575          startPosition = mainStart;
9576          reason = mainStart === configValue ? 'config' : 'next load start';
9577        } else if (liveSyncPosition !== null) {
9578          startPosition = liveSyncPosition;
9579          reason = 'live edge';
9580        } else {
9581          startPosition = pos;
9582          reason = 'buffer pos';
9583        }
9584        if (startPosition < fragStart) {
9585          startPosition = fragStart;
9586          reason = 'live frag start';
9587        }
9588        if (startPosition < playlistStart) {
9589          startPosition = playlistStart;
9590          reason = 'playlist start';
9591        }
9592        if (this.startPosition != startPosition || this.nextLoadPosition != startPosition) {
9593          this.log(`Setting startPosition to ${startPosition.toFixed(3)} ${mainStart === -1 ? '' : `(from ${configValue}) `}based on ${reason}. live edge: ${liveSyncPosition} live frag start: ${fragStart.toFixed(3)} playlist start: ${playlistStart.toFixed(3)} buffer pos: ${pos}`);
9594          this.startPosition = this.nextLoadPosition = startPosition;
9595        }
9596      }
9597    } else if (pos <= playlistStart) {
9598      // VoD playlist: if loadPosition before start of playlist, load first fragment
9599      frag = fragments[0];
9600    }
9601
9602    // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
9603    if (!frag) {
9604      const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
9605      frag = this.getFragmentAtPosition(pos, end, levelDetails);
9606    }
9607    let programFrag = this.filterReplacedPrimary(frag, levelDetails);
9608    if (!programFrag && frag) {
9609      const curSNIdx = frag.sn - levelDetails.startSN;
9610      programFrag = this.filterReplacedPrimary(fragments[curSNIdx + 1] || null, levelDetails);
9611    }
9612    return this.mapToInitFragWhenRequired(programFrag);
9613  }
9614  isLoopLoading(frag, targetBufferTime) {
9615    if (this.nextLoadPosition <= targetBufferTime) {
9616      return false;
9617    }
9618    const trackerState = this.fragmentTracker.getState(frag);
9619    return trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap;
9620  }
9621  getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) {
9622    let nextFragment = null;
9623    if (frag.gap) {
9624      nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails);
9625      if (nextFragment && !nextFragment.gap && bufferInfo.nextStart) {
9626        // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length
9627        const nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType, 0);
9628        if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) {
9629          // Returning here might result in not finding an audio and video candiate to skip to
9630          const sn = nextFragment.sn;
9631          if (this.loopSn !== sn) {
9632            this.log(`buffer full after gaps in "${playlistType}" playlist starting at sn: ${sn}`);
9633            this.loopSn = sn;
9634          }
9635          return null;
9636        }
9637      }
9638    }
9639    this.loopSn = undefined;
9640    return nextFragment;
9641  }
9642  get primaryPrefetch() {
9643    if (interstitialsEnabled(this.config)) {
9644      var _this$hls$interstitia;
9645      const playingInterstitial = (_this$hls$interstitia = this.hls.interstitialsManager) == null || (_this$hls$interstitia = _this$hls$interstitia.playingItem) == null ? void 0 : _this$hls$interstitia.event;
9646      if (playingInterstitial) {
9647        return true;
9648      }
9649    }
9650    return false;
9651  }
9652  filterReplacedPrimary(frag, details) {
9653    if (!frag) {
9654      return frag;
9655    }
9656    if (interstitialsEnabled(this.config) && frag.type !== PlaylistLevelType.SUBTITLE) {
9657      // Do not load fragments outside the buffering schedule segment
9658      const interstitials = this.hls.interstitialsManager;
9659      const bufferingItem = interstitials == null ? void 0 : interstitials.bufferingItem;
9660      if (bufferingItem) {
9661        const bufferingInterstitial = bufferingItem.event;
9662        if (bufferingInterstitial) {
9663          // Do not stream fragments while buffering Interstitial Events (except for overlap at the start)
9664          if (bufferingInterstitial.appendInPlace || Math.abs(frag.start - bufferingItem.start) > 1 || bufferingItem.start === 0) {
9665            return null;
9666          }
9667        } else {
9668          // Limit fragment loading to media in schedule item
9669          if (frag.end <= bufferingItem.start && (details == null ? void 0 : details.live) === false) {
9670            // fragment ends by schedule item start
9671            // this.fragmentTracker.fragBuffered(frag, true);
9672            return null;
9673          }
9674          if (frag.start > bufferingItem.end && bufferingItem.nextEvent) {
9675            // fragment is past schedule item end
9676            // allow some overflow when not appending in place to prevent stalls
9677            if (bufferingItem.nextEvent.appendInPlace || frag.start - bufferingItem.end > 1) {
9678              return null;
9679            }
9680          }
9681        }
9682      }
9683      // Skip loading of fragments that overlap completely with appendInPlace interstitials
9684      const playerQueue = interstitials == null ? void 0 : interstitials.playerQueue;
9685      if (playerQueue) {
9686        for (let i = playerQueue.length; i--;) {
9687          const interstitial = playerQueue[i].interstitial;
9688          if (interstitial.appendInPlace && frag.start >= interstitial.startTime && frag.end <= interstitial.resumeTime) {
9689            return null;
9690          }
9691        }
9692      }
9693    }
9694    return frag;
9695  }
9696  mapToInitFragWhenRequired(frag) {
9697    // If an initSegment is present, it must be buffered first
9698    if (frag != null && frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
9699      return frag.initSegment;
9700    }
9701    return frag;
9702  }
9703  getNextPart(partList, frag, targetBufferTime) {
9704    let nextPart = -1;
9705    let contiguous = false;
9706    let independentAttrOmitted = true;
9707    for (let i = 0, len = partList.length; i < len; i++) {
9708      const part = partList[i];
9709      independentAttrOmitted = independentAttrOmitted && !part.independent;
9710      if (nextPart > -1 && targetBufferTime < part.start) {
9711        break;
9712      }
9713      const loaded = part.loaded;
9714      if (loaded) {
9715        nextPart = -1;
9716      } else if (contiguous || (part.independent || independentAttrOmitted) && part.fragment === frag) {
9717        if (part.fragment !== frag) {
9718          this.warn(`Need buffer at ${targetBufferTime} but next unloaded part starts at ${part.start}`);
9719        }
9720        nextPart = i;
9721      }
9722      contiguous = loaded;
9723    }
9724    return nextPart;
9725  }
9726  loadedEndOfParts(partList, targetBufferTime) {
9727    let part;
9728    for (let i = partList.length; i--;) {
9729      part = partList[i];
9730      if (!part.loaded) {
9731        return false;
9732      }
9733      if (targetBufferTime > part.start) {
9734        return true;
9735      }
9736    }
9737    return false;
9738  }
9739
9740  /*
9741   This method is used find the best matching first fragment for a live playlist. This fragment is used to calculate the
9742   "sliding" of the playlist, which is its offset from the start of playback. After sliding we can compute the real
9743   start and end times for each fragment in the playlist (after which this method will not need to be called).
9744  */
9745  getInitialLiveFragment(levelDetails) {
9746    const fragments = levelDetails.fragments;
9747    const fragPrevious = this.fragPrevious;
9748    let frag = null;
9749    if (fragPrevious) {
9750      if (levelDetails.hasProgramDateTime) {
9751        // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding
9752        frag = findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance);
9753        if (frag) {
9754          this.log(`Live playlist, switching playlist, load frag with same PDT: ${fragPrevious.programDateTime}`);
9755        }
9756      }
9757      if (!frag) {
9758        // SN does not need to be accurate between renditions, but depending on the packaging it may be so.
9759        const targetSN = fragPrevious.sn + 1;
9760        if (targetSN >= levelDetails.startSN && targetSN <= levelDetails.endSN) {
9761          const fragNext = fragments[targetSN - levelDetails.startSN];
9762          // Ensure that we're staying within the continuity range, since PTS resets upon a new range
9763          if (fragPrevious.cc === fragNext.cc) {
9764            frag = fragNext;
9765            this.log(`Live playlist, switching playlist, load frag with next SN: ${frag.sn}`);
9766          }
9767        }
9768        // It's important to stay within the continuity range if available; otherwise the fragments in the playlist
9769        // will have the wrong start times
9770        if (!frag) {
9771          frag = findNearestWithCC(levelDetails, fragPrevious.cc, fragPrevious.end);
9772          if (frag) {
9773            this.log(`Live playlist, switching playlist, load frag with same CC: ${frag.sn}`);
9774          }
9775        }
9776      }
9777    } else {
9778      // Find a new start fragment when fragPrevious is null
9779      const liveStart = this.hls.liveSyncPosition;
9780      if (liveStart !== null) {
9781        frag = this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd : levelDetails.edge, levelDetails);
9782      }
9783    }
9784    return frag;
9785  }
9786
9787  /*
9788  This method finds the best matching fragment given the provided position.
9789   */
9790  getFragmentAtPosition(bufferEnd, end, levelDetails) {
9791    const {
9792      config
9793    } = this;
9794    let {
9795      fragPrevious
9796    } = this;
9797    let {
9798      fragments,
9799      endSN
9800    } = levelDetails;
9801    const {
9802      fragmentHint
9803    } = levelDetails;
9804    const {
9805      maxFragLookUpTolerance
9806    } = config;
9807    const partList = levelDetails.partList;
9808    const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
9809    if (loadingParts && !this.bitrateTest && partList[partList.length - 1].fragment.sn === fragmentHint.sn) {
9810      // Include incomplete fragment with parts at end
9811      fragments = fragments.concat(fragmentHint);
9812      endSN = fragmentHint.sn;
9813    }
9814    let frag;
9815    if (bufferEnd < end) {
9816      var _this$media3;
9817      const backwardSeek = bufferEnd < this.lastCurrentTime;
9818      const lookupTolerance = backwardSeek || bufferEnd > end - maxFragLookUpTolerance || (_this$media3 = this.media) != null && _this$media3.paused || !this.startFragRequested ? 0 : maxFragLookUpTolerance;
9819      // Remove the tolerance if it would put the bufferEnd past the actual end of stream
9820      // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE)
9821      frag = findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance);
9822    } else {
9823      // reach end of playlist
9824      frag = fragments[fragments.length - 1];
9825    }
9826    if (frag) {
9827      const curSNIdx = frag.sn - levelDetails.startSN;
9828      // Move fragPrevious forward to support forcing the next fragment to load
9829      // when the buffer catches up to a previously buffered range.
9830      const fragState = this.fragmentTracker.getState(frag);
9831      if (fragState === FragmentState.OK || fragState === FragmentState.PARTIAL && frag.gap) {
9832        fragPrevious = frag;
9833      }
9834      if (fragPrevious && frag.sn === fragPrevious.sn && (!loadingParts || partList[0].fragment.sn > frag.sn || !levelDetails.live)) {
9835        // Force the next fragment to load if the previous one was already selected. This can occasionally happen with
9836        // non-uniform fragment durations
9837        const sameLevel = frag.level === fragPrevious.level;
9838        if (sameLevel) {
9839          const nextFrag = fragments[curSNIdx + 1];
9840          if (frag.sn < endSN && this.fragmentTracker.getState(nextFrag) !== FragmentState.OK) {
9841            frag = nextFrag;
9842          } else {
9843            frag = null;
9844          }
9845        }
9846      }
9847    }
9848    return frag;
9849  }
9850  alignPlaylists(details, previousDetails, switchDetails) {
9851    // TODO: If not for `shouldAlignOnDiscontinuities` requiring fragPrevious.cc,
9852    //  this could all go in level-helper mergeDetails()
9853    const length = details.fragments.length;
9854    if (!length) {
9855      this.warn(`No fragments in live playlist`);
9856      return 0;
9857    }
9858    const slidingStart = details.fragmentStart;
9859    const firstLevelLoad = !previousDetails;
9860    const aligned = details.alignedSliding && isFiniteNumber(slidingStart);
9861    if (firstLevelLoad || !aligned && !slidingStart) {
9862      alignStream(switchDetails, details);
9863      const alignedSlidingStart = details.fragmentStart;
9864      this.log(`Live playlist sliding: ${alignedSlidingStart.toFixed(2)} start-sn: ${previousDetails ? previousDetails.startSN : 'na'}->${details.startSN} fragments: ${length}`);
9865      return alignedSlidingStart;
9866    }
9867    return slidingStart;
9868  }
9869  waitForCdnTuneIn(details) {
9870    // Wait for Low-Latency CDN Tune-in to get an updated playlist
9871    const advancePartLimit = 3;
9872    return details.live && details.canBlockReload && details.partTarget && details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit);
9873  }
9874  setStartPosition(details, sliding) {
9875    // compute start position if set to -1. use it straight away if value is defined
9876    let startPosition = this.startPosition;
9877    if (startPosition < sliding) {
9878      startPosition = -1;
9879    }
9880    const timelineOffset = this.timelineOffset;
9881    if (startPosition === -1) {
9882      // Use Playlist EXT-X-START:TIME-OFFSET when set
9883      // Prioritize Multivariant Playlist offset so that main, audio, and subtitle stream-controller start times match
9884      const offsetInMultivariantPlaylist = this.startTimeOffset !== null;
9885      const startTimeOffset = offsetInMultivariantPlaylist ? this.startTimeOffset : details.startTimeOffset;
9886      if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) {
9887        startPosition = sliding + startTimeOffset;
9888        if (startTimeOffset < 0) {
9889          startPosition += details.edge;
9890        }
9891        startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration);
9892        this.log(`Setting startPosition to ${startPosition} for start time offset ${startTimeOffset} found in ${offsetInMultivariantPlaylist ? 'multivariant' : 'media'} playlist`);
9893        this.startPosition = startPosition;
9894      } else if (details.live) {
9895        // Leave this.startPosition at -1, so that we can use `getInitialLiveFragment` logic when startPosition has
9896        // not been specified via the config or an as an argument to startLoad (#3736).
9897        startPosition = this.hls.liveSyncPosition || sliding;
9898        this.log(`Setting startPosition to -1 to start at live edge ${startPosition}`);
9899        this.startPosition = -1;
9900      } else {
9901        this.log(`setting startPosition to 0 by default`);
9902        this.startPosition = startPosition = 0;
9903      }
9904      this.lastCurrentTime = startPosition + timelineOffset;
9905    }
9906    this.nextLoadPosition = startPosition + timelineOffset;
9907  }
9908  getLoadPosition() {
9909    var _this$hls;
9910    const {
9911      media
9912    } = this;
9913    // if we have not yet loaded any fragment, start loading from start position
9914    let pos = 0;
9915    if ((_this$hls = this.hls) != null && _this$hls.hasEnoughToStart && media) {
9916      pos = media.currentTime;
9917    } else if (this.nextLoadPosition >= 0) {
9918      pos = this.nextLoadPosition;
9919    }
9920    return pos;
9921  }
9922  handleFragLoadAborted(frag, part) {
9923    if (this.transmuxer && frag.type === this.playlistType && isMediaFragment(frag) && frag.stats.aborted) {
9924      this.log(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of ${this.playlistLabel()} ${frag.level} was aborted`);
9925      this.resetFragmentLoading(frag);
9926    }
9927  }
9928  resetFragmentLoading(frag) {
9929    if (!this.fragCurrent || !this.fragContextChanged(frag) && this.state !== State.FRAG_LOADING_WAITING_RETRY) {
9930      this.state = State.IDLE;
9931    }
9932  }
9933  onFragmentOrKeyLoadError(filterType, data) {
9934    var _this$hls$latestLevel;
9935    if (data.chunkMeta && !data.frag) {
9936      const context = this.getCurrentContext(data.chunkMeta);
9937      if (context) {
9938        data.frag = context.frag;
9939      }
9940    }
9941    const frag = data.frag;
9942    // Handle frag error related to caller's filterType
9943    if (!frag || frag.type !== filterType || !this.levels) {
9944      return;
9945    }
9946    if (this.fragContextChanged(frag)) {
9947      var _this$fragCurrent;
9948      this.warn(`Frag load error must match current frag to retry ${frag.url} > ${(_this$fragCurrent = this.fragCurrent) == null ? void 0 : _this$fragCurrent.url}`);
9949      return;
9950    }
9951    const gapTagEncountered = data.details === ErrorDetails.FRAG_GAP;
9952    if (gapTagEncountered) {
9953      this.fragmentTracker.fragBuffered(frag, true);
9954    }
9955    // keep retrying until the limit will be reached
9956    const errorAction = data.errorAction;
9957    if (!errorAction) {
9958      this.state = State.ERROR;
9959      return;
9960    }
9961    const {
9962      action,
9963      flags,
9964      retryCount = 0,
9965      retryConfig
9966    } = errorAction;
9967    const couldRetry = !!retryConfig;
9968    const retry = couldRetry && action === NetworkErrorAction.RetryRequest;
9969    const noAlternate = couldRetry && !errorAction.resolved && flags === ErrorActionFlags.MoveAllAlternatesMatchingHost;
9970    const live = (_this$hls$latestLevel = this.hls.latestLevelDetails) == null ? void 0 : _this$hls$latestLevel.live;
9971    if (!retry && noAlternate && isMediaFragment(frag) && !frag.endList && live && !isUnusableKeyError(data)) {
9972      this.resetFragmentErrors(filterType);
9973      this.treatAsGap(frag);
9974      errorAction.resolved = true;
9975    } else if ((retry || noAlternate) && retryCount < retryConfig.maxNumRetry) {
9976      var _data$response;
9977      const offlineStatus = offlineHttpStatus((_data$response = data.response) == null ? void 0 : _data$response.code);
vendor: 11,663 bytes, lines 9978-10293
9978      const delay = getRetryDelay(retryConfig, retryCount);
9979      this.resetStartWhenNotLoaded();
9980      this.retryDate = self.performance.now() + delay;
9981      this.state = State.FRAG_LOADING_WAITING_RETRY;
9982      errorAction.resolved = true;
9983      if (offlineStatus) {
9984        this.log(`Waiting for connection (offline)`);
9985        this.retryDate = Infinity;
9986        data.reason = 'offline';
9987        return;
9988      }
9989      this.warn(`Fragment ${frag.sn} of ${filterType} ${frag.level} errored with ${data.details}, retrying loading ${retryCount + 1}/${retryConfig.maxNumRetry} in ${delay}ms`);
9990    } else if (retryConfig) {
9991      this.resetFragmentErrors(filterType);
9992      if (retryCount < retryConfig.maxNumRetry) {
9993        // Network retry is skipped when level switch is preferred
9994        if (!gapTagEncountered && action !== NetworkErrorAction.RemoveAlternatePermanently) {
9995          errorAction.resolved = true;
9996        }
9997      } else {
9998        this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
9999        return;
10000      }
10001    } else if (action === NetworkErrorAction.SendAlternateToPenaltyBox) {
10002      this.state = State.WAITING_LEVEL;
10003    } else {
10004      this.state = State.ERROR;
10005    }
10006    // Perform next async tick sooner to speed up error action resolution
10007    this.tickImmediate();
10008  }
10009  checkRetryDate() {
10010    const now = self.performance.now();
10011    const retryDate = this.retryDate;
10012    // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading
10013    const waitingForConnection = retryDate === Infinity;
10014    if (!retryDate || now >= retryDate || waitingForConnection && !offlineHttpStatus(0)) {
10015      if (waitingForConnection) {
10016        this.log(`Connection restored (online)`);
10017      }
10018      this.resetStartWhenNotLoaded();
10019      this.state = State.IDLE;
10020    }
10021  }
10022  reduceLengthAndFlushBuffer(data) {
10023    // if in appending state
10024    if (this.state === State.PARSING || this.state === State.PARSED) {
10025      const frag = data.frag;
10026      const playlistType = data.parent;
10027      const bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType);
10028      // 0.5 : tolerance needed as some browsers stalls playback before reaching buffered end
10029      // reduce max buf len if current position is buffered
10030      const buffered = bufferedInfo && bufferedInfo.len > 0.5;
10031      if (buffered) {
10032        this.reduceMaxBufferLength(bufferedInfo.len, (frag == null ? void 0 : frag.duration) || 10);
10033      }
10034      const flushBuffer = !buffered;
10035      if (flushBuffer) {
10036        // current position is not buffered, but browser is still complaining about buffer full error
10037        // this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708
10038        // in that case flush the whole audio buffer to recover
10039        this.warn(`Buffer full error while media.currentTime (${this.getLoadPosition()}) is not buffered, flush ${playlistType} buffer`);
10040      }
10041      if (frag) {
10042        this.fragmentTracker.removeFragment(frag);
10043        this.nextLoadPosition = frag.start;
10044      }
10045      this.resetLoadingState();
10046      return flushBuffer;
10047    }
10048    return false;
10049  }
10050  resetFragmentErrors(filterType) {
10051    if (filterType === PlaylistLevelType.AUDIO) {
10052      // Reset current fragment since audio track audio is essential and may not have a fail-over track
10053      this.fragCurrent = null;
10054    }
10055    // Fragment errors that result in a level switch or redundant fail-over
10056    // should reset the stream controller state to idle
10057    if (!this.hls.hasEnoughToStart) {
10058      this.startFragRequested = false;
10059    }
10060    if (this.state !== State.STOPPED) {
10061      this.state = State.IDLE;
10062    }
10063  }
10064  afterBufferFlushed(media, bufferType, playlistType) {
10065    if (!media) {
10066      return;
10067    }
10068    // After successful buffer flushing, filter flushed fragments from bufferedFrags use mediaBuffered instead of media
10069    // (so that we will check against video.buffered ranges in case of alt audio track)
10070    const bufferedTimeRanges = BufferHelper.getBuffered(media);
10071    this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, playlistType);
10072    if (this.state === State.ENDED) {
10073      this.resetLoadingState();
10074    }
10075  }
10076  resetLoadingState() {
10077    this.log('Reset loading state');
10078    this.fragCurrent = null;
10079    this.fragPrevious = null;
10080    if (this.state !== State.STOPPED) {
10081      this.state = State.IDLE;
10082    }
10083  }
10084  resetStartWhenNotLoaded() {
10085    // if loadedmetadata is not set, it means that first frag request failed
10086    // in that case, reset startFragRequested flag
10087    if (!this.hls.hasEnoughToStart) {
10088      this.startFragRequested = false;
10089      const level = this.levelLastLoaded;
10090      const details = level ? level.details : null;
10091      if (details != null && details.live) {
10092        // Update the start position and return to IDLE to recover live start
10093        this.log(`resetting startPosition for live start`);
10094        this.startPosition = -1;
10095        this.setStartPosition(details, details.fragmentStart);
10096        this.resetLoadingState();
10097      } else {
10098        this.nextLoadPosition = this.startPosition;
10099      }
10100    }
10101  }
10102  resetWhenMissingContext(chunkMeta) {
10103    this.log(`Loading context changed while buffering sn ${chunkMeta.sn} of ${this.playlistLabel()} ${chunkMeta.level === -1 ? '<removed>' : chunkMeta.level}. This chunk will not be buffered.`);
10104    this.removeUnbufferedFrags();
10105    this.resetStartWhenNotLoaded();
10106    this.resetLoadingState();
10107  }
10108  removeUnbufferedFrags(start = 0) {
10109    this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true);
10110  }
10111  updateLevelTiming(frag, part, level, partial) {
10112    const details = level.details;
10113    if (!details) {
10114      this.warn('level.details undefined');
10115      return;
10116    }
10117    const parsed = Object.keys(frag.elementaryStreams).reduce((result, type) => {
10118      const info = frag.elementaryStreams[type];
10119      if (info) {
10120        const parsedDuration = info.endPTS - info.startPTS;
10121        if (parsedDuration <= 0) {
10122          // Destroy the transmuxer after it's next time offset failed to advance because duration was <= 0.
10123          // The new transmuxer will be configured with a time offset matching the next fragment start,
10124          // preventing the timeline from shifting.
10125          this.warn(`Could not parse fragment ${frag.sn} ${type} duration reliably (${parsedDuration})`);
10126          return result || false;
10127        }
10128        const drift = partial ? 0 : updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS, this);
10129        this.hls.trigger(Events.LEVEL_PTS_UPDATED, {
10130          details,
10131          level,
10132          drift,
10133          type,
10134          frag,
10135          start: info.startPTS,
10136          end: info.endPTS
10137        });
10138        return true;
10139      }
10140      return result;
10141    }, false);
10142    if (!parsed) {
10143      var _this$transmuxer;
10144      const mediaNotFound = ((_this$transmuxer = this.transmuxer) == null ? void 0 : _this$transmuxer.error) === null;
10145      if (level.fragmentError === 0 || mediaNotFound && (level.fragmentError < 2 || frag.endList)) {
10146        // Mark and track the odd (or last) empty segment as a gap to avoid reloading
10147        this.treatAsGap(frag, level);
10148      }
10149      if (mediaNotFound) {
10150        const error = new Error(`Found no media in fragment ${frag.sn} of ${this.playlistLabel()} ${frag.level} resetting transmuxer to fallback to playlist timing`);
10151        this.warn(error.message);
10152        this.hls.trigger(Events.ERROR, {
10153          type: ErrorTypes.MEDIA_ERROR,
10154          details: ErrorDetails.FRAG_PARSING_ERROR,
10155          fatal: false,
10156          error,
10157          frag,
10158          reason: `Found no media in msn ${frag.sn} of ${this.playlistLabel()} "${level.url}"`
10159        });
10160        if (!this.hls) {
10161          return;
10162        }
10163        this.resetTransmuxer();
10164      }
10165      // For this error fallthrough. Marking parsed will allow advancing to next fragment.
10166    }
10167    this.state = State.PARSED;
10168    this.log(`Parsed ${frag.type} sn: ${frag.sn}${part ? ' part: ' + part.index : ''} of ${this.fragInfo(frag, false, part)})`);
10169    this.hls.trigger(Events.FRAG_PARSED, {
10170      frag,
10171      part
10172    });
10173  }
10174  playlistLabel() {
10175    return this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track';
10176  }
10177  fragInfo(frag, pts = true, part) {
10178    var _ref, _ref2;
10179    return `${this.playlistLabel()} ${frag.level} (${part ? 'part' : 'frag'}:[${((_ref = pts && !part ? frag.startPTS : (part || frag).start) != null ? _ref : NaN).toFixed(3)}-${((_ref2 = pts && !part ? frag.endPTS : (part || frag).end) != null ? _ref2 : NaN).toFixed(3)}]${part && frag.type === 'main' ? 'INDEPENDENT=' + (part.independent ? 'YES' : 'NO') : ''}`;
10180  }
10181  treatAsGap(frag, level) {
10182    if (level) {
10183      level.fragmentError++;
10184    }
10185    frag.gap = true;
10186    this.fragmentTracker.removeFragment(frag);
10187    this.fragmentTracker.fragBuffered(frag, true);
10188  }
10189  resetTransmuxer() {
10190    var _this$transmuxer2;
10191    (_this$transmuxer2 = this.transmuxer) == null || _this$transmuxer2.reset();
10192  }
10193  recoverWorkerError(data) {
10194    if (data.event === 'demuxerWorker') {
10195      this.fragmentTracker.removeAllFragments();
10196      if (this.transmuxer) {
10197        this.transmuxer.destroy();
10198        this.transmuxer = null;
10199      }
10200      this.resetStartWhenNotLoaded();
10201      this.resetLoadingState();
10202    }
10203  }
10204  set state(nextState) {
10205    const previousState = this._state;
10206    if (previousState !== nextState) {
10207      this._state = nextState;
10208      this.log(`${previousState}->${nextState}`);
10209    }
10210  }
10211  get state() {
10212    return this._state;
10213  }
10214}
10215function interstitialsEnabled(config) {
10216  return !!config.interstitialsController && config.enableInterstitialPlayback !== false;
10217}
10218
10219class ChunkCache {
10220  constructor() {
10221    this.chunks = [];
10222    this.dataLength = 0;
10223  }
10224  push(chunk) {
10225    this.chunks.push(chunk);
10226    this.dataLength += chunk.length;
10227  }
10228  flush() {
10229    const {
10230      chunks,
10231      dataLength
10232    } = this;
10233    let result;
10234    if (!chunks.length) {
10235      return new Uint8Array(0);
10236    } else if (chunks.length === 1) {
10237      result = chunks[0];
10238    } else {
10239      result = concatUint8Arrays(chunks, dataLength);
10240    }
10241    this.reset();
10242    return result;
10243  }
10244  reset() {
10245    this.chunks.length = 0;
10246    this.dataLength = 0;
10247  }
10248}
10249function concatUint8Arrays(chunks, dataLength) {
10250  const result = new Uint8Array(dataLength);
10251  let offset = 0;
10252  for (let i = 0; i < chunks.length; i++) {
10253    const chunk = chunks[i];
10254    result.set(chunk, offset);
10255    offset += chunk.length;
10256  }
10257  return result;
10258}
10259
10260var eventemitter3 = {exports: {}};
10261
10262var hasRequiredEventemitter3;
10263
10264function requireEventemitter3 () {
10265	if (hasRequiredEventemitter3) return eventemitter3.exports;
10266	hasRequiredEventemitter3 = 1;
10267	(function (module) {
10268
10269		var has = Object.prototype.hasOwnProperty
10270		  , prefix = '~';
10271
10272		/**
10273		 * Constructor to create a storage for our `EE` objects.
10274		 * An `Events` instance is a plain object whose properties are event names.
10275		 *
10276		 * @constructor
10277		 * @private
10278		 */
10279		function Events() {}
10280
10281		//
10282		// We try to not inherit from `Object.prototype`. In some engines creating an
10283		// instance in this way is faster than calling `Object.create(null)` directly.
10284		// If `Object.create(null)` is not supported we prefix the event names with a
10285		// character to make sure that the built-in object properties are not
10286		// overridden or used as an attack vector.
10287		//
10288		if (Object.create) {
10289		  Events.prototype = Object.create(null);
10290
10291		  //
10292		  // This hack is needed because the `__proto__` property is still inherited in
10293		  // some old browsers like Android 4, iPhone 5.1, Opera 11 and 
vendor: 4,749 bytes, lines 10293-10457
10293Safari 5.
10294		  //
10295		  if (!new Events().__proto__) prefix = false;
10296		}
10297
10298		/**
10299		 * Representation of a single event listener.
10300		 *
10301		 * @param {Function} fn The listener function.
10302		 * @param {*} context The context to invoke the listener with.
10303		 * @param {Boolean} [once=false] Specify if the listener is a one-time listener.
10304		 * @constructor
10305		 * @private
10306		 */
10307		function EE(fn, context, once) {
10308		  this.fn = fn;
10309		  this.context = context;
10310		  this.once = once || false;
10311		}
10312
10313		/**
10314		 * Add a listener for a given event.
10315		 *
10316		 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
10317		 * @param {(String|Symbol)} event The event name.
10318		 * @param {Function} fn The listener function.
10319		 * @param {*} context The context to invoke the listener with.
10320		 * @param {Boolean} once Specify if the listener is a one-time listener.
10321		 * @returns {EventEmitter}
10322		 * @private
10323		 */
10324		function addListener(emitter, event, fn, context, once) {
10325		  if (typeof fn !== 'function') {
10326		    throw new TypeError('The listener must be a function');
10327		  }
10328
10329		  var listener = new EE(fn, context || emitter, once)
10330		    , evt = prefix ? prefix + event : event;
10331
10332		  if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++;
10333		  else if (!emitter._events[evt].fn) emitter._events[evt].push(listener);
10334		  else emitter._events[evt] = [emitter._events[evt], listener];
10335
10336		  return emitter;
10337		}
10338
10339		/**
10340		 * Clear event by name.
10341		 *
10342		 * @param {EventEmitter} emitter Reference to the `EventEmitter` instance.
10343		 * @param {(String|Symbol)} evt The Event name.
10344		 * @private
10345		 */
10346		function clearEvent(emitter, evt) {
10347		  if (--emitter._eventsCount === 0) emitter._events = new Events();
10348		  else delete emitter._events[evt];
10349		}
10350
10351		/**
10352		 * Minimal `EventEmitter` interface that is molded against the Node.js
10353		 * `EventEmitter` interface.
10354		 *
10355		 * @constructor
10356		 * @public
10357		 */
10358		function EventEmitter() {
10359		  this._events = new Events();
10360		  this._eventsCount = 0;
10361		}
10362
10363		/**
10364		 * Return an array listing the events for which the emitter has registered
10365		 * listeners.
10366		 *
10367		 * @returns {Array}
10368		 * @public
10369		 */
10370		EventEmitter.prototype.eventNames = function eventNames() {
10371		  var names = []
10372		    , events
10373		    , name;
10374
10375		  if (this._eventsCount === 0) return names;
10376
10377		  for (name in (events = this._events)) {
10378		    if (has.call(events, name)) names.push(prefix ? name.slice(1) : name);
10379		  }
10380
10381		  if (Object.getOwnPropertySymbols) {
10382		    return names.concat(Object.getOwnPropertySymbols(events));
10383		  }
10384
10385		  return names;
10386		};
10387
10388		/**
10389		 * Return the listeners registered for a given event.
10390		 *
10391		 * @param {(String|Symbol)} event The event name.
10392		 * @returns {Array} The registered listeners.
10393		 * @public
10394		 */
10395		EventEmitter.prototype.listeners = function listeners(event) {
10396		  var evt = prefix ? prefix + event : event
10397		    , handlers = this._events[evt];
10398
10399		  if (!handlers) return [];
10400		  if (handlers.fn) return [handlers.fn];
10401
10402		  for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) {
10403		    ee[i] = handlers[i].fn;
10404		  }
10405
10406		  return ee;
10407		};
10408
10409		/**
10410		 * Return the number of listeners listening to a given event.
10411		 *
10412		 * @param {(String|Symbol)} event The event name.
10413		 * @returns {Number} The number of listeners.
10414		 * @public
10415		 */
10416		EventEmitter.prototype.listenerCount = function listenerCount(event) {
10417		  var evt = prefix ? prefix + event : event
10418		    , listeners = this._events[evt];
10419
10420		  if (!listeners) return 0;
10421		  if (listeners.fn) return 1;
10422		  return listeners.length;
10423		};
10424
10425		/**
10426		 * Calls each of the listeners registered for a given event.
10427		 *
10428		 * @param {(String|Symbol)} event The event name.
10429		 * @returns {Boolean} `true` if the event had listeners, else `false`.
10430		 * @public
10431		 */
10432		EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) {
10433		  var evt = prefix ? prefix + event : event;
10434
10435		  if (!this._events[evt]) return false;
10436
10437		  var listeners = this._events[evt]
10438		    , len = arguments.length
10439		    , args
10440		    , i;
10441
10442		  if (listeners.fn) {
10443		    if (listeners.once) this.removeListener(event, listeners.fn, undefined, true);
10444
10445		    switch (len) {
10446		      case 1: return listeners.fn.call(listeners.context), true;
10447		      case 2: return listeners.fn.call(listeners.context, a1), true;
10448		      case 3: return listeners.fn.call(listeners.context, a1, a2), true;
10449		      case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true;
10450		      case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true;
10451		      case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true;
10452		    }
10453
10454		    for (i = 1, args = new Array(len -1); i < len; i++) {
10455		      args[i - 1] = arguments[i];
10456		    }
10457
vendor: 4,806 bytes, lines 10458-10624
10458		    listeners.fn.apply(listeners.context, args);
10459		  } else {
10460		    var length = listeners.length
10461		      , j;
10462
10463		    for (i = 0; i < length; i++) {
10464		      if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true);
10465
10466		      switch (len) {
10467		        case 1: listeners[i].fn.call(listeners[i].context); break;
10468		        case 2: listeners[i].fn.call(listeners[i].context, a1); break;
10469		        case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break;
10470		        case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break;
10471		        default:
10472		          if (!args) for (j = 1, args = new Array(len -1); j < len; j++) {
10473		            args[j - 1] = arguments[j];
10474		          }
10475
10476		          listeners[i].fn.apply(listeners[i].context, args);
10477		      }
10478		    }
10479		  }
10480
10481		  return true;
10482		};
10483
10484		/**
10485		 * Add a listener for a given event.
10486		 *
10487		 * @param {(String|Symbol)} event The event name.
10488		 * @param {Function} fn The listener function.
10489		 * @param {*} [context=this] The context to invoke the listener with.
10490		 * @returns {EventEmitter} `this`.
10491		 * @public
10492		 */
10493		EventEmitter.prototype.on = function on(event, fn, context) {
10494		  return addListener(this, event, fn, context, false);
10495		};
10496
10497		/**
10498		 * Add a one-time listener for a given event.
10499		 *
10500		 * @param {(String|Symbol)} event The event name.
10501		 * @param {Function} fn The listener function.
10502		 * @param {*} [context=this] The context to invoke the listener with.
10503		 * @returns {EventEmitter} `this`.
10504		 * @public
10505		 */
10506		EventEmitter.prototype.once = function once(event, fn, context) {
10507		  return addListener(this, event, fn, context, true);
10508		};
10509
10510		/**
10511		 * Remove the listeners of a given event.
10512		 *
10513		 * @param {(String|Symbol)} event The event name.
10514		 * @param {Function} fn Only remove the listeners that match this function.
10515		 * @param {*} context Only remove the listeners that have this context.
10516		 * @param {Boolean} once Only remove one-time listeners.
10517		 * @returns {EventEmitter} `this`.
10518		 * @public
10519		 */
10520		EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) {
10521		  var evt = prefix ? prefix + event : event;
10522
10523		  if (!this._events[evt]) return this;
10524		  if (!fn) {
10525		    clearEvent(this, evt);
10526		    return this;
10527		  }
10528
10529		  var listeners = this._events[evt];
10530
10531		  if (listeners.fn) {
10532		    if (
10533		      listeners.fn === fn &&
10534		      (!once || listeners.once) &&
10535		      (!context || listeners.context === context)
10536		    ) {
10537		      clearEvent(this, evt);
10538		    }
10539		  } else {
10540		    for (var i = 0, events = [], length = listeners.length; i < length; i++) {
10541		      if (
10542		        listeners[i].fn !== fn ||
10543		        (once && !listeners[i].once) ||
10544		        (context && listeners[i].context !== context)
10545		      ) {
10546		        events.push(listeners[i]);
10547		      }
10548		    }
10549
10550		    //
10551		    // Reset the array, or remove it completely if we have no more listeners.
10552		    //
10553		    if (events.length) this._events[evt] = events.length === 1 ? events[0] : events;
10554		    else clearEvent(this, evt);
10555		  }
10556
10557		  return this;
10558		};
10559
10560		/**
10561		 * Remove all listeners, or those of the specified event.
10562		 *
10563		 * @param {(String|Symbol)} [event] The event name.
10564		 * @returns {EventEmitter} `this`.
10565		 * @public
10566		 */
10567		EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) {
10568		  var evt;
10569
10570		  if (event) {
10571		    evt = prefix ? prefix + event : event;
10572		    if (this._events[evt]) clearEvent(this, evt);
10573		  } else {
10574		    this._events = new Events();
10575		    this._eventsCount = 0;
10576		  }
10577
10578		  return this;
10579		};
10580
10581		//
10582		// Alias methods names because people roll like that.
10583		//
10584		EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
10585		EventEmitter.prototype.addListener = EventEmitter.prototype.on;
10586
10587		//
10588		// Expose the prefix.
10589		//
10590		EventEmitter.prefixed = prefix;
10591
10592		//
10593		// Allow `EventEmitter` to be imported as module namespace.
10594		//
10595		EventEmitter.EventEmitter = EventEmitter;
10596
10597		//
10598		// Expose the module.
10599		//
10600		{
10601		  module.exports = EventEmitter;
10602		} 
10603	} (eventemitter3));
10604	return eventemitter3.exports;
10605}
10606
10607var eventemitter3Exports = requireEventemitter3();
10608var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports);
10609
10610const version = "1.6.16";
10611
10612// ensure the worker ends up in the bundle
10613// If the worker should not be included this gets aliased to empty.js
10614const workerStore = {};
10615function hasUMDWorker() {
10616  return typeof __HLS_WORKER_BUNDLE__ === 'function';
10617}
10618function injectWorker() {
10619  const workerContext = workerStore[version];
10620  if (workerContext) {
10621    workerContext.clientCount++;
10622    return workerContext;
10623  }
10624  const blob = new self.Blob([`var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`], {
vendor: 225,853 bytes, lines 10625-17112
10625    type: 'text/javascript'
10626  });
10627  const objectURL = self.URL.createObjectURL(blob);
10628  const worker = new self.Worker(objectURL);
10629  const result = {
10630    worker,
10631    objectURL,
10632    clientCount: 1
10633  };
10634  workerStore[version] = result;
10635  return result;
10636}
10637function loadWorker(path) {
10638  const workerContext = workerStore[path];
10639  if (workerContext) {
10640    workerContext.clientCount++;
10641    return workerContext;
10642  }
10643  const scriptURL = new self.URL(path, self.location.href).href;
10644  const worker = new self.Worker(scriptURL);
10645  const result = {
10646    worker,
10647    scriptURL,
10648    clientCount: 1
10649  };
10650  workerStore[path] = result;
10651  return result;
10652}
10653function removeWorkerFromStore(path) {
10654  const workerContext = workerStore[path || version];
10655  if (workerContext) {
10656    const clientCount = workerContext.clientCount--;
10657    if (clientCount === 1) {
10658      const {
10659        worker,
10660        objectURL
10661      } = workerContext;
10662      delete workerStore[path || version];
10663      if (objectURL) {
10664        // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
10665        self.URL.revokeObjectURL(objectURL);
10666      }
10667      worker.terminate();
10668    }
10669  }
10670}
10671
10672/**
10673 * Returns true if an ID3 footer can be found at offset in data
10674 *
10675 * @param data - The data to search in
10676 * @param offset - The offset at which to start searching
10677 *
10678 * @returns `true` if an ID3 footer is found
10679 *
10680 * @internal
10681 *
10682 * @group ID3
10683 */
10684function isId3Footer(data, offset) {
10685  /*
10686   * The footer is a copy of the header, but with a different identifier
10687   */
10688  if (offset + 10 <= data.length) {
10689    // look for '3DI' identifier
10690    if (data[offset] === 0x33 && data[offset + 1] === 0x44 && data[offset + 2] === 0x49) {
10691      // check version is within range
10692      if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
10693        // check size is within range
10694        if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
10695          return true;
10696        }
10697      }
10698    }
10699  }
10700  return false;
10701}
10702
10703/**
10704 * Returns true if an ID3 header can be found at offset in data
10705 *
10706 * @param data - The data to search in
10707 * @param offset - The offset at which to start searching
10708 *
10709 * @returns `true` if an ID3 header is found
10710 *
10711 * @internal
10712 *
10713 * @group ID3
10714 */
10715function isId3Header(data, offset) {
10716  /*
10717   * http://id3.org/id3v2.3.0
10718   * [0]     = 'I'
10719   * [1]     = 'D'
10720   * [2]     = '3'
10721   * [3,4]   = {Version}
10722   * [5]     = {Flags}
10723   * [6-9]   = {ID3 Size}
10724   *
10725   * An ID3v2 tag can be detected with the following pattern:
10726   *  $49 44 33 yy yy xx zz zz zz zz
10727   * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80
10728   */
10729  if (offset + 10 <= data.length) {
10730    // look for 'ID3' identifier
10731    if (data[offset] === 0x49 && data[offset + 1] === 0x44 && data[offset + 2] === 0x33) {
10732      // check version is within range
10733      if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
10734        // check size is within range
10735        if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) {
10736          return true;
10737        }
10738      }
10739    }
10740  }
10741  return false;
10742}
10743
10744/**
10745 * Read ID3 size
10746 *
10747 * @param data - The data to read from
10748 * @param offset - The offset at which to start reading
10749 *
10750 * @returns The size
10751 *
10752 * @internal
10753 *
10754 * @group ID3
10755 */
10756function readId3Size(data, offset) {
10757  let size = 0;
10758  size = (data[offset] & 0x7f) << 21;
10759  size |= (data[offset + 1] & 0x7f) << 14;
10760  size |= (data[offset + 2] & 0x7f) << 7;
10761  size |= data[offset + 3] & 0x7f;
10762  return size;
10763}
10764
10765/**
10766 * Returns any adjacent ID3 tags found in data starting at offset, as one block of data
10767 *
10768 * @param data - The data to search in
10769 * @param offset - The offset at which to start searching
10770 *
10771 * @returns The block of data containing any ID3 tags found
10772 * or `undefined` if no header is found at the starting offset
10773 *
10774 * @internal
10775 *
10776 * @group ID3
10777 */
10778function getId3Data(data, offset) {
10779  const front = offset;
10780  let length = 0;
10781  while (isId3Header(data, offset)) {
10782    // ID3 header is 10 bytes
10783    length += 10;
10784    const size = readId3Size(data, offset + 6);
10785    length += size;
10786    if (isId3Footer(data, offset + 10)) {
10787      // ID3 footer is 10 bytes
10788      length += 10;
10789    }
10790    offset += length;
10791  }
10792  if (length > 0) {
10793    return data.subarray(front, front + length);
10794  }
10795  return undefined;
10796}
10797
10798function getAudioConfig(observer, data, offset, manifestCodec) {
10799  const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
10800  const byte2 = data[offset + 2];
10801  const adtsSamplingIndex = byte2 >> 2 & 0xf;
10802  if (adtsSamplingIndex > 12) {
10803    const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
10804    observer.emit(Events.ERROR, Events.ERROR, {
10805      type: ErrorTypes.MEDIA_ERROR,
10806      details: ErrorDetails.FRAG_PARSING_ERROR,
10807      fatal: true,
10808      error,
10809      reason: error.message
10810    });
10811    return;
10812  }
10813  // MPEG-4 Audio Object Type (profile_ObjectType+1)
10814  const adtsObjectType = (byte2 >> 6 & 0x3) + 1;
10815  const channelCount = data[offset + 3] >> 6 & 0x3 | (byte2 & 1) << 2;
10816  const codec = 'mp4a.40.' + adtsObjectType;
10817  /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
10818      ISO/IEC 14496-3 - Table 1.13 — Syntax of AudioSpecificConfig()
10819    Audio Profile / Audio Object Type
10820    0: Null
10821    1: AAC Main
10822    2: AAC LC (Low Complexity)
10823    3: AAC SSR (Scalable Sample Rate)
10824    4: AAC LTP (Long Term Prediction)
10825    5: SBR (Spectral Band Replication)
10826    6: AAC Scalable
10827   sampling freq
10828    0: 96000 Hz
10829    1: 88200 Hz
10830    2: 64000 Hz
10831    3: 48000 Hz
10832    4: 44100 Hz
10833    5: 32000 Hz
10834    6: 24000 Hz
10835    7: 22050 Hz
10836    8: 16000 Hz
10837    9: 12000 Hz
10838    10: 11025 Hz
10839    11: 8000 Hz
10840    12: 7350 Hz
10841    13: Reserved
10842    14: Reserved
10843    15: frequency is written explictly
10844    Channel Configurations
10845    These are the channel configurations:
10846    0: Defined in AOT Specifc Config
10847    1: 1 channel: front-center
10848    2: 2 channels: front-left, front-right
10849  */
10850  // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
10851  const samplerate = adtsSamplingRates[adtsSamplingIndex];
10852  let aacSampleIndex = adtsSamplingIndex;
10853  if (adtsObjectType === 5 || adtsObjectType === 29) {
10854    // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
10855    // there is a factor 2 between frame sample rate and output sample rate
10856    // multiply frequency by 2 (see table above, equivalent to substract 3)
10857    aacSampleIndex -= 3;
10858  }
10859  const config = [adtsObjectType << 3 | (aacSampleIndex & 0x0e) >> 1, (aacSampleIndex & 0x01) << 7 | channelCount << 3];
10860  logger.log(`manifest codec:${manifestCodec}, parsed codec:${codec}, channels:${channelCount}, rate:${samplerate} (ADTS object type:${adtsObjectType} sampling index:${adtsSamplingIndex})`);
10861  return {
10862    config,
10863    samplerate,
10864    channelCount,
10865    codec,
10866    parsedCodec: codec,
10867    manifestCodec
10868  };
10869}
10870function isHeaderPattern$1(data, offset) {
10871  return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0;
10872}
10873function getHeaderLength(data, offset) {
10874  return data[offset + 1] & 0x01 ? 7 : 9;
10875}
10876function getFullFrameLength(data, offset) {
10877  return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xe0) >>> 5;
10878}
10879function canGetFrameLength(data, offset) {
10880  return offset + 5 < data.length;
10881}
10882function isHeader$1(data, offset) {
10883  // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
10884  // Layer bits (position 14 and 15) in header should be always 0 for ADTS
10885  // More info https://wiki.multimedia.cx/index.php?title=ADTS
10886  return offset + 1 < data.length && isHeaderPattern$1(data, offset);
10887}
10888function canParse$1(data, offset) {
10889  return canGetFrameLength(data, offset) && isHeaderPattern$1(data, offset) && getFullFrameLength(data, offset) <= data.length - offset;
10890}
10891function probe$1(data, offset) {
10892  // same as isHeader but we also check that ADTS frame follows last ADTS frame
10893  // or end of data is reached
10894  if (isHeader$1(data, offset)) {
10895    // ADTS header Length
10896    const headerLength = getHeaderLength(data, offset);
10897    if (offset + headerLength >= data.length) {
10898      return false;
10899    }
10900    // ADTS frame Length
10901    const frameLength = getFullFrameLength(data, offset);
10902    if (frameLength <= headerLength) {
10903      return false;
10904    }
10905    const newOffset = offset + frameLength;
10906    return newOffset === data.length || isHeader$1(data, newOffset);
10907  }
10908  return false;
10909}
10910function initTrackConfig(track, observer, data, offset, audioCodec) {
10911  if (!track.samplerate) {
10912    const config = getAudioConfig(observer, data, offset, audioCodec);
10913    if (!config) {
10914      return;
10915    }
10916    _extends(track, config);
10917  }
10918}
10919function getFrameDuration(samplerate) {
10920  return 1024 * 90000 / samplerate;
10921}
10922function parseFrameHeader(data, offset) {
10923  // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header
10924  const headerLength = getHeaderLength(data, offset);
10925  if (offset + headerLength <= data.length) {
10926    // retrieve frame size
10927    const frameLength = getFullFrameLength(data, offset) - headerLength;
10928    if (frameLength > 0) {
10929      // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`);
10930      return {
10931        headerLength,
10932        frameLength
10933      };
10934    }
10935  }
10936}
10937function appendFrame$2(track, data, offset, pts, frameIndex) {
10938  const frameDuration = getFrameDuration(track.samplerate);
10939  const stamp = pts + frameIndex * frameDuration;
10940  const header = parseFrameHeader(data, offset);
10941  let unit;
10942  if (header) {
10943    const {
10944      frameLength,
10945      headerLength
10946    } = header;
10947    const _length = headerLength + frameLength;
10948    const missing = Math.max(0, offset + _length - data.length);
10949    // logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`);
10950    if (missing) {
10951      unit = new Uint8Array(_length - headerLength);
10952      unit.set(data.subarray(offset + headerLength, data.length), 0);
10953    } else {
10954      unit = data.subarray(offset + headerLength, offset + _length);
10955    }
10956    const _sample = {
10957      unit,
10958      pts: stamp
10959    };
10960    if (!missing) {
10961      track.samples.push(_sample);
10962    }
10963    return {
10964      sample: _sample,
10965      length: _length,
10966      missing
10967    };
10968  }
10969  // overflow incomplete header
10970  const length = data.length - offset;
10971  unit = new Uint8Array(length);
10972  unit.set(data.subarray(offset, data.length), 0);
10973  const sample = {
10974    unit,
10975    pts: stamp
10976  };
10977  return {
10978    sample,
10979    length,
10980    missing: -1
10981  };
10982}
10983
10984/**
10985 * Checks if the given data contains an ID3 tag.
10986 *
10987 * @param data - The data to check
10988 * @param offset - The offset at which to start checking
10989 *
10990 * @returns `true` if an ID3 tag is found
10991 *
10992 * @group ID3
10993 *
10994 * @beta
10995 */
10996function canParseId3(data, offset) {
10997  return isId3Header(data, offset) && readId3Size(data, offset + 6) + 10 <= data.length - offset;
10998}
10999
11000function toArrayBuffer(view) {
11001  if (view instanceof ArrayBuffer) {
11002    return view;
11003  } else {
11004    if (view.byteOffset == 0 && view.byteLength == view.buffer.byteLength) {
11005      // This is a TypedArray over the whole buffer.
11006      return view.buffer;
11007    }
11008    // This is a 'view' on the buffer.  Create a new buffer that only contains
11009    // the data.  Note that since this isn't an ArrayBuffer, the 'new' call
11010    // will allocate a new buffer to hold the copy.
11011    return new Uint8Array(view).buffer;
11012  }
11013}
11014
11015function toUint8(data, offset = 0, length = Infinity) {
11016  return view(data, offset, length, Uint8Array);
11017}
11018function view(data, offset, length, Type) {
11019  const buffer = unsafeGetArrayBuffer(data);
11020  let bytesPerElement = 1;
11021  if ('BYTES_PER_ELEMENT' in Type) {
11022    bytesPerElement = Type.BYTES_PER_ELEMENT;
11023  }
11024  // Absolute end of the |data| view within |buffer|.
11025  const dataOffset = isArrayBufferView(data) ? data.byteOffset : 0;
11026  const dataEnd = (dataOffset + data.byteLength) / bytesPerElement;
11027  // Absolute start of the result within |buffer|.
11028  const rawStart = (dataOffset + offset) / bytesPerElement;
11029  const start = Math.floor(Math.max(0, Math.min(rawStart, dataEnd)));
11030  // Absolute end of the result within |buffer|.
11031  const end = Math.floor(Math.min(start + Math.max(length, 0), dataEnd));
11032  return new Type(buffer, start, end - start);
11033}
11034function unsafeGetArrayBuffer(view) {
11035  if (view instanceof ArrayBuffer) {
11036    return view;
11037  } else {
11038    return view.buffer;
11039  }
11040}
11041function isArrayBufferView(obj) {
11042  return obj && obj.buffer instanceof ArrayBuffer && obj.byteLength !== undefined && obj.byteOffset !== undefined;
11043}
11044
11045function decodeId3ImageFrame(frame) {
11046  const metadataFrame = {
11047    key: frame.type,
11048    description: '',
11049    data: '',
11050    mimeType: null,
11051    pictureType: null
11052  };
11053  const utf8Encoding = 0x03;
11054  if (frame.size < 2) {
11055    return undefined;
11056  }
11057  if (frame.data[0] !== utf8Encoding) {
11058    console.log('Ignore frame with unrecognized character ' + 'encoding');
11059    return undefined;
11060  }
11061  const mimeTypeEndIndex = frame.data.subarray(1).indexOf(0);
11062  if (mimeTypeEndIndex === -1) {
11063    return undefined;
11064  }
11065  const mimeType = utf8ArrayToStr(toUint8(frame.data, 1, mimeTypeEndIndex));
11066  const pictureType = frame.data[2 + mimeTypeEndIndex];
11067  const descriptionEndIndex = frame.data.subarray(3 + mimeTypeEndIndex).indexOf(0);
11068  if (descriptionEndIndex === -1) {
11069    return undefined;
11070  }
11071  const description = utf8ArrayToStr(toUint8(frame.data, 3 + mimeTypeEndIndex, descriptionEndIndex));
11072  let data;
11073  if (mimeType === '-->') {
11074    data = utf8ArrayToStr(toUint8(frame.data, 4 + mimeTypeEndIndex + descriptionEndIndex));
11075  } else {
11076    data = toArrayBuffer(frame.data.subarray(4 + mimeTypeEndIndex + descriptionEndIndex));
11077  }
11078  metadataFrame.mimeType = mimeType;
11079  metadataFrame.pictureType = pictureType;
11080  metadataFrame.description = description;
11081  metadataFrame.data = data;
11082  return metadataFrame;
11083}
11084
11085/**
11086 * Decode an ID3 PRIV frame.
11087 *
11088 * @param frame - the ID3 PRIV frame
11089 *
11090 * @returns The decoded ID3 PRIV frame
11091 *
11092 * @internal
11093 *
11094 * @group ID3
11095 */
11096function decodeId3PrivFrame(frame) {
11097  /*
11098  Format: <text string>\0<binary data>
11099  */
11100  if (frame.size < 2) {
11101    return undefined;
11102  }
11103  const owner = utf8ArrayToStr(frame.data, true);
11104  const privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
11105  return {
11106    key: frame.type,
11107    info: owner,
11108    data: privateData.buffer
11109  };
11110}
11111
11112/**
11113 * Decodes an ID3 text frame
11114 *
11115 * @param frame - the ID3 text frame
11116 *
11117 * @returns The decoded ID3 text frame
11118 *
11119 * @internal
11120 *
11121 * @group ID3
11122 */
11123function decodeId3TextFrame(frame) {
11124  if (frame.size < 2) {
11125    return undefined;
11126  }
11127  if (frame.type === 'TXXX') {
11128    /*
11129    Format:
11130    [0]   = {Text Encoding}
11131    [1-?] = {Description}\0{Value}
11132    */
11133    let index = 1;
11134    const description = utf8ArrayToStr(frame.data.subarray(index), true);
11135    index += description.length + 1;
11136    const value = utf8ArrayToStr(frame.data.subarray(index));
11137    return {
11138      key: frame.type,
11139      info: description,
11140      data: value
11141    };
11142  }
11143  /*
11144  Format:
11145  [0]   = {Text Encoding}
11146  [1-?] = {Value}
11147  */
11148  const text = utf8ArrayToStr(frame.data.subarray(1));
11149  return {
11150    key: frame.type,
11151    info: '',
11152    data: text
11153  };
11154}
11155
11156/**
11157 * Decode a URL frame
11158 *
11159 * @param frame - the ID3 URL frame
11160 *
11161 * @returns The decoded ID3 URL frame
11162 *
11163 * @internal
11164 *
11165 * @group ID3
11166 */
11167function decodeId3UrlFrame(frame) {
11168  if (frame.type === 'WXXX') {
11169    /*
11170    Format:
11171    [0]   = {Text Encoding}
11172    [1-?] = {Description}\0{URL}
11173    */
11174    if (frame.size < 2) {
11175      return undefined;
11176    }
11177    let index = 1;
11178    const description = utf8ArrayToStr(frame.data.subarray(index), true);
11179    index += description.length + 1;
11180    const value = utf8ArrayToStr(frame.data.subarray(index));
11181    return {
11182      key: frame.type,
11183      info: description,
11184      data: value
11185    };
11186  }
11187  /*
11188  Format:
11189  [0-?] = {URL}
11190  */
11191  const url = utf8ArrayToStr(frame.data);
11192  return {
11193    key: frame.type,
11194    info: '',
11195    data: url
11196  };
11197}
11198
11199/**
11200 * Decode an ID3 frame.
11201 *
11202 * @param frame - the ID3 frame
11203 *
11204 * @returns The decoded ID3 frame
11205 *
11206 * @internal
11207 *
11208 * @group ID3
11209 */
11210function decodeId3Frame(frame) {
11211  if (frame.type === 'PRIV') {
11212    return decodeId3PrivFrame(frame);
11213  } else if (frame.type[0] === 'W') {
11214    return decodeId3UrlFrame(frame);
11215  } else if (frame.type === 'APIC') {
11216    return decodeId3ImageFrame(frame);
11217  }
11218  return decodeId3TextFrame(frame);
11219}
11220
11221/**
11222 * Returns the data of an ID3 frame.
11223 *
11224 * @param data - The data to read from
11225 *
11226 * @returns The data of the ID3 frame
11227 *
11228 * @internal
11229 *
11230 * @group ID3
11231 */
11232function getId3FrameData(data) {
11233  /*
11234  Frame ID       $xx xx xx xx (four characters)
11235  Size           $xx xx xx xx
11236  Flags          $xx xx
11237  */
11238  const type = String.fromCharCode(data[0], data[1], data[2], data[3]);
11239  const size = readId3Size(data, 4);
11240  // skip frame id, size, and flags
11241  const offset = 10;
11242  return {
11243    type,
11244    size,
11245    data: data.subarray(offset, offset + size)
11246  };
11247}
11248
11249const HEADER_FOOTER_SIZE = 10;
11250const FRAME_SIZE = 10;
11251/**
11252 * Returns an array of ID3 frames found in all the ID3 tags in the id3Data
11253 *
11254 * @param id3Data - The ID3 data containing one or more ID3 tags
11255 *
11256 * @returns Array of ID3 frame objects
11257 *
11258 * @group ID3
11259 *
11260 * @beta
11261 */
11262function getId3Frames(id3Data) {
11263  let offset = 0;
11264  const frames = [];
11265  while (isId3Header(id3Data, offset)) {
11266    const size = readId3Size(id3Data, offset + 6);
11267    if (id3Data[offset + 5] >> 6 & 1) {
11268      // skip extended header
11269      offset += HEADER_FOOTER_SIZE;
11270    }
11271    // skip past ID3 header
11272    offset += HEADER_FOOTER_SIZE;
11273    const end = offset + size;
11274    // loop through frames in the ID3 tag
11275    while (offset + FRAME_SIZE < end) {
11276      const frameData = getId3FrameData(id3Data.subarray(offset));
11277      const frame = decodeId3Frame(frameData);
11278      if (frame) {
11279        frames.push(frame);
11280      }
11281      // skip frame header and frame data
11282      offset += frameData.size + HEADER_FOOTER_SIZE;
11283    }
11284    if (isId3Footer(id3Data, offset)) {
11285      offset += HEADER_FOOTER_SIZE;
11286    }
11287  }
11288  return frames;
11289}
11290
11291/**
11292 * Returns true if the ID3 frame is an Elementary Stream timestamp frame
11293 *
11294 * @param frame - the ID3 frame
11295 *
11296 * @returns `true` if the ID3 frame is an Elementary Stream timestamp frame
11297 *
11298 * @internal
11299 *
11300 * @group ID3
11301 */
11302function isId3TimestampFrame(frame) {
11303  return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp';
11304}
11305
11306/**
11307 * Read a 33 bit timestamp from an ID3 frame.
11308 *
11309 * @param timeStampFrame - the ID3 frame
11310 *
11311 * @returns The timestamp
11312 *
11313 * @internal
11314 *
11315 * @group ID3
11316 */
11317function readId3Timestamp(timeStampFrame) {
11318  if (timeStampFrame.data.byteLength === 8) {
11319    const data = new Uint8Array(timeStampFrame.data);
11320    // timestamp is 33 bit expressed as a big-endian eight-octet number,
11321    // with the upper 31 bits set to zero.
11322    const pts33Bit = data[3] & 0x1;
11323    let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
11324    timestamp /= 45;
11325    if (pts33Bit) {
11326      timestamp += 47721858.84;
11327    } // 2^32 / 90
11328    return Math.round(timestamp);
11329  }
11330  return undefined;
11331}
11332
11333/**
11334 * Searches for the Elementary Stream timestamp found in the ID3 data chunk
11335 *
11336 * @param data - Block of data containing one or more ID3 tags
11337 *
11338 * @returns The timestamp
11339 *
11340 * @group ID3
11341 *
11342 * @beta
11343 */
11344function getId3Timestamp(data) {
11345  const frames = getId3Frames(data);
11346  for (let i = 0; i < frames.length; i++) {
11347    const frame = frames[i];
11348    if (isId3TimestampFrame(frame)) {
11349      return readId3Timestamp(frame);
11350    }
11351  }
11352  return undefined;
11353}
11354
11355let MetadataSchema = /*#__PURE__*/function (MetadataSchema) {
11356  MetadataSchema["audioId3"] = "org.id3";
11357  MetadataSchema["dateRange"] = "com.apple.quicktime.HLS";
11358  MetadataSchema["emsg"] = "https://aomedia.org/emsg/ID3";
11359  MetadataSchema["misbklv"] = "urn:misb:KLV:bin:1910.1";
11360  return MetadataSchema;
11361}({});
11362
11363function dummyTrack(type = '', inputTimeScale = 90000) {
11364  return {
11365    type,
11366    id: -1,
11367    pid: -1,
11368    inputTimeScale,
11369    sequenceNumber: -1,
11370    samples: [],
11371    dropped: 0
11372  };
11373}
11374
11375class BaseAudioDemuxer {
11376  constructor() {
11377    this._audioTrack = void 0;
11378    this._id3Track = void 0;
11379    this.frameIndex = 0;
11380    this.cachedData = null;
11381    this.basePTS = null;
11382    this.initPTS = null;
11383    this.lastPTS = null;
11384  }
11385  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11386    this._id3Track = {
11387      type: 'id3',
11388      id: 3,
11389      pid: -1,
11390      inputTimeScale: 90000,
11391      sequenceNumber: 0,
11392      samples: [],
11393      dropped: 0
11394    };
11395  }
11396  resetTimeStamp(deaultTimestamp) {
11397    this.initPTS = deaultTimestamp;
11398    this.resetContiguity();
11399  }
11400  resetContiguity() {
11401    this.basePTS = null;
11402    this.lastPTS = null;
11403    this.frameIndex = 0;
11404  }
11405  canParse(data, offset) {
11406    return false;
11407  }
11408  appendFrame(track, data, offset) {}
11409
11410  // feed incoming data to the front of the parsing pipeline
11411  demux(data, timeOffset) {
11412    if (this.cachedData) {
11413      data = appendUint8Array(this.cachedData, data);
11414      this.cachedData = null;
11415    }
11416    let id3Data = getId3Data(data, 0);
11417    let offset = id3Data ? id3Data.length : 0;
11418    let lastDataIndex;
11419    const track = this._audioTrack;
11420    const id3Track = this._id3Track;
11421    const timestamp = id3Data ? getId3Timestamp(id3Data) : undefined;
11422    const length = data.length;
11423    if (this.basePTS === null || this.frameIndex === 0 && isFiniteNumber(timestamp)) {
11424      this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS);
11425      this.lastPTS = this.basePTS;
11426    }
11427    if (this.lastPTS === null) {
11428      this.lastPTS = this.basePTS;
11429    }
11430
11431    // more expressive than alternative: id3Data?.length
11432    if (id3Data && id3Data.length > 0) {
11433      id3Track.samples.push({
11434        pts: this.lastPTS,
11435        dts: this.lastPTS,
11436        data: id3Data,
11437        type: MetadataSchema.audioId3,
11438        duration: Number.POSITIVE_INFINITY
11439      });
11440    }
11441    while (offset < length) {
11442      if (this.canParse(data, offset)) {
11443        const frame = this.appendFrame(track, data, offset);
11444        if (frame) {
11445          this.frameIndex++;
11446          this.lastPTS = frame.sample.pts;
11447          offset += frame.length;
11448          lastDataIndex = offset;
11449        } else {
11450          offset = length;
11451        }
11452      } else if (canParseId3(data, offset)) {
11453        // after a canParse, a call to getId3Data *should* always returns some data
11454        id3Data = getId3Data(data, offset);
11455        id3Track.samples.push({
11456          pts: this.lastPTS,
11457          dts: this.lastPTS,
11458          data: id3Data,
11459          type: MetadataSchema.audioId3,
11460          duration: Number.POSITIVE_INFINITY
11461        });
11462        offset += id3Data.length;
11463        lastDataIndex = offset;
11464      } else {
11465        offset++;
11466      }
11467      if (offset === length && lastDataIndex !== length) {
11468        const partialData = data.slice(lastDataIndex);
11469        if (this.cachedData) {
11470          this.cachedData = appendUint8Array(this.cachedData, partialData);
11471        } else {
11472          this.cachedData = partialData;
11473        }
11474      }
11475    }
11476    return {
11477      audioTrack: track,
11478      videoTrack: dummyTrack(),
11479      id3Track,
11480      textTrack: dummyTrack()
11481    };
11482  }
11483  demuxSampleAes(data, keyData, timeOffset) {
11484    return Promise.reject(new Error(`[${this}] This demuxer does not support Sample-AES decryption`));
11485  }
11486  flush(timeOffset) {
11487    // Parse cache in case of remaining frames.
11488    const cachedData = this.cachedData;
11489    if (cachedData) {
11490      this.cachedData = null;
11491      this.demux(cachedData, 0);
11492    }
11493    return {
11494      audioTrack: this._audioTrack,
11495      videoTrack: dummyTrack(),
11496      id3Track: this._id3Track,
11497      textTrack: dummyTrack()
11498    };
11499  }
11500  destroy() {
11501    this.cachedData = null;
11502    // @ts-ignore
11503    this._audioTrack = this._id3Track = undefined;
11504  }
11505}
11506
11507/**
11508 * Initialize PTS
11509 * <p>
11510 *    use timestamp unless it is undefined, NaN or Infinity
11511 * </p>
11512 */
11513const initPTSFn = (timestamp, timeOffset, initPTS) => {
11514  if (isFiniteNumber(timestamp)) {
11515    return timestamp * 90;
11516  }
11517  const init90kHz = initPTS ? initPTS.baseTime * 90000 / initPTS.timescale : 0;
11518  return timeOffset * 90000 + init90kHz;
11519};
11520
11521/**
11522 *  MPEG parser helper
11523 */
11524
11525let chromeVersion$1 = null;
11526const 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];
11527const SamplingRateMap = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000];
11528const SamplesCoefficients = [
11529// MPEG 2.5
11530[0,
11531// Reserved
1153272,
11533// Layer3
11534144,
11535// Layer2
1153612 // Layer1
11537],
11538// Reserved
11539[0,
11540// Reserved
115410,
11542// Layer3
115430,
11544// Layer2
115450 // Layer1
11546],
11547// MPEG 2
11548[0,
11549// Reserved
1155072,
11551// Layer3
11552144,
11553// Layer2
1155412 // Layer1
11555],
11556// MPEG 1
11557[0,
11558// Reserved
11559144,
11560// Layer3
11561144,
11562// Layer2
1156312 // Layer1
11564]];
11565const BytesInSlot = [0,
11566// Reserved
115671,
11568// Layer3
115691,
11570// Layer2
115714 // Layer1
11572];
11573function appendFrame$1(track, data, offset, pts, frameIndex) {
11574  // Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference
11575  if (offset + 24 > data.length) {
11576    return;
11577  }
11578  const header = parseHeader(data, offset);
11579  if (header && offset + header.frameLength <= data.length) {
11580    const frameDuration = header.samplesPerFrame * 90000 / header.sampleRate;
11581    const stamp = pts + frameIndex * frameDuration;
11582    const sample = {
11583      unit: data.subarray(offset, offset + header.frameLength),
11584      pts: stamp,
11585      dts: stamp
11586    };
11587    track.config = [];
11588    track.channelCount = header.channelCount;
11589    track.samplerate = header.sampleRate;
11590    track.samples.push(sample);
11591    return {
11592      sample,
11593      length: header.frameLength,
11594      missing: 0
11595    };
11596  }
11597}
11598function parseHeader(data, offset) {
11599  const mpegVersion = data[offset + 1] >> 3 & 3;
11600  const mpegLayer = data[offset + 1] >> 1 & 3;
11601  const bitRateIndex = data[offset + 2] >> 4 & 15;
11602  const sampleRateIndex = data[offset + 2] >> 2 & 3;
11603  if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) {
11604    const paddingBit = data[offset + 2] >> 1 & 1;
11605    const channelMode = data[offset + 3] >> 6;
11606    const columnInBitrates = mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4;
11607    const bitRate = BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000;
11608    const columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2;
11609    const sampleRate = SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex];
11610    const channelCount = channelMode === 3 ? 1 : 2; // If bits of channel mode are `11` then it is a single channel (Mono)
11611    const sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer];
11612    const bytesInSlot = BytesInSlot[mpegLayer];
11613    const samplesPerFrame = sampleCoefficient * 8 * bytesInSlot;
11614    const frameLength = Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot;
11615    if (chromeVersion$1 === null) {
11616      const userAgent = navigator.userAgent || '';
11617      const result = userAgent.match(/Chrome\/(\d+)/i);
11618      chromeVersion$1 = result ? parseInt(result[1]) : 0;
11619    }
11620    const needChromeFix = !!chromeVersion$1 && chromeVersion$1 <= 87;
11621    if (needChromeFix && mpegLayer === 2 && bitRate >= 224000 && channelMode === 0) {
11622      // Work around bug in Chromium by setting channelMode to dual-channel (01) instead of stereo (00)
11623      data[offset + 3] = data[offset + 3] | 0x80;
11624    }
11625    return {
11626      sampleRate,
11627      channelCount,
11628      frameLength,
11629      samplesPerFrame
11630    };
11631  }
11632}
11633function isHeaderPattern(data, offset) {
11634  return data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00;
11635}
11636function isHeader(data, offset) {
11637  // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
11638  // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
11639  // More info http://www.mp3-tech.org/programmer/frame_header.html
11640  return offset + 1 < data.length && isHeaderPattern(data, offset);
11641}
11642function canParse(data, offset) {
11643  const headerSize = 4;
11644  return isHeaderPattern(data, offset) && headerSize <= data.length - offset;
11645}
11646function probe(data, offset) {
11647  // same as isHeader but we also check that MPEG frame follows last MPEG frame
11648  // or end of data is reached
11649  if (offset + 1 < data.length && isHeaderPattern(data, offset)) {
11650    // MPEG header Length
11651    const headerLength = 4;
11652    // MPEG frame Length
11653    const header = parseHeader(data, offset);
11654    let frameLength = headerLength;
11655    if (header != null && header.frameLength) {
11656      frameLength = header.frameLength;
11657    }
11658    const newOffset = offset + frameLength;
11659    return newOffset === data.length || isHeader(data, newOffset);
11660  }
11661  return false;
11662}
11663
11664/**
11665 * AAC demuxer
11666 */
11667class AACDemuxer extends BaseAudioDemuxer {
11668  constructor(observer, config) {
11669    super();
11670    this.observer = void 0;
11671    this.config = void 0;
11672    this.observer = observer;
11673    this.config = config;
11674  }
11675  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11676    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
11677    this._audioTrack = {
11678      container: 'audio/adts',
11679      type: 'audio',
11680      id: 2,
11681      pid: -1,
11682      sequenceNumber: 0,
11683      segmentCodec: 'aac',
11684      samples: [],
11685      manifestCodec: audioCodec,
11686      duration: trackDuration,
11687      inputTimeScale: 90000,
11688      dropped: 0
11689    };
11690  }
11691
11692  // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS
11693  static probe(data, logger) {
11694    if (!data) {
11695      return false;
11696    }
11697
11698    // Check for the ADTS sync word
11699    // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1
11700    // Layer bits (position 14 and 15) in header should be always 0 for ADTS
11701    // More info https://wiki.multimedia.cx/index.php?title=ADTS
11702    const id3Data = getId3Data(data, 0);
11703    let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
11704    if (probe(data, offset)) {
11705      return false;
11706    }
11707    for (let length = data.length; offset < length; offset++) {
11708      if (probe$1(data, offset)) {
11709        logger.log('ADTS sync word found !');
11710        return true;
11711      }
11712    }
11713    return false;
11714  }
11715  canParse(data, offset) {
11716    return canParse$1(data, offset);
11717  }
11718  appendFrame(track, data, offset) {
11719    initTrackConfig(track, this.observer, data, offset, track.manifestCodec);
11720    const frame = appendFrame$2(track, data, offset, this.basePTS, this.frameIndex);
11721    if (frame && frame.missing === 0) {
11722      return frame;
11723    }
11724  }
11725}
11726
11727const getAudioBSID = (data, offset) => {
11728  // check the bsid to confirm ac-3 | ec-3
11729  let bsid = 0;
11730  let numBits = 5;
11731  offset += numBits;
11732  const temp = new Uint32Array(1); // unsigned 32 bit for temporary storage
11733  const mask = new Uint32Array(1); // unsigned 32 bit mask value
11734  const byte = new Uint8Array(1); // unsigned 8 bit for temporary storage
11735  while (numBits > 0) {
11736    byte[0] = data[offset];
11737    // read remaining bits, upto 8 bits at a time
11738    const bits = Math.min(numBits, 8);
11739    const shift = 8 - bits;
11740    mask[0] = 0xff000000 >>> 24 + shift << shift;
11741    temp[0] = (byte[0] & mask[0]) >> shift;
11742    bsid = !bsid ? temp[0] : bsid << bits | temp[0];
11743    offset += 1;
11744    numBits -= bits;
11745  }
11746  return bsid;
11747};
11748
11749class AC3Demuxer extends BaseAudioDemuxer {
11750  constructor(observer) {
11751    super();
11752    this.observer = void 0;
11753    this.observer = observer;
11754  }
11755  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11756    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
11757    this._audioTrack = {
11758      container: 'audio/ac-3',
11759      type: 'audio',
11760      id: 2,
11761      pid: -1,
11762      sequenceNumber: 0,
11763      segmentCodec: 'ac3',
11764      samples: [],
11765      manifestCodec: audioCodec,
11766      duration: trackDuration,
11767      inputTimeScale: 90000,
11768      dropped: 0
11769    };
11770  }
11771  canParse(data, offset) {
11772    return offset + 64 < data.length;
11773  }
11774  appendFrame(track, data, offset) {
11775    const frameLength = appendFrame(track, data, offset, this.basePTS, this.frameIndex);
11776    if (frameLength !== -1) {
11777      const sample = track.samples[track.samples.length - 1];
11778      return {
11779        sample,
11780        length: frameLength,
11781        missing: 0
11782      };
11783    }
11784  }
11785  static probe(data) {
11786    if (!data) {
11787      return false;
11788    }
11789    const id3Data = getId3Data(data, 0);
11790    if (!id3Data) {
11791      return false;
11792    }
11793
11794    // look for the ac-3 sync bytes
11795    const offset = id3Data.length;
11796    if (data[offset] === 0x0b && data[offset + 1] === 0x77 && getId3Timestamp(id3Data) !== undefined &&
11797    // check the bsid to confirm ac-3
11798    getAudioBSID(data, offset) < 16) {
11799      return true;
11800    }
11801    return false;
11802  }
11803}
11804function appendFrame(track, data, start, pts, frameIndex) {
11805  if (start + 8 > data.length) {
11806    return -1; // not enough bytes left
11807  }
11808  if (data[start] !== 0x0b || data[start + 1] !== 0x77) {
11809    return -1; // invalid magic
11810  }
11811
11812  // get sample rate
11813  const samplingRateCode = data[start + 4] >> 6;
11814  if (samplingRateCode >= 3) {
11815    return -1; // invalid sampling rate
11816  }
11817  const samplingRateMap = [48000, 44100, 32000];
11818  const sampleRate = samplingRateMap[samplingRateCode];
11819
11820  // get frame size
11821  const frameSizeCode = data[start + 4] & 0x3f;
11822  const frameSizeMap = [64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105, 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174, 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244, 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417, 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558, 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, 975, 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, 1728, 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920];
11823  const frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2;
11824  if (start + frameLength > data.length) {
11825    return -1;
11826  }
11827
11828  // get channel count
11829  const channelMode = data[start + 6] >> 5;
11830  let skipCount = 0;
11831  if (channelMode === 2) {
11832    skipCount += 2;
11833  } else {
11834    if (channelMode & 1 && channelMode !== 1) {
11835      skipCount += 2;
11836    }
11837    if (channelMode & 4) {
11838      skipCount += 2;
11839    }
11840  }
11841  const lfeon = (data[start + 6] << 8 | data[start + 7]) >> 12 - skipCount & 1;
11842  const channelsMap = [2, 1, 2, 3, 3, 4, 4, 5];
11843  const channelCount = channelsMap[channelMode] + lfeon;
11844
11845  // build dac3 box
11846  const bsid = data[start + 5] >> 3;
11847  const bsmod = data[start + 5] & 7;
11848  const config = new Uint8Array([samplingRateCode << 6 | bsid << 1 | bsmod >> 2, (bsmod & 3) << 6 | channelMode << 3 | lfeon << 2 | frameSizeCode >> 4, frameSizeCode << 4 & 0xe0]);
11849  const frameDuration = 1536 / sampleRate * 90000;
11850  const stamp = pts + frameIndex * frameDuration;
11851  const unit = data.subarray(start, start + frameLength);
11852  track.config = config;
11853  track.channelCount = channelCount;
11854  track.samplerate = sampleRate;
11855  track.samples.push({
11856    unit,
11857    pts: stamp
11858  });
11859  return frameLength;
11860}
11861
11862/**
11863 * MP3 demuxer
11864 */
11865class MP3Demuxer extends BaseAudioDemuxer {
11866  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11867    super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration);
11868    this._audioTrack = {
11869      container: 'audio/mpeg',
11870      type: 'audio',
11871      id: 2,
11872      pid: -1,
11873      sequenceNumber: 0,
11874      segmentCodec: 'mp3',
11875      samples: [],
11876      manifestCodec: audioCodec,
11877      duration: trackDuration,
11878      inputTimeScale: 90000,
11879      dropped: 0
11880    };
11881  }
11882  static probe(data) {
11883    if (!data) {
11884      return false;
11885    }
11886
11887    // check if data contains ID3 timestamp and MPEG sync word
11888    // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
11889    // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
11890    // More info http://www.mp3-tech.org/programmer/frame_header.html
11891    const id3Data = getId3Data(data, 0);
11892    let offset = (id3Data == null ? void 0 : id3Data.length) || 0;
11893
11894    // Check for ac-3|ec-3 sync bytes and return false if present
11895    if (id3Data && data[offset] === 0x0b && data[offset + 1] === 0x77 && getId3Timestamp(id3Data) !== undefined &&
11896    // check the bsid to confirm ac-3 or ec-3 (not mp3)
11897    getAudioBSID(data, offset) <= 16) {
11898      return false;
11899    }
11900    for (let length = data.length; offset < length; offset++) {
11901      if (probe(data, offset)) {
11902        logger.log('MPEG Audio sync word found !');
11903        return true;
11904      }
11905    }
11906    return false;
11907  }
11908  canParse(data, offset) {
11909    return canParse(data, offset);
11910  }
11911  appendFrame(track, data, offset) {
11912    if (this.basePTS === null) {
11913      return;
11914    }
11915    return appendFrame$1(track, data, offset, this.basePTS, this.frameIndex);
11916  }
11917}
11918
11919const emsgSchemePattern = /\/emsg[-/]ID3/i;
11920class MP4Demuxer {
11921  constructor(observer, config) {
11922    this.remainderData = null;
11923    this.timeOffset = 0;
11924    this.config = void 0;
11925    this.videoTrack = void 0;
11926    this.audioTrack = void 0;
11927    this.id3Track = void 0;
11928    this.txtTrack = void 0;
11929    this.config = config;
11930  }
11931  resetTimeStamp() {}
11932  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
11933    const videoTrack = this.videoTrack = dummyTrack('video', 1);
11934    const audioTrack = this.audioTrack = dummyTrack('audio', 1);
11935    const captionTrack = this.txtTrack = dummyTrack('text', 1);
11936    this.id3Track = dummyTrack('id3', 1);
11937    this.timeOffset = 0;
11938    if (!(initSegment != null && initSegment.byteLength)) {
11939      return;
11940    }
11941    const initData = parseInitSegment(initSegment);
11942    if (initData.video) {
11943      const {
11944        id,
11945        timescale,
11946        codec,
11947        supplemental
11948      } = initData.video;
11949      videoTrack.id = id;
11950      videoTrack.timescale = captionTrack.timescale = timescale;
11951      videoTrack.codec = codec;
11952      videoTrack.supplemental = supplemental;
11953    }
11954    if (initData.audio) {
11955      const {
11956        id,
11957        timescale,
11958        codec
11959      } = initData.audio;
11960      audioTrack.id = id;
11961      audioTrack.timescale = timescale;
11962      audioTrack.codec = codec;
11963    }
11964    captionTrack.id = RemuxerTrackIdConfig.text;
11965    videoTrack.sampleDuration = 0;
11966    videoTrack.duration = audioTrack.duration = trackDuration;
11967  }
11968  resetContiguity() {
11969    this.remainderData = null;
11970  }
11971  static probe(data) {
11972    return hasMoofData(data);
11973  }
11974  demux(data, timeOffset) {
11975    this.timeOffset = timeOffset;
11976    // 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
11977    let videoSamples = data;
11978    const videoTrack = this.videoTrack;
11979    const textTrack = this.txtTrack;
11980    if (this.config.progressive) {
11981      // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else.
11982      // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee
11983      // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception.
11984      if (this.remainderData) {
11985        videoSamples = appendUint8Array(this.remainderData, data);
11986      }
11987      const segmentedData = segmentValidRange(videoSamples);
11988      this.remainderData = segmentedData.remainder;
11989      videoTrack.samples = segmentedData.valid || new Uint8Array();
11990    } else {
11991      videoTrack.samples = videoSamples;
11992    }
11993    const id3Track = this.extractID3Track(videoTrack, timeOffset);
11994    textTrack.samples = parseSamples(timeOffset, videoTrack);
11995    return {
11996      videoTrack,
11997      audioTrack: this.audioTrack,
11998      id3Track,
11999      textTrack: this.txtTrack
12000    };
12001  }
12002  flush() {
12003    const timeOffset = this.timeOffset;
12004    const videoTrack = this.videoTrack;
12005    const textTrack = this.txtTrack;
12006    videoTrack.samples = this.remainderData || new Uint8Array();
12007    this.remainderData = null;
12008    const id3Track = this.extractID3Track(videoTrack, this.timeOffset);
12009    textTrack.samples = parseSamples(timeOffset, videoTrack);
12010    return {
12011      videoTrack,
12012      audioTrack: dummyTrack(),
12013      id3Track,
12014      textTrack: dummyTrack()
12015    };
12016  }
12017  extractID3Track(videoTrack, timeOffset) {
12018    const id3Track = this.id3Track;
12019    if (videoTrack.samples.length) {
12020      const emsgs = findBox(videoTrack.samples, ['emsg']);
12021      if (emsgs) {
12022        emsgs.forEach(data => {
12023          const emsgInfo = parseEmsg(data);
12024          if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) {
12025            const pts = getEmsgStartTime(emsgInfo, timeOffset);
12026            let duration = emsgInfo.eventDuration === 0xffffffff ? Number.POSITIVE_INFINITY : emsgInfo.eventDuration / emsgInfo.timeScale;
12027            // Safari takes anything <= 0.001 seconds and maps it to Infinity
12028            if (duration <= 0.001) {
12029              duration = Number.POSITIVE_INFINITY;
12030            }
12031            const payload = emsgInfo.payload;
12032            id3Track.samples.push({
12033              data: payload,
12034              len: payload.byteLength,
12035              dts: pts,
12036              pts: pts,
12037              type: MetadataSchema.emsg,
12038              duration: duration
12039            });
12040          } else if (this.config.enableEmsgKLVMetadata && emsgInfo.schemeIdUri.startsWith('urn:misb:KLV:bin:1910.1')) {
12041            const pts = getEmsgStartTime(emsgInfo, timeOffset);
12042            id3Track.samples.push({
12043              data: emsgInfo.payload,
12044              len: emsgInfo.payload.byteLength,
12045              dts: pts,
12046              pts: pts,
12047              type: MetadataSchema.misbklv,
12048              duration: Number.POSITIVE_INFINITY
12049            });
12050          }
12051        });
12052      }
12053    }
12054    return id3Track;
12055  }
12056  demuxSampleAes(data, keyData, timeOffset) {
12057    return Promise.reject(new Error('The MP4 demuxer does not support SAMPLE-AES decryption'));
12058  }
12059  destroy() {
12060    // @ts-ignore
12061    this.config = null;
12062    this.remainderData = null;
12063    this.videoTrack = this.audioTrack = this.id3Track = this.txtTrack = undefined;
12064  }
12065}
12066function getEmsgStartTime(emsgInfo, timeOffset) {
12067  return isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale : timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale;
12068}
12069
12070/**
12071 * SAMPLE-AES decrypter
12072 */
12073
12074class SampleAesDecrypter {
12075  constructor(observer, config, keyData) {
12076    this.keyData = void 0;
12077    this.decrypter = void 0;
12078    this.keyData = keyData;
12079    this.decrypter = new Decrypter(config, {
12080      removePKCS7Padding: false
12081    });
12082  }
12083  decryptBuffer(encryptedData) {
12084    return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
12085  }
12086
12087  // AAC - encrypt all full 16 bytes blocks starting from offset 16
12088  decryptAacSample(samples, sampleIndex, callback) {
12089    const curUnit = samples[sampleIndex].unit;
12090    if (curUnit.length <= 16) {
12091      // No encrypted portion in this sample (first 16 bytes is not
12092      // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html),
12093      return;
12094    }
12095    const encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16);
12096    const encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length);
12097    this.decryptBuffer(encryptedBuffer).then(decryptedBuffer => {
12098      const decryptedData = new Uint8Array(decryptedBuffer);
12099      curUnit.set(decryptedData, 16);
12100      if (!this.decrypter.isSync()) {
12101        this.decryptAacSamples(samples, sampleIndex + 1, callback);
12102      }
12103    }).catch(callback);
12104  }
12105  decryptAacSamples(samples, sampleIndex, callback) {
12106    for (;; sampleIndex++) {
12107      if (sampleIndex >= samples.length) {
12108        callback();
12109        return;
12110      }
12111      if (samples[sampleIndex].unit.length < 32) {
12112        continue;
12113      }
12114      this.decryptAacSample(samples, sampleIndex, callback);
12115      if (!this.decrypter.isSync()) {
12116        return;
12117      }
12118    }
12119  }
12120
12121  // AVC - encrypt one 16 bytes block out of ten, starting from offset 32
12122  getAvcEncryptedData(decodedData) {
12123    const encryptedDataLen = Math.floor((decodedData.length - 48) / 160) * 16 + 16;
12124    const encryptedData = new Int8Array(encryptedDataLen);
12125    let outputPos = 0;
12126    for (let inputPos = 32; inputPos < decodedData.length - 16; inputPos += 160, outputPos += 16) {
12127      encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos);
12128    }
12129    return encryptedData;
12130  }
12131  getAvcDecryptedUnit(decodedData, decryptedData) {
12132    const uint8DecryptedData = new Uint8Array(decryptedData);
12133    let inputPos = 0;
12134    for (let outputPos = 32; outputPos < decodedData.length - 16; outputPos += 160, inputPos += 16) {
12135      decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos);
12136    }
12137    return decodedData;
12138  }
12139  decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit) {
12140    const decodedData = discardEPB(curUnit.data);
12141    const encryptedData = this.getAvcEncryptedData(decodedData);
12142    this.decryptBuffer(encryptedData.buffer).then(decryptedBuffer => {
12143      curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer);
12144      if (!this.decrypter.isSync()) {
12145        this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback);
12146      }
12147    }).catch(callback);
12148  }
12149  decryptAvcSamples(samples, sampleIndex, unitIndex, callback) {
12150    if (samples instanceof Uint8Array) {
12151      throw new Error('Cannot decrypt samples of type Uint8Array');
12152    }
12153    for (;; sampleIndex++, unitIndex = 0) {
12154      if (sampleIndex >= samples.length) {
12155        callback();
12156        return;
12157      }
12158      const curUnits = samples[sampleIndex].units;
12159      for (;; unitIndex++) {
12160        if (unitIndex >= curUnits.length) {
12161          break;
12162        }
12163        const curUnit = curUnits[unitIndex];
12164        if (curUnit.data.length <= 48 || curUnit.type !== 1 && curUnit.type !== 5) {
12165          continue;
12166        }
12167        this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit);
12168        if (!this.decrypter.isSync()) {
12169          return;
12170        }
12171      }
12172    }
12173  }
12174}
12175
12176class BaseVideoParser {
12177  constructor() {
12178    this.VideoSample = null;
12179  }
12180  createVideoSample(key, pts, dts) {
12181    return {
12182      key,
12183      frame: false,
12184      pts,
12185      dts,
12186      units: [],
12187      length: 0
12188    };
12189  }
12190  getLastNalUnit(samples) {
12191    var _VideoSample;
12192    let VideoSample = this.VideoSample;
12193    let lastUnit;
12194    // try to fallback to previous sample if current one is empty
12195    if (!VideoSample || VideoSample.units.length === 0) {
12196      VideoSample = samples[samples.length - 1];
12197    }
12198    if ((_VideoSample = VideoSample) != null && _VideoSample.units) {
12199      const units = VideoSample.units;
12200      lastUnit = units[units.length - 1];
12201    }
12202    return lastUnit;
12203  }
12204  pushAccessUnit(VideoSample, videoTrack) {
12205    if (VideoSample.units.length && VideoSample.frame) {
12206      // if sample does not have PTS/DTS, patch with last sample PTS/DTS
12207      if (VideoSample.pts === undefined) {
12208        const samples = videoTrack.samples;
12209        const nbSamples = samples.length;
12210        if (nbSamples) {
12211          const lastSample = samples[nbSamples - 1];
12212          VideoSample.pts = lastSample.pts;
12213          VideoSample.dts = lastSample.dts;
12214        } else {
12215          // dropping samples, no timestamp found
12216          videoTrack.dropped++;
12217          return;
12218        }
12219      }
12220      videoTrack.samples.push(VideoSample);
12221    }
12222  }
12223  parseNALu(track, array, endOfSegment) {
12224    const len = array.byteLength;
12225    let state = track.naluState || 0;
12226    const lastState = state;
12227    const units = [];
12228    let i = 0;
12229    let value;
12230    let overflow;
12231    let unitType;
12232    let lastUnitStart = -1;
12233    let lastUnitType = 0;
12234    // logger.log('PES:' + Hex.hexDump(array));
12235
12236    if (state === -1) {
12237      // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
12238      lastUnitStart = 0;
12239      // NALu type is value read from offset 0
12240      lastUnitType = this.getNALuType(array, 0);
12241      state = 0;
12242      i = 1;
12243    }
12244    while (i < len) {
12245      value = array[i++];
12246      // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
12247      if (!state) {
12248        state = value ? 0 : 1;
12249        continue;
12250      }
12251      if (state === 1) {
12252        state = value ? 0 : 2;
12253        continue;
12254      }
12255      // here we have state either equal to 2 or 3
12256      if (!value) {
12257        state = 3;
12258      } else if (value === 1) {
12259        overflow = i - state - 1;
12260        if (lastUnitStart >= 0) {
12261          const unit = {
12262            data: array.subarray(lastUnitStart, overflow),
12263            type: lastUnitType
12264          };
12265          // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
12266          units.push(unit);
12267        } else {
12268          // lastUnitStart is undefined => this is the first start code found in this PES packet
12269          // first check if start code delimiter is overlapping between 2 PES packets,
12270          // ie it started in last packet (lastState not zero)
12271          // and ended at the beginning of this PES packet (i <= 4 - lastState)
12272          const lastUnit = this.getLastNalUnit(track.samples);
12273          if (lastUnit) {
12274            if (lastState && i <= 4 - lastState) {
12275              // start delimiter overlapping between PES packets
12276              // strip start delimiter bytes from the end of last NAL unit
12277              // check if lastUnit had a state different from zero
12278              if (lastUnit.state) {
12279                // strip last bytes
12280                lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
12281              }
12282            }
12283            // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
12284
12285            if (overflow > 0) {
12286              // logger.log('first NALU found with overflow:' + overflow);
12287              lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
12288              lastUnit.state = 0;
12289            }
12290          }
12291        }
12292        // check if we can read unit type
12293        if (i < len) {
12294          unitType = this.getNALuType(array, i);
12295          // logger.log('find NALU @ offset:' + i + ',type:' + unitType);
12296          lastUnitStart = i;
12297          lastUnitType = unitType;
12298          state = 0;
12299        } else {
12300          // not enough byte to read unit type. let's read it on next PES parsing
12301          state = -1;
12302        }
12303      } else {
12304        state = 0;
12305      }
12306    }
12307    if (lastUnitStart >= 0 && state >= 0) {
12308      const unit = {
12309        data: array.subarray(lastUnitStart, len),
12310        type: lastUnitType,
12311        state: state
12312      };
12313      units.push(unit);
12314      // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
12315    }
12316    // no NALu found
12317    if (units.length === 0) {
12318      // append pes.data to previous NAL unit
12319      const lastUnit = this.getLastNalUnit(track.samples);
12320      if (lastUnit) {
12321        lastUnit.data = appendUint8Array(lastUnit.data, array);
12322      }
12323    }
12324    track.naluState = state;
12325    return units;
12326  }
12327}
12328
12329/**
12330 * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
12331 */
12332
12333class ExpGolomb {
12334  constructor(data) {
12335    this.data = void 0;
12336    this.bytesAvailable = void 0;
12337    this.word = void 0;
12338    this.bitsAvailable = void 0;
12339    this.data = data;
12340    // the number of bytes left to examine in this.data
12341    this.bytesAvailable = data.byteLength;
12342    // the current word being examined
12343    this.word = 0; // :uint
12344    // the number of bits left to examine in the current word
12345    this.bitsAvailable = 0; // :uint
12346  }
12347
12348  // ():void
12349  loadWord() {
12350    const data = this.data;
12351    const bytesAvailable = this.bytesAvailable;
12352    const position = data.byteLength - bytesAvailable;
12353    const workingBytes = new Uint8Array(4);
12354    const availableBytes = Math.min(4, bytesAvailable);
12355    if (availableBytes === 0) {
12356      throw new Error('no bytes available');
12357    }
12358    workingBytes.set(data.subarray(position, position + availableBytes));
12359    this.word = new DataView(workingBytes.buffer).getUint32(0);
12360    // track the amount of this.data that has been processed
12361    this.bitsAvailable = availableBytes * 8;
12362    this.bytesAvailable -= availableBytes;
12363  }
12364
12365  // (count:int):void
12366  skipBits(count) {
12367    let skipBytes; // :int
12368    count = Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable);
12369    if (this.bitsAvailable > count) {
12370      this.word <<= count;
12371      this.bitsAvailable -= count;
12372    } else {
12373      count -= this.bitsAvailable;
12374      skipBytes = count >> 3;
12375      count -= skipBytes << 3;
12376      this.bytesAvailable -= skipBytes;
12377      this.loadWord();
12378      this.word <<= count;
12379      this.bitsAvailable -= count;
12380    }
12381  }
12382
12383  // (size:int):uint
12384  readBits(size) {
12385    let bits = Math.min(this.bitsAvailable, size); // :uint
12386    const valu = this.word >>> 32 - bits; // :uint
12387    if (size > 32) {
12388      logger.error('Cannot read more than 32 bits at a time');
12389    }
12390    this.bitsAvailable -= bits;
12391    if (this.bitsAvailable > 0) {
12392      this.word <<= bits;
12393    } else if (this.bytesAvailable > 0) {
12394      this.loadWord();
12395    } else {
12396      throw new Error('no bits available');
12397    }
12398    bits = size - bits;
12399    if (bits > 0 && this.bitsAvailable) {
12400      return valu << bits | this.readBits(bits);
12401    } else {
12402      return valu;
12403    }
12404  }
12405
12406  // ():uint
12407  skipLZ() {
12408    let leadingZeroCount; // :uint
12409    for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) {
12410      if ((this.word & 0x80000000 >>> leadingZeroCount) !== 0) {
12411        // the first bit of working word is 1
12412        this.word <<= leadingZeroCount;
12413        this.bitsAvailable -= leadingZeroCount;
12414        return leadingZeroCount;
12415      }
12416    }
12417    // we exhausted word and still have not found a 1
12418    this.loadWord();
12419    return leadingZeroCount + this.skipLZ();
12420  }
12421
12422  // ():void
12423  skipUEG() {
12424    this.skipBits(1 + this.skipLZ());
12425  }
12426
12427  // ():void
12428  skipEG() {
12429    this.skipBits(1 + this.skipLZ());
12430  }
12431
12432  // ():uint
12433  readUEG() {
12434    const clz = this.skipLZ(); // :uint
12435    return this.readBits(clz + 1) - 1;
12436  }
12437
12438  // ():int
12439  readEG() {
12440    const valu = this.readUEG(); // :int
12441    if (0x01 & valu) {
12442      // the number is odd if the low order bit is set
12443      return 1 + valu >>> 1; // add 1 to make it even, and divide by 2
12444    } else {
12445      return -1 * (valu >>> 1); // divide by two then make it negative
12446    }
12447  }
12448
12449  // Some convenience functions
12450  // :Boolean
12451  readBoolean() {
12452    return this.readBits(1) === 1;
12453  }
12454
12455  // ():int
12456  readUByte() {
12457    return this.readBits(8);
12458  }
12459
12460  // ():int
12461  readUShort() {
12462    return this.readBits(16);
12463  }
12464
12465  // ():int
12466  readUInt() {
12467    return this.readBits(32);
12468  }
12469}
12470
12471class AvcVideoParser extends BaseVideoParser {
12472  parsePES(track, textTrack, pes, endOfSegment) {
12473    const units = this.parseNALu(track, pes.data, endOfSegment);
12474    let VideoSample = this.VideoSample;
12475    let push;
12476    let spsfound = false;
12477    // free pes.data to save up some memory
12478    pes.data = null;
12479
12480    // if new NAL units found and last sample still there, let's push ...
12481    // this helps parsing streams with missing AUD (only do this if AUD never found)
12482    if (VideoSample && units.length && !track.audFound) {
12483      this.pushAccessUnit(VideoSample, track);
12484      VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
12485    }
12486    units.forEach(unit => {
12487      var _VideoSample2, _VideoSample3;
12488      switch (unit.type) {
12489        // NDR
12490        case 1:
12491          {
12492            let iskey = false;
12493            push = true;
12494            const data = unit.data;
12495            // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
12496            if (spsfound && data.length > 4) {
12497              // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
12498              const sliceType = this.readSliceType(data);
12499              // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
12500              // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
12501              // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
12502              // I slice: A slice that is not an SI slice that is decoded using intra prediction only.
12503              // if (sliceType === 2 || sliceType === 7) {
12504              if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
12505                iskey = true;
12506              }
12507            }
12508            if (iskey) {
12509              var _VideoSample;
12510              // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
12511              if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12512                this.pushAccessUnit(VideoSample, track);
12513                VideoSample = this.VideoSample = null;
12514              }
12515            }
12516            if (!VideoSample) {
12517              VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
12518            }
12519            VideoSample.frame = true;
12520            VideoSample.key = iskey;
12521            break;
12522            // IDR
12523          }
12524        case 5:
12525          push = true;
12526          // handle PES not starting with AUD
12527          // if we have frame data already, that cannot belong to the same frame, so force a push
12528          if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
12529            this.pushAccessUnit(VideoSample, track);
12530            VideoSample = this.VideoSample = null;
12531          }
12532          if (!VideoSample) {
12533            VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
12534          }
12535          VideoSample.key = true;
12536          VideoSample.frame = true;
12537          break;
12538        // SEI
12539        case 6:
12540          {
12541            push = true;
12542            parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
12543            break;
12544            // SPS
12545          }
12546        case 7:
12547          {
12548            var _track$pixelRatio, _track$pixelRatio2;
12549            push = true;
12550            spsfound = true;
12551            const sps = unit.data;
12552            const config = this.readSPS(sps);
12553            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]) {
12554              track.width = config.width;
12555              track.height = config.height;
12556              track.pixelRatio = config.pixelRatio;
12557              track.sps = [sps];
12558              const codecarray = sps.subarray(1, 4);
12559              let codecstring = 'avc1.';
12560              for (let i = 0; i < 3; i++) {
12561                let h = codecarray[i].toString(16);
12562                if (h.length < 2) {
12563                  h = '0' + h;
12564                }
12565                codecstring += h;
12566              }
12567              track.codec = codecstring;
12568            }
12569            break;
12570          }
12571        // PPS
12572        case 8:
12573          push = true;
12574          track.pps = [unit.data];
12575          break;
12576        // AUD
12577        case 9:
12578          push = true;
12579          track.audFound = true;
12580          if ((_VideoSample3 = VideoSample) != null && _VideoSample3.frame) {
12581            this.pushAccessUnit(VideoSample, track);
12582            VideoSample = null;
12583          }
12584          if (!VideoSample) {
12585            VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
12586          }
12587          break;
12588        // Filler Data
12589        case 12:
12590          push = true;
12591          break;
12592        default:
12593          push = false;
12594          break;
12595      }
12596      if (VideoSample && push) {
12597        const units = VideoSample.units;
12598        units.push(unit);
12599      }
12600    });
12601    // if last PES packet, push samples
12602    if (endOfSegment && VideoSample) {
12603      this.pushAccessUnit(VideoSample, track);
12604      this.VideoSample = null;
12605    }
12606  }
12607  getNALuType(data, offset) {
12608    return data[offset] & 0x1f;
12609  }
12610  readSliceType(data) {
12611    const eg = new ExpGolomb(data);
12612    // skip NALu type
12613    eg.readUByte();
12614    // discard first_mb_in_slice
12615    eg.readUEG();
12616    // return slice_type
12617    return eg.readUEG();
12618  }
12619
12620  /**
12621   * The scaling list is optionally transmitted as part of a sequence parameter
12622   * set and is not relevant to transmuxing.
12623   * @param count the number of entries in this scaling list
12624   * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
12625   */
12626  skipScalingList(count, reader) {
12627    let lastScale = 8;
12628    let nextScale = 8;
12629    let deltaScale;
12630    for (let j = 0; j < count; j++) {
12631      if (nextScale !== 0) {
12632        deltaScale = reader.readEG();
12633        nextScale = (lastScale + deltaScale + 256) % 256;
12634      }
12635      lastScale = nextScale === 0 ? lastScale : nextScale;
12636    }
12637  }
12638
12639  /**
12640   * Read a sequence parameter set and return some interesting video
12641   * properties. A sequence parameter set is the H264 metadata that
12642   * describes the properties of upcoming video frames.
12643   * @returns an object with configuration parsed from the
12644   * sequence parameter set, including the dimensions of the
12645   * associated video frames.
12646   */
12647  readSPS(sps) {
12648    const eg = new ExpGolomb(sps);
12649    let frameCropLeftOffset = 0;
12650    let frameCropRightOffset = 0;
12651    let frameCropTopOffset = 0;
12652    let frameCropBottomOffset = 0;
12653    let numRefFramesInPicOrderCntCycle;
12654    let scalingListCount;
12655    let i;
12656    const readUByte = eg.readUByte.bind(eg);
12657    const readBits = eg.readBits.bind(eg);
12658    const readUEG = eg.readUEG.bind(eg);
12659    const readBoolean = eg.readBoolean.bind(eg);
12660    const skipBits = eg.skipBits.bind(eg);
12661    const skipEG = eg.skipEG.bind(eg);
12662    const skipUEG = eg.skipUEG.bind(eg);
12663    const skipScalingList = this.skipScalingList.bind(this);
12664    readUByte();
12665    const profileIdc = readUByte(); // profile_idc
12666    readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
12667    skipBits(3); // reserved_zero_3bits u(3),
12668    readUByte(); // level_idc u(8)
12669    skipUEG(); // seq_parameter_set_id
12670    // some profiles have more optional data we don't need
12671    if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
12672      const chromaFormatIdc = readUEG();
12673      if (chromaFormatIdc === 3) {
12674        skipBits(1);
12675      } // separate_colour_plane_flag
12676
12677      skipUEG(); // bit_depth_luma_minus8
12678      skipUEG(); // bit_depth_chroma_minus8
12679      skipBits(1); // qpprime_y_zero_transform_bypass_flag
12680      if (readBoolean()) {
12681        // seq_scaling_matrix_present_flag
12682        scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
12683        for (i = 0; i < scalingListCount; i++) {
12684          if (readBoolean()) {
12685            // seq_scaling_list_present_flag[ i ]
12686            if (i < 6) {
12687              skipScalingList(16, eg);
12688            } else {
12689              skipScalingList(64, eg);
12690            }
12691          }
12692        }
12693      }
12694    }
12695    skipUEG(); // log2_max_frame_num_minus4
12696    const picOrderCntType = readUEG();
12697    if (picOrderCntType === 0) {
12698      readUEG(); // log2_max_pic_order_cnt_lsb_minus4
12699    } else if (picOrderCntType === 1) {
12700      skipBits(1); // delta_pic_order_always_zero_flag
12701      skipEG(); // offset_for_non_ref_pic
12702      skipEG(); // offset_for_top_to_bottom_field
12703      numRefFramesInPicOrderCntCycle = readUEG();
12704      for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
12705        skipEG();
12706      } // offset_for_ref_frame[ i ]
12707    }
12708    skipUEG(); // max_num_ref_frames
12709    skipBits(1); // gaps_in_frame_num_value_allowed_flag
12710    const picWidthInMbsMinus1 = readUEG();
12711    const picHeightInMapUnitsMinus1 = readUEG();
12712    const frameMbsOnlyFlag = readBits(1);
12713    if (frameMbsOnlyFlag === 0) {
12714      skipBits(1);
12715    } // mb_adaptive_frame_field_flag
12716
12717    skipBits(1); // direct_8x8_inference_flag
12718    if (readBoolean()) {
12719      // frame_cropping_flag
12720      frameCropLeftOffset = readUEG();
12721      frameCropRightOffset = readUEG();
12722      frameCropTopOffset = readUEG();
12723      frameCropBottomOffset = readUEG();
12724    }
12725    let pixelRatio = [1, 1];
12726    if (readBoolean()) {
12727      // vui_parameters_present_flag
12728      if (readBoolean()) {
12729        // aspect_ratio_info_present_flag
12730        const aspectRatioIdc = readUByte();
12731        switch (aspectRatioIdc) {
12732          case 1:
12733            pixelRatio = [1, 1];
12734            break;
12735          case 2:
12736            pixelRatio = [12, 11];
12737            break;
12738          case 3:
12739            pixelRatio = [10, 11];
12740            break;
12741          case 4:
12742            pixelRatio = [16, 11];
12743            break;
12744          case 5:
12745            pixelRatio = [40, 33];
12746            break;
12747          case 6:
12748            pixelRatio = [24, 11];
12749            break;
12750          case 7:
12751            pixelRatio = [20, 11];
12752            break;
12753          case 8:
12754            pixelRatio = [32, 11];
12755            break;
12756          case 9:
12757            pixelRatio = [80, 33];
12758            break;
12759          case 10:
12760            pixelRatio = [18, 11];
12761            break;
12762          case 11:
12763            pixelRatio = [15, 11];
12764            break;
12765          case 12:
12766            pixelRatio = [64, 33];
12767            break;
12768          case 13:
12769            pixelRatio = [160, 99];
12770            break;
12771          case 14:
12772            pixelRatio = [4, 3];
12773            break;
12774          case 15:
12775            pixelRatio = [3, 2];
12776            break;
12777          case 16:
12778            pixelRatio = [2, 1];
12779            break;
12780          case 255:
12781            {
12782              pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
12783              break;
12784            }
12785        }
12786      }
12787    }
12788    return {
12789      width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
12790      height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
12791      pixelRatio: pixelRatio
12792    };
12793  }
12794}
12795
12796class HevcVideoParser extends BaseVideoParser {
12797  constructor(...args) {
12798    super(...args);
12799    this.initVPS = null;
12800  }
12801  parsePES(track, textTrack, pes, endOfSegment) {
12802    const units = this.parseNALu(track, pes.data, endOfSegment);
12803    let VideoSample = this.VideoSample;
12804    let push;
12805    let spsfound = false;
12806    // free pes.data to save up some memory
12807    pes.data = null;
12808
12809    // if new NAL units found and last sample still there, let's push ...
12810    // this helps parsing streams with missing AUD (only do this if AUD never found)
12811    if (VideoSample && units.length && !track.audFound) {
12812      this.pushAccessUnit(VideoSample, track);
12813      VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
12814    }
12815    units.forEach(unit => {
12816      var _VideoSample2, _VideoSample3;
12817      switch (unit.type) {
12818        // NON-IDR, NON RANDOM ACCESS SLICE
12819        case 0:
12820        case 1:
12821        case 2:
12822        case 3:
12823        case 4:
12824        case 5:
12825        case 6:
12826        case 7:
12827        case 8:
12828        case 9:
12829          if (!VideoSample) {
12830            VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
12831          }
12832          VideoSample.frame = true;
12833          push = true;
12834          break;
12835
12836        // CRA, BLA (random access picture)
12837        case 16:
12838        case 17:
12839        case 18:
12840        case 21:
12841          push = true;
12842          if (spsfound) {
12843            var _VideoSample;
12844            // handle PES not starting with AUD
12845            // if we have frame data already, that cannot belong to the same frame, so force a push
12846            if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
12847              this.pushAccessUnit(VideoSample, track);
12848              VideoSample = this.VideoSample = null;
12849            }
12850          }
12851          if (!VideoSample) {
12852            VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
12853          }
12854          VideoSample.key = true;
12855          VideoSample.frame = true;
12856          break;
12857
12858        // IDR
12859        case 19:
12860        case 20:
12861          push = true;
12862          // handle PES not starting with AUD
12863          // if we have frame data already, that cannot belong to the same frame, so force a push
12864          if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
12865            this.pushAccessUnit(VideoSample, track);
12866            VideoSample = this.VideoSample = null;
12867          }
12868          if (!VideoSample) {
12869            VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
12870          }
12871          VideoSample.key = true;
12872          VideoSample.frame = true;
12873          break;
12874
12875        // SEI
12876        case 39:
12877          push = true;
12878          parseSEIMessageFromNALu(unit.data, 2,
12879          // NALu header size
12880          pes.pts, textTrack.samples);
12881          break;
12882
12883        // VPS
12884        case 32:
12885          push = true;
12886          if (!track.vps) {
12887            if (typeof track.params !== 'object') {
12888              track.params = {};
12889            }
12890            track.params = _extends(track.params, this.readVPS(unit.data));
12891            this.initVPS = unit.data;
12892          }
12893          track.vps = [unit.data];
12894          break;
12895
12896        // SPS
12897        case 33:
12898          push = true;
12899          spsfound = true;
12900          if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
12901            this.initVPS = track.vps[0];
12902            track.sps = track.pps = undefined;
12903          }
12904          if (!track.sps) {
12905            const config = this.readSPS(unit.data);
12906            track.width = config.width;
12907            track.height = config.height;
12908            track.pixelRatio = config.pixelRatio;
12909            track.codec = config.codecString;
12910            track.sps = [];
12911            if (typeof track.params !== 'object') {
12912              track.params = {};
12913            }
12914            for (const prop in config.params) {
12915              track.params[prop] = config.params[prop];
12916            }
12917          }
12918          this.pushParameterSet(track.sps, unit.data, track.vps);
12919          if (!VideoSample) {
12920            VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts);
12921          }
12922          VideoSample.key = true;
12923          break;
12924
12925        // PPS
12926        case 34:
12927          push = true;
12928          if (typeof track.params === 'object') {
12929            if (!track.pps) {
12930              track.pps = [];
12931              const config = this.readPPS(unit.data);
12932              for (const prop in config) {
12933                track.params[prop] = config[prop];
12934              }
12935            }
12936            this.pushParameterSet(track.pps, unit.data, track.vps);
12937          }
12938          break;
12939
12940        // ACCESS UNIT DELIMITER
12941        case 35:
12942          push = true;
12943          track.audFound = true;
12944          if ((_VideoSample3 = VideoSample) != null && _VideoSample3.frame) {
12945            this.pushAccessUnit(VideoSample, track);
12946            VideoSample = null;
12947          }
12948          if (!VideoSample) {
12949            VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts);
12950          }
12951          break;
12952        default:
12953          push = false;
12954          break;
12955      }
12956      if (VideoSample && push) {
12957        const units = VideoSample.units;
12958        units.push(unit);
12959      }
12960    });
12961    // if last PES packet, push samples
12962    if (endOfSegment && VideoSample) {
12963      this.pushAccessUnit(VideoSample, track);
12964      this.VideoSample = null;
12965    }
12966  }
12967  pushParameterSet(parameterSets, data, vps) {
12968    if (vps && vps[0] === this.initVPS || !vps && !parameterSets.length) {
12969      parameterSets.push(data);
12970    }
12971  }
12972  getNALuType(data, offset) {
12973    return (data[offset] & 0x7e) >>> 1;
12974  }
12975  ebsp2rbsp(arr) {
12976    const dst = new Uint8Array(arr.byteLength);
12977    let dstIdx = 0;
12978    for (let i = 0; i < arr.byteLength; i++) {
12979      if (i >= 2) {
12980        // Unescape: Skip 0x03 after 00 00
12981        if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
12982          continue;
12983        }
12984      }
12985      dst[dstIdx] = arr[i];
12986      dstIdx++;
12987    }
12988    return new Uint8Array(dst.buffer, 0, dstIdx);
12989  }
12990  pushAccessUnit(VideoSample, videoTrack) {
12991    super.pushAccessUnit(VideoSample, videoTrack);
12992    if (this.initVPS) {
12993      this.initVPS = null; // null initVPS to prevent possible track's sps/pps growth until next VPS
12994    }
12995  }
12996  readVPS(vps) {
12997    const eg = new ExpGolomb(vps);
12998    // remove header
12999    eg.readUByte();
13000    eg.readUByte();
13001    eg.readBits(4); // video_parameter_set_id
13002    eg.skipBits(2);
13003    eg.readBits(6); // max_layers_minus1
13004    const max_sub_layers_minus1 = eg.readBits(3);
13005    const temporal_id_nesting_flag = eg.readBoolean();
13006    // ...vui fps can be here, but empty fps value is not critical for metadata
13007
13008    return {
13009      numTemporalLayers: max_sub_layers_minus1 + 1,
13010      temporalIdNested: temporal_id_nesting_flag
13011    };
13012  }
13013  readSPS(sps) {
13014    const eg = new ExpGolomb(this.ebsp2rbsp(sps));
13015    eg.readUByte();
13016    eg.readUByte();
13017    eg.readBits(4); //video_parameter_set_id
13018    const max_sub_layers_minus1 = eg.readBits(3);
13019    eg.readBoolean(); // temporal_id_nesting_flag
13020
13021    // profile_tier_level
13022    const general_profile_space = eg.readBits(2);
13023    const general_tier_flag = eg.readBoolean();
13024    const general_profile_idc = eg.readBits(5);
13025    const general_profile_compatibility_flags_1 = eg.readUByte();
13026    const general_profile_compatibility_flags_2 = eg.readUByte();
13027    const general_profile_compatibility_flags_3 = eg.readUByte();
13028    const general_profile_compatibility_flags_4 = eg.readUByte();
13029    const general_constraint_indicator_flags_1 = eg.readUByte();
13030    const general_constraint_indicator_flags_2 = eg.readUByte();
13031    const general_constraint_indicator_flags_3 = eg.readUByte();
13032    const general_constraint_indicator_flags_4 = eg.readUByte();
13033    const general_constraint_indicator_flags_5 = eg.readUByte();
13034    const general_constraint_indicator_flags_6 = eg.readUByte();
13035    const general_level_idc = eg.readUByte();
13036    const sub_layer_profile_present_flags = [];
13037    const sub_layer_level_present_flags = [];
13038    for (let i = 0; i < max_sub_layers_minus1; i++) {
13039      sub_layer_profile_present_flags.push(eg.readBoolean());
13040      sub_layer_level_present_flags.push(eg.readBoolean());
13041    }
13042    if (max_sub_layers_minus1 > 0) {
13043      for (let i = max_sub_layers_minus1; i < 8; i++) {
13044        eg.readBits(2);
13045      }
13046    }
13047    for (let i = 0; i < max_sub_layers_minus1; i++) {
13048      if (sub_layer_profile_present_flags[i]) {
13049        eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
13050        eg.readUByte();
13051        eg.readUByte();
13052        eg.readUByte();
13053        eg.readUByte(); // sub_layer_profile_compatibility_flag
13054        eg.readUByte();
13055        eg.readUByte();
13056        eg.readUByte();
13057        eg.readUByte();
13058        eg.readUByte();
13059        eg.readUByte();
13060      }
13061      if (sub_layer_level_present_flags[i]) {
13062        eg.readUByte();
13063      }
13064    }
13065    eg.readUEG(); // seq_parameter_set_id
13066    const chroma_format_idc = eg.readUEG();
13067    if (chroma_format_idc == 3) {
13068      eg.skipBits(1); //separate_colour_plane_flag
13069    }
13070    const pic_width_in_luma_samples = eg.readUEG();
13071    const pic_height_in_luma_samples = eg.readUEG();
13072    const conformance_window_flag = eg.readBoolean();
13073    let pic_left_offset = 0,
13074      pic_right_offset = 0,
13075      pic_top_offset = 0,
13076      pic_bottom_offset = 0;
13077    if (conformance_window_flag) {
13078      pic_left_offset += eg.readUEG();
13079      pic_right_offset += eg.readUEG();
13080      pic_top_offset += eg.readUEG();
13081      pic_bottom_offset += eg.readUEG();
13082    }
13083    const bit_depth_luma_minus8 = eg.readUEG();
13084    const bit_depth_chroma_minus8 = eg.readUEG();
13085    const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
13086    const sub_layer_ordering_info_present_flag = eg.readBoolean();
13087    for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
13088      eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
13089      eg.skipUEG(); // max_num_reorder_pics[i]
13090      eg.skipUEG(); // max_latency_increase_plus1[i]
13091    }
13092    eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
13093    eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
13094    eg.skipUEG(); // log2_min_transform_block_size_minus2
13095    eg.skipUEG(); // log2_diff_max_min_transform_block_size
13096    eg.skipUEG(); // max_transform_hierarchy_depth_inter
13097    eg.skipUEG(); // max_transform_hierarchy_depth_intra
13098    const scaling_list_enabled_flag = eg.readBoolean();
13099    if (scaling_list_enabled_flag) {
13100      const sps_scaling_list_data_present_flag = eg.readBoolean();
13101      if (sps_scaling_list_data_present_flag) {
13102        for (let sizeId = 0; sizeId < 4; sizeId++) {
13103          for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
13104            const scaling_list_pred_mode_flag = eg.readBoolean();
13105            if (!scaling_list_pred_mode_flag) {
13106              eg.readUEG(); // scaling_list_pred_matrix_id_delta
13107            } else {
13108              const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
13109              if (sizeId > 1) {
13110                eg.readEG();
13111              }
13112              for (let i = 0; i < coefNum; i++) {
13113                eg.readEG();
13114              }
13115            }
13116          }
13117        }
13118      }
13119    }
13120    eg.readBoolean(); // amp_enabled_flag
13121    eg.readBoolean(); // sample_adaptive_offset_enabled_flag
13122    const pcm_enabled_flag = eg.readBoolean();
13123    if (pcm_enabled_flag) {
13124      eg.readUByte();
13125      eg.skipUEG();
13126      eg.skipUEG();
13127      eg.readBoolean();
13128    }
13129    const num_short_term_ref_pic_sets = eg.readUEG();
13130    let num_delta_pocs = 0;
13131    for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
13132      let inter_ref_pic_set_prediction_flag = false;
13133      if (i !== 0) {
13134        inter_ref_pic_set_prediction_flag = eg.readBoolean();
13135      }
13136      if (inter_ref_pic_set_prediction_flag) {
13137        if (i === num_short_term_ref_pic_sets) {
13138          eg.readUEG();
13139        }
13140        eg.readBoolean();
13141        eg.readUEG();
13142        let next_num_delta_pocs = 0;
13143        for (let j = 0; j <= num_delta_pocs; j++) {
13144          const used_by_curr_pic_flag = eg.readBoolean();
13145          let use_delta_flag = false;
13146          if (!used_by_curr_pic_flag) {
13147            use_delta_flag = eg.readBoolean();
13148          }
13149          if (used_by_curr_pic_flag || use_delta_flag) {
13150            next_num_delta_pocs++;
13151          }
13152        }
13153        num_delta_pocs = next_num_delta_pocs;
13154      } else {
13155        const num_negative_pics = eg.readUEG();
13156        const num_positive_pics = eg.readUEG();
13157        num_delta_pocs = num_negative_pics + num_positive_pics;
13158        for (let j = 0; j < num_negative_pics; j++) {
13159          eg.readUEG();
13160          eg.readBoolean();
13161        }
13162        for (let j = 0; j < num_positive_pics; j++) {
13163          eg.readUEG();
13164          eg.readBoolean();
13165        }
13166      }
13167    }
13168    const long_term_ref_pics_present_flag = eg.readBoolean();
13169    if (long_term_ref_pics_present_flag) {
13170      const num_long_term_ref_pics_sps = eg.readUEG();
13171      for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
13172        for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
13173          eg.readBits(1);
13174        }
13175        eg.readBits(1);
13176      }
13177    }
13178    let min_spatial_segmentation_idc = 0;
13179    let sar_width = 1,
13180      sar_height = 1;
13181    let fps_fixed = true,
13182      fps_den = 1,
13183      fps_num = 0;
13184    eg.readBoolean(); // sps_temporal_mvp_enabled_flag
13185    eg.readBoolean(); // strong_intra_smoothing_enabled_flag
13186    let default_display_window_flag = false;
13187    const vui_parameters_present_flag = eg.readBoolean();
13188    if (vui_parameters_present_flag) {
13189      const aspect_ratio_info_present_flag = eg.readBoolean();
13190      if (aspect_ratio_info_present_flag) {
13191        const aspect_ratio_idc = eg.readUByte();
13192        const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
13193        const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
13194        if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
13195          sar_width = sar_width_table[aspect_ratio_idc - 1];
13196          sar_height = sar_height_table[aspect_ratio_idc - 1];
13197        } else if (aspect_ratio_idc === 255) {
13198          sar_width = eg.readBits(16);
13199          sar_height = eg.readBits(16);
13200        }
13201      }
13202      const overscan_info_present_flag = eg.readBoolean();
13203      if (overscan_info_present_flag) {
13204        eg.readBoolean();
13205      }
13206      const video_signal_type_present_flag = eg.readBoolean();
13207      if (video_signal_type_present_flag) {
13208        eg.readBits(3);
13209        eg.readBoolean();
13210        const colour_description_present_flag = eg.readBoolean();
13211        if (colour_description_present_flag) {
13212          eg.readUByte();
13213          eg.readUByte();
13214          eg.readUByte();
13215        }
13216      }
13217      const chroma_loc_info_present_flag = eg.readBoolean();
13218      if (chroma_loc_info_present_flag) {
13219        eg.readUEG();
13220        eg.readUEG();
13221      }
13222      eg.readBoolean(); // neutral_chroma_indication_flag
13223      eg.readBoolean(); // field_seq_flag
13224      eg.readBoolean(); // frame_field_info_present_flag
13225      default_display_window_flag = eg.readBoolean();
13226      if (default_display_window_flag) {
13227        eg.skipUEG();
13228        eg.skipUEG();
13229        eg.skipUEG();
13230        eg.skipUEG();
13231      }
13232      const vui_timing_info_present_flag = eg.readBoolean();
13233      if (vui_timing_info_present_flag) {
13234        fps_den = eg.readBits(32);
13235        fps_num = eg.readBits(32);
13236        const vui_poc_proportional_to_timing_flag = eg.readBoolean();
13237        if (vui_poc_proportional_to_timing_flag) {
13238          eg.readUEG();
13239        }
13240        const vui_hrd_parameters_present_flag = eg.readBoolean();
13241        if (vui_hrd_parameters_present_flag) {
13242          //const commonInfPresentFlag = true;
13243          //if (commonInfPresentFlag) {
13244          const nal_hrd_parameters_present_flag = eg.readBoolean();
13245          const vcl_hrd_parameters_present_flag = eg.readBoolean();
13246          let sub_pic_hrd_params_present_flag = false;
13247          if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
13248            sub_pic_hrd_params_present_flag = eg.readBoolean();
13249            if (sub_pic_hrd_params_present_flag) {
13250              eg.readUByte();
13251              eg.readBits(5);
13252              eg.readBoolean();
13253              eg.readBits(5);
13254            }
13255            eg.readBits(4); // bit_rate_scale
13256            eg.readBits(4); // cpb_size_scale
13257            if (sub_pic_hrd_params_present_flag) {
13258              eg.readBits(4);
13259            }
13260            eg.readBits(5);
13261            eg.readBits(5);
13262            eg.readBits(5);
13263          }
13264          //}
13265          for (let i = 0; i <= max_sub_layers_minus1; i++) {
13266            fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
13267            const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
13268            let low_delay_hrd_flag = false;
13269            if (fixed_pic_rate_within_cvs_flag) {
13270              eg.readEG();
13271            } else {
13272              low_delay_hrd_flag = eg.readBoolean();
13273            }
13274            const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
13275            if (nal_hrd_parameters_present_flag) {
13276              for (let j = 0; j < cpb_cnt; j++) {
13277                eg.readUEG();
13278                eg.readUEG();
13279                if (sub_pic_hrd_params_present_flag) {
13280                  eg.readUEG();
13281                  eg.readUEG();
13282                }
13283                eg.skipBits(1);
13284              }
13285            }
13286            if (vcl_hrd_parameters_present_flag) {
13287              for (let j = 0; j < cpb_cnt; j++) {
13288                eg.readUEG();
13289                eg.readUEG();
13290                if (sub_pic_hrd_params_present_flag) {
13291                  eg.readUEG();
13292                  eg.readUEG();
13293                }
13294                eg.skipBits(1);
13295              }
13296            }
13297          }
13298        }
13299      }
13300      const bitstream_restriction_flag = eg.readBoolean();
13301      if (bitstream_restriction_flag) {
13302        eg.readBoolean(); // tiles_fixed_structure_flag
13303        eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
13304        eg.readBoolean(); // restricted_ref_pic_lists_flag
13305        min_spatial_segmentation_idc = eg.readUEG();
13306      }
13307    }
13308    let width = pic_width_in_luma_samples,
13309      height = pic_height_in_luma_samples;
13310    if (conformance_window_flag) {
13311      let chroma_scale_w = 1,
13312        chroma_scale_h = 1;
13313      if (chroma_format_idc === 1) {
13314        // YUV 420
13315        chroma_scale_w = chroma_scale_h = 2;
13316      } else if (chroma_format_idc == 2) {
13317        // YUV 422
13318        chroma_scale_w = 2;
13319      }
13320      width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
13321      height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
13322    }
13323    const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
13324    const profile_compatibility_buf = general_profile_compatibility_flags_1 << 24 | general_profile_compatibility_flags_2 << 16 | general_profile_compatibility_flags_3 << 8 | general_profile_compatibility_flags_4;
13325    let profile_compatibility_rev = 0;
13326    for (let i = 0; i < 32; i++) {
13327      profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
13328    }
13329    let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
13330    if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
13331      profile_compatibility_flags_string = '6';
13332    }
13333    const tier_flag_string = general_tier_flag ? 'H' : 'L';
13334    return {
13335      codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
13336      params: {
13337        general_tier_flag,
13338        general_profile_idc,
13339        general_profile_space,
13340        general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
13341        general_constraint_indicator_flags: [general_constraint_indicator_flags_1, general_constraint_indicator_flags_2, general_constraint_indicator_flags_3, general_constraint_indicator_flags_4, general_constraint_indicator_flags_5, general_constraint_indicator_flags_6],
13342        general_level_idc,
13343        bit_depth: bit_depth_luma_minus8 + 8,
13344        bit_depth_luma_minus8,
13345        bit_depth_chroma_minus8,
13346        min_spatial_segmentation_idc,
13347        chroma_format_idc: chroma_format_idc,
13348        frame_rate: {
13349          fixed: fps_fixed,
13350          fps: fps_num / fps_den
13351        }
13352      },
13353      width,
13354      height,
13355      pixelRatio: [sar_width, sar_height]
13356    };
13357  }
13358  readPPS(pps) {
13359    const eg = new ExpGolomb(this.ebsp2rbsp(pps));
13360    eg.readUByte();
13361    eg.readUByte();
13362    eg.skipUEG(); // pic_parameter_set_id
13363    eg.skipUEG(); // seq_parameter_set_id
13364    eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
13365    eg.skipBits(3); // num_extra_slice_header_bits
13366    eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
13367    eg.skipUEG();
13368    eg.skipUEG();
13369    eg.skipEG(); // init_qp_minus26
13370    eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
13371    const cu_qp_delta_enabled_flag = eg.readBoolean();
13372    if (cu_qp_delta_enabled_flag) {
13373      eg.skipUEG();
13374    }
13375    eg.skipEG(); // cb_qp_offset
13376    eg.skipEG(); // cr_qp_offset
13377    eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
13378    const tiles_enabled_flag = eg.readBoolean();
13379    const entropy_coding_sync_enabled_flag = eg.readBoolean();
13380    let parallelismType = 1; // slice-based parallel decoding
13381    if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
13382      parallelismType = 0; // mixed-type parallel decoding
13383    } else if (entropy_coding_sync_enabled_flag) {
13384      parallelismType = 3; // wavefront-based parallel decoding
13385    } else if (tiles_enabled_flag) {
13386      parallelismType = 2; // tile-based parallel decoding
13387    }
13388    return {
13389      parallelismType
13390    };
13391  }
13392  matchSPS(sps1, sps2) {
13393    // compare without headers and VPS related params
13394    return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
13395  }
13396}
13397
13398const PACKET_LENGTH = 188;
13399class TSDemuxer {
13400  constructor(observer, config, typeSupported, logger) {
13401    this.logger = void 0;
13402    this.observer = void 0;
13403    this.config = void 0;
13404    this.typeSupported = void 0;
13405    this.sampleAes = null;
13406    this.pmtParsed = false;
13407    this.audioCodec = void 0;
13408    this.videoCodec = void 0;
13409    this._pmtId = -1;
13410    this._videoTrack = void 0;
13411    this._audioTrack = void 0;
13412    this._id3Track = void 0;
13413    this._txtTrack = void 0;
13414    this.aacOverFlow = null;
13415    this.remainderData = null;
13416    this.videoParser = void 0;
13417    this.observer = observer;
13418    this.config = config;
13419    this.typeSupported = typeSupported;
13420    this.logger = logger;
13421    this.videoParser = null;
13422  }
13423  static probe(data, logger) {
13424    const syncOffset = TSDemuxer.syncOffset(data);
13425    if (syncOffset > 0) {
13426      logger.warn(`MPEG2-TS detected but first sync word found @ offset ${syncOffset}`);
13427    }
13428    return syncOffset !== -1;
13429  }
13430  static syncOffset(data) {
13431    const length = data.length;
13432    let scanwindow = Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1;
13433    let i = 0;
13434    while (i < scanwindow) {
13435      // a TS init segment should contain at least 2 TS packets: PAT and PMT, each starting with 0x47
13436      let foundPat = false;
13437      let packetStart = -1;
13438      let tsPackets = 0;
13439      for (let j = i; j < length; j += PACKET_LENGTH) {
13440        if (data[j] === 0x47 && (length - j === PACKET_LENGTH || data[j + PACKET_LENGTH] === 0x47)) {
13441          tsPackets++;
13442          if (packetStart === -1) {
13443            packetStart = j;
13444            // First sync word found at offset, increase scan length (#5251)
13445            if (packetStart !== 0) {
13446              scanwindow = Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1;
13447            }
13448          }
13449          if (!foundPat) {
13450            foundPat = parsePID(data, j) === 0;
13451          }
13452          // Sync word found at 0 with 3 packets, or found at offset least 2 packets up to scanwindow (#5501)
13453          if (foundPat && tsPackets > 1 && (packetStart === 0 && tsPackets > 2 || j + PACKET_LENGTH > scanwindow)) {
13454            return packetStart;
13455          }
13456        } else if (tsPackets) {
13457          // Exit if sync word found, but does not contain contiguous packets
13458          return -1;
13459        } else {
13460          break;
13461        }
13462      }
13463      i++;
13464    }
13465    return -1;
13466  }
13467
13468  /**
13469   * Creates a track model internal to demuxer used to drive remuxing input
13470   */
13471  static createTrack(type, duration) {
13472    return {
13473      container: type === 'video' || type === 'audio' ? 'video/mp2t' : undefined,
13474      type,
13475      id: RemuxerTrackIdConfig[type],
13476      pid: -1,
13477      inputTimeScale: 90000,
13478      sequenceNumber: 0,
13479      samples: [],
13480      dropped: 0,
13481      duration: type === 'audio' ? duration : undefined
13482    };
13483  }
13484
13485  /**
13486   * Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start)
13487   * Resets all internal track instances of the demuxer.
13488   */
13489  resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) {
13490    this.pmtParsed = false;
13491    this._pmtId = -1;
13492    this._videoTrack = TSDemuxer.createTrack('video');
13493    this._videoTrack.duration = trackDuration;
13494    this._audioTrack = TSDemuxer.createTrack('audio', trackDuration);
13495    this._id3Track = TSDemuxer.createTrack('id3');
13496    this._txtTrack = TSDemuxer.createTrack('text');
13497    this._audioTrack.segmentCodec = 'aac';
13498
13499    // flush any partial content
13500    this.videoParser = null;
13501    this.aacOverFlow = null;
13502    this.remainderData = null;
13503    this.audioCodec = audioCodec;
13504    this.videoCodec = videoCodec;
13505  }
13506  resetTimeStamp() {}
13507  resetContiguity() {
13508    const {
13509      _audioTrack,
13510      _videoTrack,
13511      _id3Track
13512    } = this;
13513    if (_audioTrack) {
13514      _audioTrack.pesData = null;
13515    }
13516    if (_videoTrack) {
13517      _videoTrack.pesData = null;
13518    }
13519    if (_id3Track) {
13520      _id3Track.pesData = null;
13521    }
13522    this.aacOverFlow = null;
13523    this.remainderData = null;
13524  }
13525  demux(data, timeOffset, isSampleAes = false, flush = false) {
13526    if (!isSampleAes) {
13527      this.sampleAes = null;
13528    }
13529    let pes;
13530    const videoTrack = this._videoTrack;
13531    const audioTrack = this._audioTrack;
13532    const id3Track = this._id3Track;
13533    const textTrack = this._txtTrack;
13534    let videoPid = videoTrack.pid;
13535    let videoData = videoTrack.pesData;
13536    let audioPid = audioTrack.pid;
13537    let id3Pid = id3Track.pid;
13538    let audioData = audioTrack.pesData;
13539    let id3Data = id3Track.pesData;
13540    let unknownPID = null;
13541    let pmtParsed = this.pmtParsed;
13542    let pmtId = this._pmtId;
13543    let len = data.length;
13544    if (this.remainderData) {
13545      data = appendUint8Array(this.remainderData, data);
13546      len = data.length;
13547      this.remainderData = null;
13548    }
13549    if (len < PACKET_LENGTH && !flush) {
13550      this.remainderData = data;
13551      return {
13552        audioTrack,
13553        videoTrack,
13554        id3Track,
13555        textTrack
13556      };
13557    }
13558    const syncOffset = Math.max(0, TSDemuxer.syncOffset(data));
13559    len -= (len - syncOffset) % PACKET_LENGTH;
13560    if (len < data.byteLength && !flush) {
13561      this.remainderData = new Uint8Array(data.buffer, len, data.buffer.byteLength - len);
13562    }
13563
13564    // loop through TS packets
13565    let tsPacketErrors = 0;
13566    for (let start = syncOffset; start < len; start += PACKET_LENGTH) {
13567      if (data[start] === 0x47) {
13568        const stt = !!(data[start + 1] & 0x40);
13569        const pid = parsePID(data, start);
13570        const atf = (data[start + 3] & 0x30) >> 4;
13571
13572        // if an adaption field is present, its length is specified by the fifth byte of the TS packet header.
13573        let offset;
13574        if (atf > 1) {
13575          offset = start + 5 + data[start + 4];
13576          // continue if there is only adaptation field
13577          if (offset === start + PACKET_LENGTH) {
13578            continue;
13579          }
13580        } else {
13581          offset = start + 4;
13582        }
13583        switch (pid) {
13584          case videoPid:
13585            if (stt) {
13586              if (videoData && (pes = parsePES(videoData, this.logger))) {
13587                this.readyVideoParser(videoTrack.segmentCodec);
13588                if (this.videoParser !== null) {
13589                  this.videoParser.parsePES(videoTrack, textTrack, pes, false);
13590                }
13591              }
13592              videoData = {
13593                data: [],
13594                size: 0
13595              };
13596            }
13597            if (videoData) {
13598              videoData.data.push(data.subarray(offset, start + PACKET_LENGTH));
13599              videoData.size += start + PACKET_LENGTH - offset;
13600            }
13601            break;
13602          case audioPid:
13603            if (stt) {
13604              if (audioData && (pes = parsePES(audioData, this.logger))) {
13605                switch (audioTrack.segmentCodec) {
13606                  case 'aac':
13607                    this.parseAACPES(audioTrack, pes);
13608                    break;
13609                  case 'mp3':
13610                    this.parseMPEGPES(audioTrack, pes);
13611                    break;
13612                  case 'ac3':
13613                    {
13614                      this.parseAC3PES(audioTrack, pes);
13615                    }
13616                    break;
13617                }
13618              }
13619              audioData = {
13620                data: [],
13621                size: 0
13622              };
13623            }
13624            if (audioData) {
13625              audioData.data.push(data.subarray(offset, start + PACKET_LENGTH));
13626              audioData.size += start + PACKET_LENGTH - offset;
13627            }
13628            break;
13629          case id3Pid:
13630            if (stt) {
13631              if (id3Data && (pes = parsePES(id3Data, this.logger))) {
13632                this.parseID3PES(id3Track, pes);
13633              }
13634              id3Data = {
13635                data: [],
13636                size: 0
13637              };
13638            }
13639            if (id3Data) {
13640              id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH));
13641              id3Data.size += start + PACKET_LENGTH - offset;
13642            }
13643            break;
13644          case 0:
13645            if (stt) {
13646              offset += data[offset] + 1;
13647            }
13648            pmtId = this._pmtId = parsePAT(data, offset);
13649            // this.logger.log('PMT PID:'  + this._pmtId);
13650            break;
13651          case pmtId:
13652            {
13653              if (stt) {
13654                offset += data[offset] + 1;
13655              }
13656              const parsedPIDs = parsePMT(data, offset, this.typeSupported, isSampleAes, this.observer, this.logger);
13657
13658              // only update track id if track PID found while parsing PMT
13659              // this is to avoid resetting the PID to -1 in case
13660              // track PID transiently disappears from the stream
13661              // this could happen in case of transient missing audio samples for example
13662              // NOTE this is only the PID of the track as found in TS,
13663              // but we are not using this for MP4 track IDs.
13664              videoPid = parsedPIDs.videoPid;
13665              if (videoPid > 0) {
13666                videoTrack.pid = videoPid;
13667                videoTrack.segmentCodec = parsedPIDs.segmentVideoCodec;
13668              }
13669              audioPid = parsedPIDs.audioPid;
13670              if (audioPid > 0) {
13671                audioTrack.pid = audioPid;
13672                audioTrack.segmentCodec = parsedPIDs.segmentAudioCodec;
13673              }
13674              id3Pid = parsedPIDs.id3Pid;
13675              if (id3Pid > 0) {
13676                id3Track.pid = id3Pid;
13677              }
13678              if (unknownPID !== null && !pmtParsed) {
13679                this.logger.warn(`MPEG-TS PMT found at ${start} after unknown PID '${unknownPID}'. Backtracking to sync byte @${syncOffset} to parse all TS packets.`);
13680                unknownPID = null;
13681                // we set it to -188, the += 188 in the for loop will reset start to 0
13682                start = syncOffset - 188;
13683              }
13684              pmtParsed = this.pmtParsed = true;
13685              break;
13686            }
13687          case 0x11:
13688          case 0x1fff:
13689            break;
13690          default:
13691            unknownPID = pid;
13692            break;
13693        }
13694      } else {
13695        tsPacketErrors++;
13696      }
13697    }
13698    if (tsPacketErrors > 0) {
13699      emitParsingError(this.observer, new Error(`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`), undefined, this.logger);
13700    }
13701    videoTrack.pesData = videoData;
13702    audioTrack.pesData = audioData;
13703    id3Track.pesData = id3Data;
13704    const demuxResult = {
13705      audioTrack,
13706      videoTrack,
13707      id3Track,
13708      textTrack
13709    };
13710    if (flush) {
13711      this.extractRemainingSamples(demuxResult);
13712    }
13713    return demuxResult;
13714  }
13715  flush() {
13716    const {
13717      remainderData
13718    } = this;
13719    this.remainderData = null;
13720    let result;
13721    if (remainderData) {
13722      result = this.demux(remainderData, -1, false, true);
13723    } else {
13724      result = {
13725        videoTrack: this._videoTrack,
13726        audioTrack: this._audioTrack,
13727        id3Track: this._id3Track,
13728        textTrack: this._txtTrack
13729      };
13730    }
13731    this.extractRemainingSamples(result);
13732    if (this.sampleAes) {
13733      return this.decrypt(result, this.sampleAes);
13734    }
13735    return result;
13736  }
13737  extractRemainingSamples(demuxResult) {
13738    const {
13739      audioTrack,
13740      videoTrack,
13741      id3Track,
13742      textTrack
13743    } = demuxResult;
13744    const videoData = videoTrack.pesData;
13745    const audioData = audioTrack.pesData;
13746    const id3Data = id3Track.pesData;
13747    // try to parse last PES packets
13748    let pes;
13749    if (videoData && (pes = parsePES(videoData, this.logger))) {
13750      this.readyVideoParser(videoTrack.segmentCodec);
13751      if (this.videoParser !== null) {
13752        this.videoParser.parsePES(videoTrack, textTrack, pes, true);
13753        videoTrack.pesData = null;
13754      }
13755    } else {
13756      // either avcData null or PES truncated, keep it for next frag parsing
13757      videoTrack.pesData = videoData;
13758    }
13759    if (audioData && (pes = parsePES(audioData, this.logger))) {
13760      switch (audioTrack.segmentCodec) {
13761        case 'aac':
13762          this.parseAACPES(audioTrack, pes);
13763          break;
13764        case 'mp3':
13765          this.parseMPEGPES(audioTrack, pes);
13766          break;
13767        case 'ac3':
13768          {
13769            this.parseAC3PES(audioTrack, pes);
13770          }
13771          break;
13772      }
13773      audioTrack.pesData = null;
13774    } else {
13775      if (audioData != null && audioData.size) {
13776        this.logger.log('last AAC PES packet truncated,might overlap between fragments');
13777      }
13778
13779      // either audioData null or PES truncated, keep it for next frag parsing
13780      audioTrack.pesData = audioData;
13781    }
13782    if (id3Data && (pes = parsePES(id3Data, this.logger))) {
13783      this.parseID3PES(id3Track, pes);
13784      id3Track.pesData = null;
13785    } else {
13786      // either id3Data null or PES truncated, keep it for next frag parsing
13787      id3Track.pesData = id3Data;
13788    }
13789  }
13790  demuxSampleAes(data, keyData, timeOffset) {
13791    const demuxResult = this.demux(data, timeOffset, true, !this.config.progressive);
13792    const sampleAes = this.sampleAes = new SampleAesDecrypter(this.observer, this.config, keyData);
13793    return this.decrypt(demuxResult, sampleAes);
13794  }
13795  readyVideoParser(codec) {
13796    if (this.videoParser === null) {
13797      if (codec === 'avc') {
13798        this.videoParser = new AvcVideoParser();
13799      } else if (codec === 'hevc') {
13800        this.videoParser = new HevcVideoParser();
13801      }
13802    }
13803  }
13804  decrypt(demuxResult, sampleAes) {
13805    return new Promise(resolve => {
13806      const {
13807        audioTrack,
13808        videoTrack
13809      } = demuxResult;
13810      if (audioTrack.samples && audioTrack.segmentCodec === 'aac') {
13811        sampleAes.decryptAacSamples(audioTrack.samples, 0, () => {
13812          if (videoTrack.samples) {
13813            sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
13814              resolve(demuxResult);
13815            });
13816          } else {
13817            resolve(demuxResult);
13818          }
13819        });
13820      } else if (videoTrack.samples) {
13821        sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
13822          resolve(demuxResult);
13823        });
13824      }
13825    });
13826  }
13827  destroy() {
13828    if (this.observer) {
13829      this.observer.removeAllListeners();
13830    }
13831    // @ts-ignore
13832    this.config = this.logger = this.observer = null;
13833    this.aacOverFlow = this.videoParser = this.remainderData = this.sampleAes = null;
13834    this._videoTrack = this._audioTrack = this._id3Track = this._txtTrack = undefined;
13835  }
13836  parseAACPES(track, pes) {
13837    let startOffset = 0;
13838    const aacOverFlow = this.aacOverFlow;
13839    let data = pes.data;
13840    if (aacOverFlow) {
13841      this.aacOverFlow = null;
13842      const frameMissingBytes = aacOverFlow.missing;
13843      const sampleLength = aacOverFlow.sample.unit.byteLength;
13844      // logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`);
13845      if (frameMissingBytes === -1) {
13846        data = appendUint8Array(aacOverFlow.sample.unit, data);
13847      } else {
13848        const frameOverflowBytes = sampleLength - frameMissingBytes;
13849        aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes);
13850        track.samples.push(aacOverFlow.sample);
13851        startOffset = aacOverFlow.missing;
13852      }
13853    }
13854    // look for ADTS header (0xFFFx)
13855    let offset;
13856    let len;
13857    for (offset = startOffset, len = data.length; offset < len - 1; offset++) {
13858      if (isHeader$1(data, offset)) {
13859        break;
13860      }
13861    }
13862    // if ADTS header does not start straight from the beginning of the PES payload, raise an error
13863    if (offset !== startOffset) {
13864      let reason;
13865      const recoverable = offset < len - 1;
13866      if (recoverable) {
13867        reason = `AAC PES did not start with ADTS header,offset:${offset}`;
13868      } else {
13869        reason = 'No ADTS header found in AAC PES';
13870      }
13871      emitParsingError(this.observer, new Error(reason), recoverable, this.logger);
13872      if (!recoverable) {
13873        return;
13874      }
13875    }
13876    initTrackConfig(track, this.observer, data, offset, this.audioCodec);
13877    let pts;
13878    if (pes.pts !== undefined) {
13879      pts = pes.pts;
13880    } else if (aacOverFlow) {
13881      // if last AAC frame is overflowing, we should ensure timestamps are contiguous:
13882      // first sample PTS should be equal to last sample PTS + frameDuration
13883      const frameDuration = getFrameDuration(track.samplerate);
13884      pts = aacOverFlow.sample.pts + frameDuration;
13885    } else {
13886      this.logger.warn('[tsdemuxer]: AAC PES unknown PTS');
13887      return;
13888    }
13889
13890    // scan for aac samples
13891    let frameIndex = 0;
13892    let frame;
13893    while (offset < len) {
13894      frame = appendFrame$2(track, data, offset, pts, frameIndex);
13895      offset += frame.length;
13896      if (!frame.missing) {
13897        frameIndex++;
13898        for (; offset < len - 1; offset++) {
13899          if (isHeader$1(data, offset)) {
13900            break;
13901          }
13902        }
13903      } else {
13904        this.aacOverFlow = frame;
13905        break;
13906      }
13907    }
13908  }
13909  parseMPEGPES(track, pes) {
13910    const data = pes.data;
13911    const length = data.length;
13912    let frameIndex = 0;
13913    let offset = 0;
13914    const pts = pes.pts;
13915    if (pts === undefined) {
13916      this.logger.warn('[tsdemuxer]: MPEG PES unknown PTS');
13917      return;
13918    }
13919    while (offset < length) {
13920      if (isHeader(data, offset)) {
13921        const frame = appendFrame$1(track, data, offset, pts, frameIndex);
13922        if (frame) {
13923          offset += frame.length;
13924          frameIndex++;
13925        } else {
13926          // logger.log('Unable to parse Mpeg audio frame');
13927          break;
13928        }
13929      } else {
13930        // nothing found, keep looking
13931        offset++;
13932      }
13933    }
13934  }
13935  parseAC3PES(track, pes) {
13936    {
13937      const data = pes.data;
13938      const pts = pes.pts;
13939      if (pts === undefined) {
13940        this.logger.warn('[tsdemuxer]: AC3 PES unknown PTS');
13941        return;
13942      }
13943      const length = data.length;
13944      let frameIndex = 0;
13945      let offset = 0;
13946      let parsed;
13947      while (offset < length && (parsed = appendFrame(track, data, offset, pts, frameIndex++)) > 0) {
13948        offset += parsed;
13949      }
13950    }
13951  }
13952  parseID3PES(id3Track, pes) {
13953    if (pes.pts === undefined) {
13954      this.logger.warn('[tsdemuxer]: ID3 PES unknown PTS');
13955      return;
13956    }
13957    const id3Sample = _extends({}, pes, {
13958      type: this._videoTrack ? MetadataSchema.emsg : MetadataSchema.audioId3,
13959      duration: Number.POSITIVE_INFINITY
13960    });
13961    id3Track.samples.push(id3Sample);
13962  }
13963}
13964function parsePID(data, offset) {
13965  // pid is a 13-bit field starting at the last bit of TS[1]
13966  return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2];
13967}
13968function parsePAT(data, offset) {
13969  // skip the PSI header and parse the first PMT entry
13970  return (data[offset + 10] & 0x1f) << 8 | data[offset + 11];
13971}
13972function parsePMT(data, offset, typeSupported, isSampleAes, observer, logger) {
13973  const result = {
13974    audioPid: -1,
13975    videoPid: -1,
13976    id3Pid: -1,
13977    segmentVideoCodec: 'avc',
13978    segmentAudioCodec: 'aac'
13979  };
13980  const sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2];
13981  const tableEnd = offset + 3 + sectionLength - 4;
13982  // to determine where the table is, we have to figure out how
13983  // long the program info descriptors are
13984  const programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11];
13985  // advance the offset to the first entry in the mapping table
13986  offset += 12 + programInfoLength;
13987  while (offset < tableEnd) {
13988    const pid = parsePID(data, offset);
13989    const esInfoLength = (data[offset + 3] & 0x0f) << 8 | data[offset + 4];
13990    switch (data[offset]) {
13991      case 0xcf:
13992        // SAMPLE-AES AAC
13993        if (!isSampleAes) {
13994          logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC', logger);
13995          break;
13996        }
13997      /* falls through */
13998      case 0x0f:
13999        // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
14000        // logger.log('AAC PID:'  + pid);
14001        if (result.audioPid === -1) {
14002          result.audioPid = pid;
14003        }
14004        break;
14005
14006      // Packetized metadata (ID3)
14007      case 0x15:
14008        // logger.log('ID3 PID:'  + pid);
14009        if (result.id3Pid === -1) {
14010          result.id3Pid = pid;
14011        }
14012        break;
14013      case 0xdb:
14014        // SAMPLE-AES AVC
14015        if (!isSampleAes) {
14016          logEncryptedSamplesFoundInUnencryptedStream('H.264', logger);
14017          break;
14018        }
14019      /* falls through */
14020      case 0x1b:
14021        // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
14022        // logger.log('AVC PID:'  + pid);
14023        if (result.videoPid === -1) {
14024          result.videoPid = pid;
14025        }
14026        break;
14027
14028      // ISO/IEC 11172-3 (MPEG-1 audio)
14029      // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
14030      case 0x03:
14031      case 0x04:
14032        // logger.log('MPEG PID:'  + pid);
14033        if (!typeSupported.mpeg && !typeSupported.mp3) {
14034          logger.log('MPEG audio found, not supported in this browser');
14035        } else if (result.audioPid === -1) {
14036          result.audioPid = pid;
14037          result.segmentAudioCodec = 'mp3';
14038        }
14039        break;
14040      case 0xc1:
14041        // SAMPLE-AES AC3
14042        if (!isSampleAes) {
14043          logEncryptedSamplesFoundInUnencryptedStream('AC-3', logger);
14044          break;
14045        }
14046      /* falls through */
14047      case 0x81:
14048        {
14049          if (!typeSupported.ac3) {
14050            logger.log('AC-3 audio found, not supported in this browser');
14051          } else if (result.audioPid === -1) {
14052            result.audioPid = pid;
14053            result.segmentAudioCodec = 'ac3';
14054          }
14055        }
14056        break;
14057      case 0x06:
14058        // stream_type 6 can mean a lot of different things in case of DVB.
14059        // We need to look at the descriptors. Right now, we're only interested
14060        // in AC-3 audio, so we do the descriptor parsing only when we don't have
14061        // an audio PID yet.
14062        if (result.audioPid === -1 && esInfoLength > 0) {
14063          let parsePos = offset + 5;
14064          let remaining = esInfoLength;
14065          while (remaining > 2) {
14066            const descriptorId = data[parsePos];
14067            switch (descriptorId) {
14068              case 0x6a:
14069                // DVB Descriptor for AC-3
14070                {
14071                  if (typeSupported.ac3 !== true) {
14072                    logger.log('AC-3 audio found, not supported in this browser for now');
14073                  } else {
14074                    result.audioPid = pid;
14075                    result.segmentAudioCodec = 'ac3';
14076                  }
14077                }
14078                break;
14079            }
14080            const descriptorLen = data[parsePos + 1] + 2;
14081            parsePos += descriptorLen;
14082            remaining -= descriptorLen;
14083          }
14084        }
14085        break;
14086      case 0xc2: // SAMPLE-AES EC3
14087      /* falls through */
14088      case 0x87:
14089        emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found'), undefined, logger);
14090        return result;
14091      case 0x24:
14092        // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
14093        {
14094          if (result.videoPid === -1) {
14095            result.videoPid = pid;
14096            result.segmentVideoCodec = 'hevc';
14097            logger.log('HEVC in M2TS found');
14098          }
14099        }
14100        break;
14101    }
14102    // move to the next table entry
14103    // skip past the elementary stream descriptors, if present
14104    offset += esInfoLength + 5;
14105  }
14106  return result;
14107}
14108function emitParsingError(observer, error, levelRetry, logger) {
14109  logger.warn(`parsing error: ${error.message}`);
14110  observer.emit(Events.ERROR, Events.ERROR, {
14111    type: ErrorTypes.MEDIA_ERROR,
14112    details: ErrorDetails.FRAG_PARSING_ERROR,
14113    fatal: false,
14114    levelRetry,
14115    error,
14116    reason: error.message
14117  });
14118}
14119function logEncryptedSamplesFoundInUnencryptedStream(type, logger) {
14120  logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`);
14121}
14122function parsePES(stream, logger) {
14123  let i = 0;
14124  let frag;
14125  let pesLen;
14126  let pesHdrLen;
14127  let pesPts;
14128  let pesDts;
14129  const data = stream.data;
14130  // safety check
14131  if (!stream || stream.size === 0) {
14132    return null;
14133  }
14134
14135  // we might need up to 19 bytes to read PES header
14136  // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes
14137  // usually only one merge is needed (and this is rare ...)
14138  while (data[0].length < 19 && data.length > 1) {
14139    data[0] = appendUint8Array(data[0], data[1]);
14140    data.splice(1, 1);
14141  }
14142  // retrieve PTS/DTS from first fragment
14143  frag = data[0];
14144  const pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
14145  if (pesPrefix === 1) {
14146    pesLen = (frag[4] << 8) + frag[5];
14147    // if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated
14148    // minus 6 : PES header size
14149    if (pesLen && pesLen > stream.size - 6) {
14150      return null;
14151    }
14152    const pesFlags = frag[7];
14153    if (pesFlags & 0xc0) {
14154      /* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
14155          as PTS / DTS is 33 bit we cannot use bitwise operator in JS,
14156          as Bitwise operators treat their operands as a sequence of 32 bits */
14157      pesPts = (frag[9] & 0x0e) * 536870912 +
14158      // 1 << 29
14159      (frag[10] & 0xff) * 4194304 +
14160      // 1 << 22
14161      (frag[11] & 0xfe) * 16384 +
14162      // 1 << 14
14163      (frag[12] & 0xff) * 128 +
14164      // 1 << 7
14165      (frag[13] & 0xfe) / 2;
14166      if (pesFlags & 0x40) {
14167        pesDts = (frag[14] & 0x0e) * 536870912 +
14168        // 1 << 29
14169        (frag[15] & 0xff) * 4194304 +
14170        // 1 << 22
14171        (frag[16] & 0xfe) * 16384 +
14172        // 1 << 14
14173        (frag[17] & 0xff) * 128 +
14174        // 1 << 7
14175        (frag[18] & 0xfe) / 2;
14176        if (pesPts - pesDts > 60 * 90000) {
14177          logger.warn(`${Math.round((pesPts - pesDts) / 90000)}s delta between PTS and DTS, align them`);
14178          pesPts = pesDts;
14179        }
14180      } else {
14181        pesDts = pesPts;
14182      }
14183    }
14184    pesHdrLen = frag[8];
14185    // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
14186    let payloadStartOffset = pesHdrLen + 9;
14187    if (stream.size <= payloadStartOffset) {
14188      return null;
14189    }
14190    stream.size -= payloadStartOffset;
14191    // reassemble PES packet
14192    const pesData = new Uint8Array(stream.size);
14193    for (let j = 0, dataLen = data.length; j < dataLen; j++) {
14194      frag = data[j];
14195      let len = frag.byteLength;
14196      if (payloadStartOffset) {
14197        if (payloadStartOffset > len) {
14198          // trim full frag if PES header bigger than frag
14199          payloadStartOffset -= len;
14200          continue;
14201        } else {
14202          // trim partial frag if PES header smaller than frag
14203          frag = frag.subarray(payloadStartOffset);
14204          len -= payloadStartOffset;
14205          payloadStartOffset = 0;
14206        }
14207      }
14208      pesData.set(frag, i);
14209      i += len;
14210    }
14211    if (pesLen) {
14212      // payload size : remove PES header + PES extension
14213      pesLen -= pesHdrLen + 3;
14214    }
14215    return {
14216      data: pesData,
14217      pts: pesPts,
14218      dts: pesDts,
14219      len: pesLen
14220    };
14221  }
14222  return null;
14223}
14224
14225/**
14226 *  AAC helper
14227 */
14228
14229class AAC {
14230  static getSilentFrame(codec, channelCount) {
14231    switch (codec) {
14232      case 'mp4a.40.2':
14233        if (channelCount === 1) {
14234          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]);
14235        } else if (channelCount === 2) {
14236          return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]);
14237        } else if (channelCount === 3) {
14238          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]);
14239        } else if (channelCount === 4) {
14240          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]);
14241        } else if (channelCount === 5) {
14242          return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]);
14243        } else if (channelCount === 6) {
14244          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]);
14245        }
14246        break;
14247      // handle HE-AAC below (mp4a.40.5 / mp4a.40.29)
14248      default:
14249        if (channelCount === 1) {
14250          // 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
14251          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]);
14252        } else if (channelCount === 2) {
14253          // 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
14254          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]);
14255        } else if (channelCount === 3) {
14256          // 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
14257          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]);
14258        }
14259        break;
14260    }
14261    return undefined;
14262  }
14263}
14264
14265/**
14266 * Generate MP4 Box
14267 */
14268
14269const UINT32_MAX = Math.pow(2, 32) - 1;
14270class MP4 {
14271  static init() {
14272    MP4.types = {
14273      avc1: [],
14274      // codingname
14275      avcC: [],
14276      hvc1: [],
14277      hvcC: [],
14278      btrt: [],
14279      dinf: [],
14280      dref: [],
14281      esds: [],
14282      ftyp: [],
14283      hdlr: [],
14284      mdat: [],
14285      mdhd: [],
14286      mdia: [],
14287      mfhd: [],
14288      minf: [],
14289      moof: [],
14290      moov: [],
14291      mp4a: [],
14292      '.mp3': [],
14293      dac3: [],
14294      'ac-3': [],
14295      mvex: [],
14296      mvhd: [],
14297      pasp: [],
14298      sdtp: [],
14299      stbl: [],
14300      stco: [],
14301      stsc: [],
14302      stsd: [],
14303      stsz: [],
14304      stts: [],
14305      tfdt: [],
14306      tfhd: [],
14307      traf: [],
14308      trak: [],
14309      trun: [],
14310      trex: [],
14311      tkhd: [],
14312      vmhd: [],
14313      smhd: []
14314    };
14315    let i;
14316    for (i in MP4.types) {
14317      if (MP4.types.hasOwnProperty(i)) {
14318        MP4.types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
14319      }
14320    }
14321    const videoHdlr = new Uint8Array([0x00,
14322    // version 0
14323    0x00, 0x00, 0x00,
14324    // flags
14325    0x00, 0x00, 0x00, 0x00,
14326    // pre_defined
14327    0x76, 0x69, 0x64, 0x65,
14328    // handler_type: 'vide'
14329    0x00, 0x00, 0x00, 0x00,
14330    // reserved
14331    0x00, 0x00, 0x00, 0x00,
14332    // reserved
14333    0x00, 0x00, 0x00, 0x00,
14334    // reserved
14335    0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
14336    ]);
14337    const audioHdlr = new Uint8Array([0x00,
14338    // version 0
14339    0x00, 0x00, 0x00,
14340    // flags
14341    0x00, 0x00, 0x00, 0x00,
14342    // pre_defined
14343    0x73, 0x6f, 0x75, 0x6e,
14344    // handler_type: 'soun'
14345    0x00, 0x00, 0x00, 0x00,
14346    // reserved
14347    0x00, 0x00, 0x00, 0x00,
14348    // reserved
14349    0x00, 0x00, 0x00, 0x00,
14350    // reserved
14351    0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
14352    ]);
14353    MP4.HDLR_TYPES = {
14354      video: videoHdlr,
14355      audio: audioHdlr
14356    };
14357    const dref = new Uint8Array([0x00,
14358    // version 0
14359    0x00, 0x00, 0x00,
14360    // flags
14361    0x00, 0x00, 0x00, 0x01,
14362    // entry_count
14363    0x00, 0x00, 0x00, 0x0c,
14364    // entry_size
14365    0x75, 0x72, 0x6c, 0x20,
14366    // 'url' type
14367    0x00,
14368    // version 0
14369    0x00, 0x00, 0x01 // entry_flags
14370    ]);
14371    const stco = new Uint8Array([0x00,
14372    // version
14373    0x00, 0x00, 0x00,
14374    // flags
14375    0x00, 0x00, 0x00, 0x00 // entry_count
14376    ]);
14377    MP4.STTS = MP4.STSC = MP4.STCO = stco;
14378    MP4.STSZ = new Uint8Array([0x00,
14379    // version
14380    0x00, 0x00, 0x00,
14381    // flags
14382    0x00, 0x00, 0x00, 0x00,
14383    // sample_size
14384    0x00, 0x00, 0x00, 0x00 // sample_count
14385    ]);
14386    MP4.VMHD = new Uint8Array([0x00,
14387    // version
14388    0x00, 0x00, 0x01,
14389    // flags
14390    0x00, 0x00,
14391    // graphicsmode
14392    0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
14393    ]);
14394    MP4.SMHD = new Uint8Array([0x00,
14395    // version
14396    0x00, 0x00, 0x00,
14397    // flags
14398    0x00, 0x00,
14399    // balance
14400    0x00, 0x00 // reserved
14401    ]);
14402    MP4.STSD = new Uint8Array([0x00,
14403    // version 0
14404    0x00, 0x00, 0x00,
14405    // flags
14406    0x00, 0x00, 0x00, 0x01]); // entry_count
14407
14408    const majorBrand = new Uint8Array([105, 115, 111, 109]); // isom
14409    const avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1
14410    const minorVersion = new Uint8Array([0, 0, 0, 1]);
14411    MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand);
14412    MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref));
14413  }
14414  static box(type, ...payload) {
14415    let size = 8;
14416    let i = payload.length;
14417    const len = i;
14418    // calculate the total size we need to allocate
14419    while (i--) {
14420      size += payload[i].byteLength;
14421    }
14422    const result = new Uint8Array(size);
14423    result[0] = size >> 24 & 0xff;
14424    result[1] = size >> 16 & 0xff;
14425    result[2] = size >> 8 & 0xff;
14426    result[3] = size & 0xff;
14427    result.set(type, 4);
14428    // copy the payload into the result
14429    for (i = 0, size = 8; i < len; i++) {
14430      // copy payload[i] array @ offset size
14431      result.set(payload[i], size);
14432      size += payload[i].byteLength;
14433    }
14434    return result;
14435  }
14436  static hdlr(type) {
14437    return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]);
14438  }
14439  static mdat(data) {
14440    return MP4.box(MP4.types.mdat, data);
14441  }
14442  static mdhd(timescale, duration) {
14443    duration *= timescale;
14444    const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
14445    const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
14446    return MP4.box(MP4.types.mdhd, new Uint8Array([0x01,
14447    // version 1
14448    0x00, 0x00, 0x00,
14449    // flags
14450    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
14451    // creation_time
14452    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
14453    // modification_time
14454    timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,
14455    // timescale
14456    upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x55, 0xc4,
14457    // 'und' language (undetermined)
14458    0x00, 0x00]));
14459  }
14460  static mdia(track) {
14461    return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale || 0, track.duration || 0), MP4.hdlr(track.type), MP4.minf(track));
14462  }
14463  static mfhd(sequenceNumber) {
14464    return MP4.box(MP4.types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00,
14465    // flags
14466    sequenceNumber >> 24, sequenceNumber >> 16 & 0xff, sequenceNumber >> 8 & 0xff, sequenceNumber & 0xff // sequence_number
14467    ]));
14468  }
14469  static minf(track) {
14470    if (track.type === 'audio') {
14471      return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track));
14472    } else {
14473      return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track));
14474    }
14475  }
14476  static moof(sn, baseMediaDecodeTime, track) {
14477    return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime));
14478  }
14479  static moov(tracks) {
14480    let i = tracks.length;
14481    const boxes = [];
14482    while (i--) {
14483      boxes[i] = MP4.trak(tracks[i]);
14484    }
14485    return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale || 0, tracks[0].duration || 0)].concat(boxes).concat(MP4.mvex(tracks)));
14486  }
14487  static mvex(tracks) {
14488    let i = tracks.length;
14489    const boxes = [];
14490    while (i--) {
14491      boxes[i] = MP4.trex(tracks[i]);
14492    }
14493    return MP4.box.apply(null, [MP4.types.mvex, ...boxes]);
14494  }
14495  static mvhd(timescale, duration) {
14496    duration *= timescale;
14497    const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
14498    const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
14499    const bytes = new Uint8Array([0x01,
14500    // version 1
14501    0x00, 0x00, 0x00,
14502    // flags
14503    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
14504    // creation_time
14505    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
14506    // modification_time
14507    timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff,
14508    // timescale
14509    upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x01, 0x00, 0x00,
14510    // 1.0 rate
14511    0x01, 0x00,
14512    // 1.0 volume
14513    0x00, 0x00,
14514    // reserved
14515    0x00, 0x00, 0x00, 0x00,
14516    // reserved
14517    0x00, 0x00, 0x00, 0x00,
14518    // reserved
14519    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,
14520    // transformation: unity matrix
14521    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14522    // pre_defined
14523    0xff, 0xff, 0xff, 0xff // next_track_ID
14524    ]);
14525    return MP4.box(MP4.types.mvhd, bytes);
14526  }
14527  static sdtp(track) {
14528    const samples = track.samples || [];
14529    const bytes = new Uint8Array(4 + samples.length);
14530    let i;
14531    let flags;
14532    // leave the full box header (4 bytes) all zero
14533    // write the sample table
14534    for (i = 0; i < samples.length; i++) {
14535      flags = samples[i].flags;
14536      bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
14537    }
14538    return MP4.box(MP4.types.sdtp, bytes);
14539  }
14540  static stbl(track) {
14541    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));
14542  }
14543  static avc1(track) {
14544    let sps = [];
14545    let pps = [];
14546    let i;
14547    let data;
14548    let len;
14549    // assemble the SPSs
14550
14551    for (i = 0; i < track.sps.length; i++) {
14552      data = track.sps[i];
14553      len = data.byteLength;
14554      sps.push(len >>> 8 & 0xff);
14555      sps.push(len & 0xff);
14556
14557      // SPS
14558      sps = sps.concat(Array.prototype.slice.call(data));
14559    }
14560
14561    // assemble the PPSs
14562    for (i = 0; i < track.pps.length; i++) {
14563      data = track.pps[i];
14564      len = data.byteLength;
14565      pps.push(len >>> 8 & 0xff);
14566      pps.push(len & 0xff);
14567      pps = pps.concat(Array.prototype.slice.call(data));
14568    }
14569    const avcc = MP4.box(MP4.types.avcC, new Uint8Array([0x01,
14570    // version
14571    sps[3],
14572    // profile
14573    sps[4],
14574    // profile compat
14575    sps[5],
14576    // level
14577    0xfc | 3,
14578    // lengthSizeMinusOne, hard-coded to 4 bytes
14579    0xe0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets
14580    ].concat(sps).concat([track.pps.length // numOfPictureParameterSets
14581    ]).concat(pps))); // "PPS"
14582    const width = track.width;
14583    const height = track.height;
14584    const hSpacing = track.pixelRatio[0];
14585    const vSpacing = track.pixelRatio[1];
14586    return MP4.box(MP4.types.avc1, new Uint8Array([0x00, 0x00, 0x00,
14587    // reserved
14588    0x00, 0x00, 0x00,
14589    // reserved
14590    0x00, 0x01,
14591    // data_reference_index
14592    0x00, 0x00,
14593    // pre_defined
14594    0x00, 0x00,
14595    // reserved
14596    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14597    // pre_defined
14598    width >> 8 & 0xff, width & 0xff,
14599    // width
14600    height >> 8 & 0xff, height & 0xff,
14601    // height
14602    0x00, 0x48, 0x00, 0x00,
14603    // horizresolution
14604    0x00, 0x48, 0x00, 0x00,
14605    // vertresolution
14606    0x00, 0x00, 0x00, 0x00,
14607    // reserved
14608    0x00, 0x01,
14609    // frame_count
14610    0x12, 0x64, 0x61, 0x69, 0x6c,
14611    // dailymotion/hls.js
14612    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,
14613    // compressorname
14614    0x00, 0x18,
14615    // depth = 24
14616    0x11, 0x11]),
14617    // pre_defined = -1
14618    avcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14619    // bufferSizeDB
14620    0x00, 0x2d, 0xc6, 0xc0,
14621    // maxBitrate
14622    0x00, 0x2d, 0xc6, 0xc0])),
14623    // avgBitrate
14624    MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14625    // hSpacing
14626    hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14627    // vSpacing
14628    vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14629  }
14630  static esds(track) {
14631    const config = track.config;
14632    return new Uint8Array([0x00,
14633    // version 0
14634    0x00, 0x00, 0x00,
14635    // flags
14636
14637    0x03,
14638    // descriptor_type
14639    0x19,
14640    // length
14641
14642    0x00, 0x01,
14643    // es_id
14644
14645    0x00,
14646    // stream_priority
14647
14648    0x04,
14649    // descriptor_type
14650    0x11,
14651    // length
14652    0x40,
14653    // codec : mpeg4_audio
14654    0x15,
14655    // stream_type
14656    0x00, 0x00, 0x00,
14657    // buffer_size
14658    0x00, 0x00, 0x00, 0x00,
14659    // maxBitrate
14660    0x00, 0x00, 0x00, 0x00,
14661    // avgBitrate
14662
14663    0x05,
14664    // descriptor_type
14665    0x02,
14666    // length
14667    ...config, 0x06, 0x01, 0x02 // GASpecificConfig)); // length + audio config descriptor
14668    ]);
14669  }
14670  static audioStsd(track) {
14671    const samplerate = track.samplerate || 0;
14672    return new Uint8Array([0x00, 0x00, 0x00,
14673    // reserved
14674    0x00, 0x00, 0x00,
14675    // reserved
14676    0x00, 0x01,
14677    // data_reference_index
14678    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14679    // reserved
14680    0x00, track.channelCount || 0,
14681    // channelcount
14682    0x00, 0x10,
14683    // sampleSize:16bits
14684    0x00, 0x00, 0x00, 0x00,
14685    // reserved2
14686    samplerate >> 8 & 0xff, samplerate & 0xff,
14687    //
14688    0x00, 0x00]);
14689  }
14690  static mp4a(track) {
14691    return MP4.box(MP4.types.mp4a, MP4.audioStsd(track), MP4.box(MP4.types.esds, MP4.esds(track)));
14692  }
14693  static mp3(track) {
14694    return MP4.box(MP4.types['.mp3'], MP4.audioStsd(track));
14695  }
14696  static ac3(track) {
14697    return MP4.box(MP4.types['ac-3'], MP4.audioStsd(track), MP4.box(MP4.types.dac3, track.config));
14698  }
14699  static stsd(track) {
14700    const {
14701      segmentCodec
14702    } = track;
14703    if (track.type === 'audio') {
14704      if (segmentCodec === 'aac') {
14705        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
14706      }
14707      if (segmentCodec === 'ac3' && track.config) {
14708        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
14709      }
14710      if (segmentCodec === 'mp3' && track.codec === 'mp3') {
14711        return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track));
14712      }
14713    } else {
14714      if (track.pps && track.sps) {
14715        if (segmentCodec === 'avc') {
14716          return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
14717        }
14718        if (segmentCodec === 'hevc' && track.vps) {
14719          return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
14720        }
14721      } else {
14722        throw new Error(`video track missing pps or sps`);
14723      }
14724    }
14725    throw new Error(`unsupported ${track.type} segment codec (${segmentCodec}/${track.codec})`);
14726  }
14727  static tkhd(track) {
14728    const id = track.id;
14729    const duration = (track.duration || 0) * (track.timescale || 0);
14730    const width = track.width || 0;
14731    const height = track.height || 0;
14732    const upperWordDuration = Math.floor(duration / (UINT32_MAX + 1));
14733    const lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1));
14734    return MP4.box(MP4.types.tkhd, new Uint8Array([0x01,
14735    // version 1
14736    0x00, 0x00, 0x07,
14737    // flags
14738    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
14739    // creation_time
14740    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
14741    // modification_time
14742    id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,
14743    // track_ID
14744    0x00, 0x00, 0x00, 0x00,
14745    // reserved
14746    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,
14747    // reserved
14748    0x00, 0x00,
14749    // layer
14750    0x00, 0x00,
14751    // alternate_group
14752    0x00, 0x00,
14753    // non-audio track volume
14754    0x00, 0x00,
14755    // reserved
14756    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,
14757    // transformation: unity matrix
14758    width >> 8 & 0xff, width & 0xff, 0x00, 0x00,
14759    // width
14760    height >> 8 & 0xff, height & 0xff, 0x00, 0x00 // height
14761    ]));
14762  }
14763  static traf(track, baseMediaDecodeTime) {
14764    const sampleDependencyTable = MP4.sdtp(track);
14765    const id = track.id;
14766    const upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1));
14767    const lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));
14768    return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([0x00,
14769    // version 0
14770    0x00, 0x00, 0x00,
14771    // flags
14772    id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff // track_ID
14773    ])), MP4.box(MP4.types.tfdt, new Uint8Array([0x01,
14774    // version 1
14775    0x00, 0x00, 0x00,
14776    // flags
14777    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 +
14778    // tfhd
14779    20 +
14780    // tfdt
14781    8 +
14782    // traf header
14783    16 +
14784    // mfhd
14785    8 +
14786    // moof header
14787    8),
14788    // mdat header
14789    sampleDependencyTable);
14790  }
14791
14792  /**
14793   * Generate a track box.
14794   * @param track a track definition
14795   */
14796  static trak(track) {
14797    track.duration = track.duration || 0xffffffff;
14798    return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track));
14799  }
14800  static trex(track) {
14801    const id = track.id;
14802    return MP4.box(MP4.types.trex, new Uint8Array([0x00,
14803    // version 0
14804    0x00, 0x00, 0x00,
14805    // flags
14806    id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff,
14807    // track_ID
14808    0x00, 0x00, 0x00, 0x01,
14809    // default_sample_description_index
14810    0x00, 0x00, 0x00, 0x00,
14811    // default_sample_duration
14812    0x00, 0x00, 0x00, 0x00,
14813    // default_sample_size
14814    0x00, 0x01, 0x00, 0x01 // default_sample_flags
14815    ]));
14816  }
14817  static trun(track, offset) {
14818    const samples = track.samples || [];
14819    const len = samples.length;
14820    const arraylen = 12 + 16 * len;
14821    const array = new Uint8Array(arraylen);
14822    let i;
14823    let sample;
14824    let duration;
14825    let size;
14826    let flags;
14827    let cts;
14828    offset += 8 + arraylen;
14829    array.set([track.type === 'video' ? 0x01 : 0x00,
14830    // version 1 for video with signed-int sample_composition_time_offset
14831    0x00, 0x0f, 0x01,
14832    // flags
14833    len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,
14834    // sample_count
14835    offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset
14836    ], 0);
14837    for (i = 0; i < len; i++) {
14838      sample = samples[i];
14839      duration = sample.duration;
14840      size = sample.size;
14841      flags = sample.flags;
14842      cts = sample.cts;
14843      array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,
14844      // sample_duration
14845      size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,
14846      // sample_size
14847      flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,
14848      // sample_flags
14849      cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset
14850      ], 12 + 16 * i);
14851    }
14852    return MP4.box(MP4.types.trun, array);
14853  }
14854  static initSegment(tracks) {
14855    if (!MP4.types) {
14856      MP4.init();
14857    }
14858    const movie = MP4.moov(tracks);
14859    const result = appendUint8Array(MP4.FTYP, movie);
14860    return result;
14861  }
14862  static hvc1(track) {
14863    const ps = track.params;
14864    const units = [track.vps, track.sps, track.pps];
14865    const NALuLengthSize = 4;
14866    const config = new Uint8Array([0x01, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0x00, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
14867
14868    // compute hvcC size in bytes
14869    let length = config.length;
14870    for (let i = 0; i < units.length; i += 1) {
14871      length += 3;
14872      for (let j = 0; j < units[i].length; j += 1) {
14873        length += 2 + units[i][j].length;
14874      }
14875    }
14876    const hvcC = new Uint8Array(length);
14877    hvcC.set(config, 0);
14878    length = config.length;
14879    // append parameter set units: one vps, one or more sps and pps
14880    const iMax = units.length - 1;
14881    for (let i = 0; i < units.length; i += 1) {
14882      hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
14883      length += 3;
14884      for (let j = 0; j < units[i].length; j += 1) {
14885        hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
14886        length += 2;
14887        hvcC.set(units[i][j], length);
14888        length += units[i][j].length;
14889      }
14890    }
14891    const hvcc = MP4.box(MP4.types.hvcC, hvcC);
14892    const width = track.width;
14893    const height = track.height;
14894    const hSpacing = track.pixelRatio[0];
14895    const vSpacing = track.pixelRatio[1];
14896    return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
14897    // reserved
14898    0x00, 0x00, 0x00,
14899    // reserved
14900    0x00, 0x01,
14901    // data_reference_index
14902    0x00, 0x00,
14903    // pre_defined
14904    0x00, 0x00,
14905    // reserved
14906    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
14907    // pre_defined
14908    width >> 8 & 0xff, width & 0xff,
14909    // width
14910    height >> 8 & 0xff, height & 0xff,
14911    // height
14912    0x00, 0x48, 0x00, 0x00,
14913    // horizresolution
14914    0x00, 0x48, 0x00, 0x00,
14915    // vertresolution
14916    0x00, 0x00, 0x00, 0x00,
14917    // reserved
14918    0x00, 0x01,
14919    // frame_count
14920    0x12, 0x64, 0x61, 0x69, 0x6c,
14921    // dailymotion/hls.js
14922    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,
14923    // compressorname
14924    0x00, 0x18,
14925    // depth = 24
14926    0x11, 0x11]),
14927    // pre_defined = -1
14928    hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
14929    // bufferSizeDB
14930    0x00, 0x2d, 0xc6, 0xc0,
14931    // maxBitrate
14932    0x00, 0x2d, 0xc6, 0xc0])),
14933    // avgBitrate
14934    MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
14935    // hSpacing
14936    hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
14937    // vSpacing
14938    vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
14939  }
14940}
14941MP4.types = void 0;
14942MP4.HDLR_TYPES = void 0;
14943MP4.STTS = void 0;
14944MP4.STSC = void 0;
14945MP4.STCO = void 0;
14946MP4.STSZ = void 0;
14947MP4.VMHD = void 0;
14948MP4.SMHD = void 0;
14949MP4.STSD = void 0;
14950MP4.FTYP = void 0;
14951MP4.DINF = void 0;
14952
14953const MPEG_TS_CLOCK_FREQ_HZ = 90000;
14954function toTimescaleFromBase(baseTime, destScale, srcBase = 1, round = false) {
14955  const result = baseTime * destScale * srcBase; // equivalent to `(value * scale) / (1 / base)`
14956  return round ? Math.round(result) : result;
14957}
14958function toTimescaleFromScale(baseTime, destScale, srcScale = 1, round = false) {
14959  return toTimescaleFromBase(baseTime, destScale, 1 / srcScale, round);
14960}
14961function toMsFromMpegTsClock(baseTime, round = false) {
14962  return toTimescaleFromBase(baseTime, 1000, 1 / MPEG_TS_CLOCK_FREQ_HZ, round);
14963}
14964function toMpegTsClockFromTimescale(baseTime, srcScale = 1) {
14965  return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale);
14966}
14967function timestampToString(timestamp) {
14968  const {
14969    baseTime,
14970    timescale,
14971    trackId
14972  } = timestamp;
14973  return `${baseTime / timescale} (${baseTime}/${timescale}) trackId: ${trackId}`;
14974}
14975
14976const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds
14977const AAC_SAMPLES_PER_FRAME = 1024;
14978const MPEG_AUDIO_SAMPLE_PER_FRAME = 1152;
14979const AC3_SAMPLES_PER_FRAME = 1536;
14980let chromeVersion = null;
14981let safariWebkitVersion = null;
14982function createMp4Sample(isKeyframe, duration, size, cts) {
14983  return {
14984    duration,
14985    size,
14986    cts,
14987    flags: {
14988      isLeading: 0,
14989      isDependedOn: 0,
14990      hasRedundancy: 0,
14991      degradPrio: 0,
14992      dependsOn: isKeyframe ? 2 : 1,
14993      isNonSync: isKeyframe ? 0 : 1
14994    }
14995  };
14996}
14997class MP4Remuxer extends Logger {
14998  constructor(observer, config, typeSupported, logger) {
14999    super('mp4-remuxer', logger);
15000    this.observer = void 0;
15001    this.config = void 0;
15002    this.typeSupported = void 0;
15003    this.ISGenerated = false;
15004    this._initPTS = null;
15005    this._initDTS = null;
15006    this.nextVideoTs = null;
15007    this.nextAudioTs = null;
15008    this.videoSampleDuration = null;
15009    this.isAudioContiguous = false;
15010    this.isVideoContiguous = false;
15011    this.videoTrackConfig = void 0;
15012    this.observer = observer;
15013    this.config = config;
15014    this.typeSupported = typeSupported;
15015    this.ISGenerated = false;
15016    if (chromeVersion === null) {
15017      const userAgent = navigator.userAgent || '';
15018      const result = userAgent.match(/Chrome\/(\d+)/i);
15019      chromeVersion = result ? parseInt(result[1]) : 0;
15020    }
15021    if (safariWebkitVersion === null) {
15022      const result = navigator.userAgent.match(/Safari\/(\d+)/i);
15023      safariWebkitVersion = result ? parseInt(result[1]) : 0;
15024    }
15025  }
15026  destroy() {
15027    // @ts-ignore
15028    this.config = this.videoTrackConfig = this._initPTS = this._initDTS = null;
15029  }
15030  resetTimeStamp(defaultTimeStamp) {
15031    const initPTS = this._initPTS;
15032    if (!initPTS || !defaultTimeStamp || defaultTimeStamp.trackId !== initPTS.trackId || defaultTimeStamp.baseTime !== initPTS.baseTime || defaultTimeStamp.timescale !== initPTS.timescale) {
15033      this.log(`Reset initPTS: ${initPTS ? timestampToString(initPTS) : initPTS} > ${defaultTimeStamp ? timestampToString(defaultTimeStamp) : defaultTimeStamp}`);
15034    }
15035    this._initPTS = this._initDTS = defaultTimeStamp;
15036  }
15037  resetNextTimestamp() {
15038    this.log('reset next timestamp');
15039    this.isVideoContiguous = false;
15040    this.isAudioContiguous = false;
15041  }
15042  resetInitSegment() {
15043    this.log('ISGenerated flag reset');
15044    this.ISGenerated = false;
15045    this.videoTrackConfig = undefined;
15046  }
15047  getVideoStartPts(videoSamples) {
15048    // Get the minimum PTS value relative to the first sample's PTS, normalized for 33-bit wrapping
15049    let rolloverDetected = false;
15050    const firstPts = videoSamples[0].pts;
15051    const startPTS = videoSamples.reduce((minPTS, sample) => {
15052      let pts = sample.pts;
15053      let delta = pts - minPTS;
15054      if (delta < -4294967296) {
15055        // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation
15056        rolloverDetected = true;
15057        pts = normalizePts(pts, firstPts);
15058        delta = pts - minPTS;
15059      }
15060      if (delta > 0) {
15061        return minPTS;
15062      }
15063      return pts;
15064    }, firstPts);
15065    if (rolloverDetected) {
15066      this.debug('PTS rollover detected');
15067    }
15068    return startPTS;
15069  }
15070  remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType) {
15071    let video;
15072    let audio;
15073    let initSegment;
15074    let text;
15075    let id3;
15076    let independent;
15077    let audioTimeOffset = timeOffset;
15078    let videoTimeOffset = timeOffset;
15079
15080    // If we're remuxing audio and video progressively, wait until we've received enough samples for each track before proceeding.
15081    // This is done to synchronize the audio and video streams. We know if the current segment will have samples if the "pid"
15082    // parameter is greater than -1. The pid is set when the PMT is parsed, which contains the tracks list.
15083    // However, if the initSegment has already been generated, or we've reached the end of a segment (flush),
15084    // then we can remux one track without waiting for the other.
15085    const hasAudio = audioTrack.pid > -1;
15086    const hasVideo = videoTrack.pid > -1;
15087    const length = videoTrack.samples.length;
15088    const enoughAudioSamples = audioTrack.samples.length > 0;
15089    const enoughVideoSamples = flush && length > 0 || length > 1;
15090    const canRemuxAvc = (!hasAudio || enoughAudioSamples) && (!hasVideo || enoughVideoSamples) || this.ISGenerated || flush;
15091    if (canRemuxAvc) {
15092      if (this.ISGenerated) {
15093        var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2;
15094        const config = this.videoTrackConfig;
15095        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])) || !config && enoughVideoSamples || this.nextAudioTs === null && enoughAudioSamples) {
15096          this.resetInitSegment();
15097        }
15098      }
15099      if (!this.ISGenerated) {
15100        initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
15101      }
15102      const isVideoContiguous = this.isVideoContiguous;
15103      let firstKeyFrameIndex = -1;
15104      let firstKeyFramePTS;
15105      if (enoughVideoSamples) {
15106        firstKeyFrameIndex = findKeyframeIndex(videoTrack.samples);
15107        if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) {
15108          independent = true;
15109          if (firstKeyFrameIndex > 0) {
15110            this.warn(`Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`);
15111            const startPTS = this.getVideoStartPts(videoTrack.samples);
15112            videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex);
15113            videoTrack.dropped += firstKeyFrameIndex;
15114            videoTimeOffset += (videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale;
15115            firstKeyFramePTS = videoTimeOffset;
15116          } else if (firstKeyFrameIndex === -1) {
15117            this.warn(`No keyframe found out of ${length} video samples`);
15118            independent = false;
15119          }
15120        }
15121      }
15122      if (this.ISGenerated) {
15123        if (enoughAudioSamples && enoughVideoSamples) {
15124          // timeOffset is expected to be the offset of the first timestamp of this fragment (first DTS)
15125          // if first audio DTS is not aligned with first video DTS then we need to take that into account
15126          // when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small
15127          // drift between audio and video streams
15128          const startPTS = this.getVideoStartPts(videoTrack.samples);
15129          const tsDelta = normalizePts(audioTrack.samples[0].pts, startPTS) - startPTS;
15130          const audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale;
15131          audioTimeOffset += Math.max(0, audiovideoTimestampDelta);
15132          videoTimeOffset += Math.max(0, -audiovideoTimestampDelta);
15133        }
15134
15135        // Purposefully remuxing audio before video, so that remuxVideo can use nextAudioPts, which is calculated in remuxAudio.
15136        if (enoughAudioSamples) {
15137          // if initSegment was generated without audio samples, regenerate it again
15138          if (!audioTrack.samplerate) {
15139            this.warn('regenerate InitSegment as audio detected');
15140            initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
15141          }
15142          audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === PlaylistLevelType.AUDIO ? videoTimeOffset : undefined);
15143          if (enoughVideoSamples) {
15144            const audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0;
15145            // if initSegment was generated without video samples, regenerate it again
15146            if (!videoTrack.inputTimeScale) {
15147              this.warn('regenerate InitSegment as video detected');
15148              initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset);
15149            }
15150            video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength);
15151          }
15152        } else if (enoughVideoSamples) {
15153          video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0);
15154        }
15155        if (video) {
15156          video.firstKeyFrame = firstKeyFrameIndex;
15157          video.independent = firstKeyFrameIndex !== -1;
15158          video.firstKeyFramePTS = firstKeyFramePTS;
15159        }
15160      }
15161    }
15162
15163    // Allow ID3 and text to remux, even if more audio/video samples are required
15164    if (this.ISGenerated && this._initPTS && this._initDTS) {
15165      if (id3Track.samples.length) {
15166        id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS);
15167      }
15168      if (textTrack.samples.length) {
15169        text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS);
15170      }
15171    }
15172    return {
15173      audio,
15174      video,
15175      initSegment,
15176      independent,
15177      text,
15178      id3
15179    };
15180  }
15181  computeInitPts(basetime, timescale, presentationTime, type) {
15182    const offset = Math.round(presentationTime * timescale);
15183    let timestamp = normalizePts(basetime, offset);
15184    if (timestamp < offset + timescale) {
15185      this.log(`Adjusting PTS for rollover in timeline near ${(offset - timestamp) / timescale} ${type}`);
15186      while (timestamp < offset + timescale) {
15187        timestamp += 8589934592;
15188      }
15189    }
15190    return timestamp - offset;
15191  }
15192  generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset) {
15193    const audioSamples = audioTrack.samples;
15194    const videoSamples = videoTrack.samples;
15195    const typeSupported = this.typeSupported;
15196    const tracks = {};
15197    const _initPTS = this._initPTS;
15198    let computePTSDTS = !_initPTS || accurateTimeOffset;
15199    let container = 'audio/mp4';
15200    let initPTS;
15201    let initDTS;
15202    let timescale;
15203    let trackId = -1;
15204    if (computePTSDTS) {
15205      initPTS = initDTS = Infinity;
15206    }
15207    if (audioTrack.config && audioSamples.length) {
15208      // let's use audio sampling rate as MP4 time scale.
15209      // rationale is that there is a integer nb of audio frames per audio sample (1024 for AAC)
15210      // using audio sampling rate here helps having an integer MP4 frame duration
15211      // this avoids potential rounding issue and AV sync issue
15212      audioTrack.timescale = audioTrack.samplerate;
15213      switch (audioTrack.segmentCodec) {
15214        case 'mp3':
15215          if (typeSupported.mpeg) {
15216            // Chrome and Safari
15217            container = 'audio/mpeg';
15218            audioTrack.codec = '';
15219          } else if (typeSupported.mp3) {
15220            // Firefox
15221            audioTrack.codec = 'mp3';
15222          }
15223          break;
15224        case 'ac3':
15225          audioTrack.codec = 'ac-3';
15226          break;
15227      }
15228      tracks.audio = {
15229        id: 'audio',
15230        container: container,
15231        codec: audioTrack.codec,
15232        initSegment: audioTrack.segmentCodec === 'mp3' && typeSupported.mpeg ? new Uint8Array(0) : MP4.initSegment([audioTrack]),
15233        metadata: {
15234          channelCount: audioTrack.channelCount
15235        }
15236      };
15237      if (computePTSDTS) {
15238        trackId = audioTrack.id;
15239        timescale = audioTrack.inputTimeScale;
15240        if (!_initPTS || timescale !== _initPTS.timescale) {
15241          // remember first PTS of this demuxing context. for audio, PTS = DTS
15242          initPTS = initDTS = this.computeInitPts(audioSamples[0].pts, timescale, timeOffset, 'audio');
15243        } else {
15244          computePTSDTS = false;
15245        }
15246      }
15247    }
15248    if (videoTrack.sps && videoTrack.pps && videoSamples.length) {
15249      // let's use input time scale as MP4 video timescale
15250      // we use input time scale straight away to avoid rounding issues on frame duration / cts computation
15251      videoTrack.timescale = videoTrack.inputTimeScale;
15252      tracks.video = {
15253        id: 'main',
15254        container: 'video/mp4',
15255        codec: videoTrack.codec,
15256        initSegment: MP4.initSegment([videoTrack]),
15257        metadata: {
15258          width: videoTrack.width,
15259          height: videoTrack.height
15260        }
15261      };
15262      if (computePTSDTS) {
15263        trackId = videoTrack.id;
15264        timescale = videoTrack.inputTimeScale;
15265        if (!_initPTS || timescale !== _initPTS.timescale) {
15266          const basePTS = this.getVideoStartPts(videoSamples);
15267          const baseDTS = normalizePts(videoSamples[0].dts, basePTS);
15268          const videoInitDTS = this.computeInitPts(baseDTS, timescale, timeOffset, 'video');
15269          const videoInitPTS = this.computeInitPts(basePTS, timescale, timeOffset, 'video');
15270          initDTS = Math.min(initDTS, videoInitDTS);
15271          initPTS = Math.min(initPTS, videoInitPTS);
15272        } else {
15273          computePTSDTS = false;
15274        }
15275      }
15276      this.videoTrackConfig = {
15277        width: videoTrack.width,
15278        height: videoTrack.height,
15279        pixelRatio: videoTrack.pixelRatio
15280      };
15281    }
15282    if (Object.keys(tracks).length) {
15283      this.ISGenerated = true;
15284      if (computePTSDTS) {
15285        if (_initPTS) {
15286          this.warn(`Timestamps at playlist time: ${accurateTimeOffset ? '' : '~'}${timeOffset} ${initPTS / timescale} != initPTS: ${_initPTS.baseTime / _initPTS.timescale} (${_initPTS.baseTime}/${_initPTS.timescale}) trackId: ${_initPTS.trackId}`);
15287        }
15288        this.log(`Found initPTS at playlist time: ${timeOffset} offset: ${initPTS / timescale} (${initPTS}/${timescale}) trackId: ${trackId}`);
15289        this._initPTS = {
15290          baseTime: initPTS,
15291          timescale: timescale,
15292          trackId: trackId
15293        };
15294        this._initDTS = {
15295          baseTime: initDTS,
15296          timescale: timescale,
15297          trackId: trackId
15298        };
15299      } else {
15300        initPTS = timescale = undefined;
15301      }
15302      return {
15303        tracks,
15304        initPTS,
15305        timescale,
15306        trackId
15307      };
15308    }
15309  }
15310  remuxVideo(track, timeOffset, contiguous, audioTrackLength) {
15311    const timeScale = track.inputTimeScale;
15312    const inputSamples = track.samples;
15313    const outputSamples = [];
15314    const nbSamples = inputSamples.length;
15315    const initPTS = this._initPTS;
15316    const initTime = initPTS.baseTime * timeScale / initPTS.timescale;
15317    let nextVideoTs = this.nextVideoTs;
15318    let offset = 8;
15319    let mp4SampleDuration = this.videoSampleDuration;
15320    let firstDTS;
15321    let lastDTS;
15322    let minPTS = Number.POSITIVE_INFINITY;
15323    let maxPTS = Number.NEGATIVE_INFINITY;
15324    let sortSamples = false;
15325
15326    // if parsed fragment is contiguous with last one, let's use last DTS value as reference
15327    if (!contiguous || nextVideoTs === null) {
15328      const pts = initTime + timeOffset * timeScale;
15329      const cts = inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts);
15330      if (chromeVersion && nextVideoTs !== null && Math.abs(pts - cts - (nextVideoTs + initTime)) < 15000) {
15331        // treat as contigous to adjust samples that would otherwise produce video buffer gaps in Chrome
15332        contiguous = true;
15333      } else {
15334        // if not contiguous, let's use target timeOffset
15335        nextVideoTs = pts - cts - initTime;
15336      }
15337    }
15338
15339    // PTS is coded on 33bits, and can loop from -2^32 to 2^32
15340    // PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value
15341    const nextVideoPts = nextVideoTs + initTime;
15342    for (let i = 0; i < nbSamples; i++) {
15343      const sample = inputSamples[i];
15344      sample.pts = normalizePts(sample.pts, nextVideoPts);
15345      sample.dts = normalizePts(sample.dts, nextVideoPts);
15346      if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) {
15347        sortSamples = true;
15348      }
15349    }
15350
15351    // sort video samples by DTS then PTS then demux id order
15352    if (sortSamples) {
15353      inputSamples.sort(function (a, b) {
15354        const deltadts = a.dts - b.dts;
15355        const deltapts = a.pts - b.pts;
15356        return deltadts || deltapts;
15357      });
15358    }
15359
15360    // Get first/last DTS
15361    firstDTS = inputSamples[0].dts;
15362    lastDTS = inputSamples[inputSamples.length - 1].dts;
15363
15364    // Sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS
15365    // set this constant duration as being the avg delta between consecutive DTS.
15366    const inputDuration = lastDTS - firstDTS;
15367    const averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) : mp4SampleDuration || track.inputTimeScale / 30;
15368
15369    // if fragment are contiguous, detect hole/overlapping between fragments
15370    if (contiguous) {
15371      // check timestamp continuity across consecutive fragments (this is to remove inter-fragment gap/hole)
15372      const delta = firstDTS - nextVideoPts;
15373      const foundHole = delta > averageSampleDuration;
15374      const foundOverlap = delta < -1;
15375      if (foundHole || foundOverlap) {
15376        if (foundHole) {
15377          this.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
15378        } else {
15379          this.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
15380        }
15381        if (!foundOverlap || nextVideoPts >= inputSamples[0].pts || chromeVersion) {
15382          firstDTS = nextVideoPts;
15383          const firstPTS = inputSamples[0].pts - delta;
15384          if (foundHole) {
15385            inputSamples[0].dts = firstDTS;
15386            inputSamples[0].pts = firstPTS;
15387          } else {
15388            let isPTSOrderRetained = true;
15389            for (let i = 0; i < inputSamples.length; i++) {
15390              if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
15391                break;
15392              }
15393              const prevPTS = inputSamples[i].pts;
15394              inputSamples[i].dts -= delta;
15395              inputSamples[i].pts -= delta;
15396
15397              // check to see if this sample's PTS order has changed
15398              // relative to the next one
15399              if (i < inputSamples.length - 1) {
15400                const nextSamplePTS = inputSamples[i + 1].pts;
15401                const currentSamplePTS = inputSamples[i].pts;
15402                const currentOrder = nextSamplePTS <= currentSamplePTS;
15403                const prevOrder = nextSamplePTS <= prevPTS;
15404                isPTSOrderRetained = currentOrder == prevOrder;
15405              }
15406            }
15407          }
15408          this.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
15409        }
15410      }
15411    }
15412    firstDTS = Math.max(0, firstDTS);
15413    let nbNalu = 0;
15414    let naluLen = 0;
15415    let dtsStep = firstDTS;
15416    for (let i = 0; i < nbSamples; i++) {
15417      // compute total/avc sample length and nb of NAL units
15418      const sample = inputSamples[i];
15419      const units = sample.units;
15420      const nbUnits = units.length;
15421      let sampleLen = 0;
15422      for (let j = 0; j < nbUnits; j++) {
15423        sampleLen += units[j].data.length;
15424      }
15425      naluLen += sampleLen;
15426      nbNalu += nbUnits;
15427      sample.length = sampleLen;
15428
15429      // ensure sample monotonic DTS
15430      if (sample.dts < dtsStep) {
15431        sample.dts = dtsStep;
15432        dtsStep += averageSampleDuration / 4 | 0 || 1;
15433      } else {
15434        dtsStep = sample.dts;
15435      }
15436      minPTS = Math.min(sample.pts, minPTS);
15437      maxPTS = Math.max(sample.pts, maxPTS);
15438    }
15439    lastDTS = inputSamples[nbSamples - 1].dts;
15440
15441    /* concatenate the video data and construct the mdat in place
15442      (need 8 more bytes to fill length and mpdat type) */
15443    const mdatSize = naluLen + 4 * nbNalu + 8;
15444    let mdat;
15445    try {
15446      mdat = new Uint8Array(mdatSize);
15447    } catch (err) {
15448      this.observer.emit(Events.ERROR, Events.ERROR, {
15449        type: ErrorTypes.MUX_ERROR,
15450        details: ErrorDetails.REMUX_ALLOC_ERROR,
15451        fatal: false,
15452        error: err,
15453        bytes: mdatSize,
15454        reason: `fail allocating video mdat ${mdatSize}`
15455      });
15456      return;
15457    }
15458    const view = new DataView(mdat.buffer);
15459    view.setUint32(0, mdatSize);
15460    mdat.set(MP4.types.mdat, 4);
15461    let stretchedLastFrame = false;
15462    let minDtsDelta = Number.POSITIVE_INFINITY;
15463    let minPtsDelta = Number.POSITIVE_INFINITY;
15464    let maxDtsDelta = Number.NEGATIVE_INFINITY;
15465    let maxPtsDelta = Number.NEGATIVE_INFINITY;
15466    for (let i = 0; i < nbSamples; i++) {
15467      const VideoSample = inputSamples[i];
15468      const VideoSampleUnits = VideoSample.units;
15469      let mp4SampleLength = 0;
15470      // convert NALU bitstream to MP4 format (prepend NALU with size field)
15471      for (let j = 0, nbUnits = VideoSampleUnits.length; j < nbUnits; j++) {
15472        const unit = VideoSampleUnits[j];
15473        const unitData = unit.data;
15474        const unitDataLen = unit.data.byteLength;
15475        view.setUint32(offset, unitDataLen);
15476        offset += 4;
15477        mdat.set(unitData, offset);
15478        offset += unitDataLen;
15479        mp4SampleLength += 4 + unitDataLen;
15480      }
15481
15482      // expected sample duration is the Decoding Timestamp diff of consecutive samples
15483      let ptsDelta;
15484      if (i < nbSamples - 1) {
15485        mp4SampleDuration = inputSamples[i + 1].dts - VideoSample.dts;
15486        ptsDelta = inputSamples[i + 1].pts - VideoSample.pts;
15487      } else {
15488        const config = this.config;
15489        const lastFrameDuration = i > 0 ? VideoSample.dts - inputSamples[i - 1].dts : averageSampleDuration;
15490        ptsDelta = i > 0 ? VideoSample.pts - inputSamples[i - 1].pts : averageSampleDuration;
15491        if (config.stretchShortVideoTrack && this.nextAudioTs !== null) {
15492          // In some cases, a segment's audio track duration may exceed the video track duration.
15493          // Since we've already remuxed audio, and we know how long the audio track is, we look to
15494          // see if the delta to the next segment is longer than maxBufferHole.
15495          // If so, playback would potentially get stuck, so we artificially inflate
15496          // the duration of the last frame to minimize any potential gap between segments.
15497          const gapTolerance = Math.floor(config.maxBufferHole * timeScale);
15498          const deltaToFrameEnd = (audioTrackLength ? minPTS + audioTrackLength * timeScale : this.nextAudioTs + initTime) - VideoSample.pts;
15499          if (deltaToFrameEnd > gapTolerance) {
15500            // We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video
15501            // frame overlap. maxBufferHole should be >> lastFrameDuration anyway.
15502            mp4SampleDuration = deltaToFrameEnd - lastFrameDuration;
15503            if (mp4SampleDuration < 0) {
15504              mp4SampleDuration = lastFrameDuration;
15505            } else {
15506              stretchedLastFrame = true;
15507            }
15508            this.log(`It is approximately ${deltaToFrameEnd / 90} ms to the next segment; using duration ${mp4SampleDuration / 90} ms for the last video frame.`);
15509          } else {
15510            mp4SampleDuration = lastFrameDuration;
15511          }
15512        } else {
15513          mp4SampleDuration = lastFrameDuration;
15514        }
15515      }
15516      const compositionTimeOffset = Math.round(VideoSample.pts - VideoSample.dts);
15517      minDtsDelta = Math.min(minDtsDelta, mp4SampleDuration);
15518      maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration);
15519      minPtsDelta = Math.min(minPtsDelta, ptsDelta);
15520      maxPtsDelta = Math.max(maxPtsDelta, ptsDelta);
15521      outputSamples.push(createMp4Sample(VideoSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset));
15522    }
15523    if (outputSamples.length) {
15524      if (chromeVersion) {
15525        if (chromeVersion < 70) {
15526          // Chrome workaround, mark first sample as being a Random Access Point (keyframe) to avoid sourcebuffer append issue
15527          // https://code.google.com/p/chromium/issues/detail?id=229412
15528          const flags = outputSamples[0].flags;
15529          flags.dependsOn = 2;
15530          flags.isNonSync = 0;
15531        }
15532      } else if (safariWebkitVersion) {
15533        // Fix for "CNN special report, with CC" in test-streams (Safari browser only)
15534        // Ignore DTS when frame durations are irregular. Safari MSE does not handle this leading to gaps.
15535        if (maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta && averageSampleDuration / maxDtsDelta < 0.025 && outputSamples[0].cts === 0) {
15536          this.warn('Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.');
15537          let dts = firstDTS;
15538          for (let i = 0, len = outputSamples.length; i < len; i++) {
15539            const nextDts = dts + outputSamples[i].duration;
15540            const pts = dts + outputSamples[i].cts;
15541            if (i < len - 1) {
15542              const nextPts = nextDts + outputSamples[i + 1].cts;
15543              outputSamples[i].duration = nextPts - pts;
15544            } else {
15545              outputSamples[i].duration = i ? outputSamples[i - 1].duration : averageSampleDuration;
15546            }
15547            outputSamples[i].cts = 0;
15548            dts = nextDts;
15549          }
15550        }
15551      }
15552    }
15553    // next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
15554    mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
15555    const endDTS = lastDTS + mp4SampleDuration;
15556    this.nextVideoTs = nextVideoTs = endDTS - initTime;
15557    this.videoSampleDuration = mp4SampleDuration;
15558    this.isVideoContiguous = true;
15559    const moof = MP4.moof(track.sequenceNumber++, firstDTS, _extends(track, {
15560      samples: outputSamples
15561    }));
15562    const type = 'video';
15563    const data = {
15564      data1: moof,
15565      data2: mdat,
15566      startPTS: (minPTS - initTime) / timeScale,
15567      endPTS: (maxPTS + mp4SampleDuration - initTime) / timeScale,
15568      startDTS: (firstDTS - initTime) / timeScale,
15569      endDTS: nextVideoTs / timeScale,
15570      type,
15571      hasAudio: false,
15572      hasVideo: true,
15573      nb: outputSamples.length,
15574      dropped: track.dropped
15575    };
15576    track.samples = [];
15577    track.dropped = 0;
15578    return data;
15579  }
15580  getSamplesPerFrame(track) {
15581    switch (track.segmentCodec) {
15582      case 'mp3':
15583        return MPEG_AUDIO_SAMPLE_PER_FRAME;
15584      case 'ac3':
15585        return AC3_SAMPLES_PER_FRAME;
15586      default:
15587        return AAC_SAMPLES_PER_FRAME;
15588    }
15589  }
15590  remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset) {
15591    const inputTimeScale = track.inputTimeScale;
15592    const mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale;
15593    const scaleFactor = inputTimeScale / mp4timeScale;
15594    const mp4SampleDuration = this.getSamplesPerFrame(track);
15595    const inputSampleDuration = mp4SampleDuration * scaleFactor;
15596    const initPTS = this._initPTS;
15597    const rawMPEG = track.segmentCodec === 'mp3' && this.typeSupported.mpeg;
15598    const outputSamples = [];
15599    const alignedWithVideo = videoTimeOffset !== undefined;
15600    let inputSamples = track.samples;
15601    let offset = rawMPEG ? 0 : 8;
15602    let nextAudioTs = this.nextAudioTs || -1;
15603
15604    // window.audioSamples ? window.audioSamples.push(inputSamples.map(s => s.pts)) : (window.audioSamples = [inputSamples.map(s => s.pts)]);
15605
15606    // for audio samples, also consider consecutive fragments as being contiguous (even if a level switch occurs),
15607    // for sake of clarity:
15608    // consecutive fragments are frags with
15609    //  - less than 100ms gaps between new time offset (if accurate) and next expected PTS OR
15610    //  - less than 20 audio frames distance
15611    // contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1)
15612    // this helps ensuring audio continuity
15613    // and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame
15614    const initTime = initPTS.baseTime * inputTimeScale / initPTS.timescale;
15615    const timeOffsetMpegTS = initTime + timeOffset * inputTimeScale;
15616    this.isAudioContiguous = contiguous = contiguous || inputSamples.length && nextAudioTs > 0 && (accurateTimeOffset && Math.abs(timeOffsetMpegTS - (nextAudioTs + initTime)) < 9000 || Math.abs(normalizePts(inputSamples[0].pts, timeOffsetMpegTS) - (nextAudioTs + initTime)) < 20 * inputSampleDuration);
15617
15618    // compute normalized PTS
15619    inputSamples.forEach(function (sample) {
15620      sample.pts = normalizePts(sample.pts, timeOffsetMpegTS);
15621    });
15622    if (!contiguous || nextAudioTs < 0) {
15623      const sampleCount = inputSamples.length;
15624      // filter out sample with negative PTS that are not playable anyway
15625      // if we don't remove these negative samples, they will shift all audio samples forward.
15626      // leading to audio overlap between current / next fragment
15627      inputSamples = inputSamples.filter(sample => sample.pts >= 0);
15628      if (sampleCount !== inputSamples.length) {
15629        this.warn(`Removed ${inputSamples.length - sampleCount} of ${sampleCount} samples (initPTS ${initTime} / ${inputTimeScale})`);
15630      }
15631
15632      // in case all samples have negative PTS, and have been filtered out, return now
15633      if (!inputSamples.length) {
15634        return;
15635      }
15636      if (videoTimeOffset === 0) {
15637        // Set the start to match video so that start gaps larger than inputSampleDuration are filled with silence
15638        nextAudioTs = 0;
15639      } else if (accurateTimeOffset && !alignedWithVideo) {
15640        // When not seeking, not live, and LevelDetails.PTSKnown, use fragment start as predicted next audio PTS
15641        nextAudioTs = Math.max(0, timeOffsetMpegTS - initTime);
15642      } else {
15643        // if frags are not contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS
15644        nextAudioTs = inputSamples[0].pts - initTime;
15645      }
15646    }
15647
15648    // If the audio track is missing samples, the frames seem to get "left-shifted" within the
15649    // resulting mp4 segment, causing sync issues and leaving gaps at the end of the audio segment.
15650    // In an effort to prevent this from happening, we inject frames here where there are gaps.
15651    // When possible, we inject a silent frame; when that's not possible, we duplicate the last
15652    // frame.
15653
15654    if (track.segmentCodec === 'aac') {
15655      const maxAudioFramesDrift = this.config.maxAudioFramesDrift;
15656      for (let i = 0, nextPts = nextAudioTs + initTime; i < inputSamples.length; i++) {
15657        // First, let's see how far off this frame is from where we expect it to be
15658        const sample = inputSamples[i];
15659        const pts = sample.pts;
15660        const delta = pts - nextPts;
15661        const duration = Math.abs(1000 * delta / inputTimeScale);
15662
15663        // When remuxing with video, if we're overlapping by more than a duration, drop this sample to stay in sync
15664        if (delta <= -maxAudioFramesDrift * inputSampleDuration && alignedWithVideo) {
15665          if (i === 0) {
15666            this.warn(`Audio frame @ ${(pts / inputTimeScale).toFixed(3)}s overlaps marker by ${Math.round(1000 * delta / inputTimeScale)} ms.`);
15667            this.nextAudioTs = nextAudioTs = pts - initTime;
15668            nextPts = pts;
15669          }
15670        } // eslint-disable-line brace-style
15671
15672        // Insert missing frames if:
15673        // 1: We're more than maxAudioFramesDrift frame away
15674        // 2: Not more than MAX_SILENT_FRAME_DURATION away
15675        // 3: currentTime (aka nextPtsNorm) is not 0
15676        // 4: remuxing with video (videoTimeOffset !== undefined)
15677        else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && alignedWithVideo) {
15678          let missing = Math.round(delta / inputSampleDuration);
15679          // Adjust nextPts so that silent samples are aligned with media pts. This will prevent media samples from
15680          // later being shifted if nextPts is based on timeOffset and delta is not a multiple of inputSampleDuration.
15681          nextPts = pts - missing * inputSampleDuration;
15682          while (nextPts < 0 && missing && inputSampleDuration) {
15683            missing--;
15684            nextPts += inputSampleDuration;
15685          }
15686          if (i === 0) {
15687            this.nextAudioTs = nextAudioTs = nextPts - initTime;
15688          }
15689          this.warn(`Injecting ${missing} audio frames @ ${((nextPts - initTime) / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
15690          for (let j = 0; j < missing; j++) {
15691            let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
15692            if (!fillFrame) {
15693              this.log('Unable to get silent frame for given audio codec; duplicating last frame instead.');
15694              fillFrame = sample.unit.subarray();
15695            }
15696            inputSamples.splice(i, 0, {
15697              unit: fillFrame,
15698              pts: nextPts
15699            });
15700            nextPts += inputSampleDuration;
15701            i++;
15702          }
15703        }
15704        sample.pts = nextPts;
15705        nextPts += inputSampleDuration;
15706      }
15707    }
15708    let firstPTS = null;
15709    let lastPTS = null;
15710    let mdat;
15711    let mdatSize = 0;
15712    let sampleLength = inputSamples.length;
15713    while (sampleLength--) {
15714      mdatSize += inputSamples[sampleLength].unit.byteLength;
15715    }
15716    for (let j = 0, _nbSamples = inputSamples.length; j < _nbSamples; j++) {
15717      const audioSample = inputSamples[j];
15718      const unit = audioSample.unit;
15719      let pts = audioSample.pts;
15720      if (lastPTS !== null) {
15721        // If we have more than one sample, set the duration of the sample to the "real" duration; the PTS diff with
15722        // the previous sample
15723        const prevSample = outputSamples[j - 1];
15724        prevSample.duration = Math.round((pts - lastPTS) / scaleFactor);
15725      } else {
15726        if (contiguous && track.segmentCodec === 'aac') {
15727          // set PTS/DTS to expected PTS/DTS
15728          pts = nextAudioTs + initTime;
15729        }
15730        // remember first PTS of our audioSamples
15731        firstPTS = pts;
15732        if (mdatSize > 0) {
15733          /* concatenate the audio data and construct the mdat in place
15734            (need 8 more bytes to fill length and mdat type) */
15735          mdatSize += offset;
15736          try {
15737            mdat = new Uint8Array(mdatSize);
15738          } catch (err) {
15739            this.observer.emit(Events.ERROR, Events.ERROR, {
15740              type: ErrorTypes.MUX_ERROR,
15741              details: ErrorDetails.REMUX_ALLOC_ERROR,
15742              fatal: false,
15743              error: err,
15744              bytes: mdatSize,
15745              reason: `fail allocating audio mdat ${mdatSize}`
15746            });
15747            return;
15748          }
15749          if (!rawMPEG) {
15750            const view = new DataView(mdat.buffer);
15751            view.setUint32(0, mdatSize);
15752            mdat.set(MP4.types.mdat, 4);
15753          }
15754        } else {
15755          // no audio samples
15756          return;
15757        }
15758      }
15759      mdat.set(unit, offset);
15760      const unitLen = unit.byteLength;
15761      offset += unitLen;
15762      // Default the sample's duration to the computed mp4SampleDuration, which will either be 1024 for AAC or 1152 for MPEG
15763      // In the case that we have 1 sample, this will be the duration. If we have more than one sample, the duration
15764      // becomes the PTS diff with the previous sample
15765      outputSamples.push(createMp4Sample(true, mp4SampleDuration, unitLen, 0));
15766      lastPTS = pts;
15767    }
15768
15769    // We could end up with no audio samples if all input samples were overlapping with the previously remuxed ones
15770    const nbSamples = outputSamples.length;
15771    if (!nbSamples) {
15772      return;
15773    }
15774
15775    // The next audio sample PTS should be equal to last sample PTS + duration
15776    const lastSample = outputSamples[outputSamples.length - 1];
15777    nextAudioTs = lastPTS - initTime;
15778    this.nextAudioTs = nextAudioTs + scaleFactor * lastSample.duration;
15779
15780    // Set the track samples from inputSamples to outputSamples before remuxing
15781    const moof = rawMPEG ? new Uint8Array(0) : MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, {
15782      samples: outputSamples
15783    }));
15784
15785    // Clear the track samples. This also clears the samples array in the demuxer, since the reference is shared
15786    track.samples = [];
15787    const start = (firstPTS - initTime) / inputTimeScale;
15788    const end = this.nextAudioTs / inputTimeScale;
15789    const type = 'audio';
15790    const audioData = {
15791      data1: moof,
15792      data2: mdat,
15793      startPTS: start,
15794      endPTS: end,
15795      startDTS: start,
15796      endDTS: end,
15797      type,
15798      hasAudio: true,
15799      hasVideo: false,
15800      nb: nbSamples
15801    };
15802    this.isAudioContiguous = true;
15803    return audioData;
15804  }
15805}
15806function normalizePts(value, reference) {
15807  let offset;
15808  if (reference === null) {
15809    return value;
15810  }
15811  if (reference < value) {
15812    // - 2^33
15813    offset = -8589934592;
15814  } else {
15815    // + 2^33
15816    offset = 8589934592;
15817  }
15818  /* PTS is 33bit (from 0 to 2^33 -1)
15819    if diff between value and reference is bigger than half of the amplitude (2^32) then it means that
15820    PTS looping occured. fill the gap */
15821  while (Math.abs(value - reference) > 4294967296) {
15822    value += offset;
15823  }
15824  return value;
15825}
15826function findKeyframeIndex(samples) {
15827  for (let i = 0; i < samples.length; i++) {
15828    if (samples[i].key) {
15829      return i;
15830    }
15831  }
15832  return -1;
15833}
15834function flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS) {
15835  const length = track.samples.length;
15836  if (!length) {
15837    return;
15838  }
15839  const inputTimeScale = track.inputTimeScale;
15840  for (let index = 0; index < length; index++) {
15841    const sample = track.samples[index];
15842    // setting id3 pts, dts to relative time
15843    // using this._initPTS and this._initDTS to calculate relative time
15844    sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
15845    sample.dts = normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
15846  }
15847  const samples = track.samples;
15848  track.samples = [];
15849  return {
15850    samples
15851  };
15852}
15853function flushTextTrackUserdataCueSamples(track, timeOffset, initPTS) {
15854  const length = track.samples.length;
15855  if (!length) {
15856    return;
15857  }
15858  const inputTimeScale = track.inputTimeScale;
15859  for (let index = 0; index < length; index++) {
15860    const sample = track.samples[index];
15861    // setting text pts, dts to relative time
15862    // using this._initPTS and this._initDTS to calculate relative time
15863    sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale;
15864  }
15865  track.samples.sort((a, b) => a.pts - b.pts);
15866  const samples = track.samples;
15867  track.samples = [];
15868  return {
15869    samples
15870  };
15871}
15872
15873class PassThroughRemuxer extends Logger {
15874  constructor(observer, config, typeSupported, logger) {
15875    super('passthrough-remuxer', logger);
15876    this.emitInitSegment = false;
15877    this.audioCodec = void 0;
15878    this.videoCodec = void 0;
15879    this.initData = void 0;
15880    this.initPTS = null;
15881    this.initTracks = void 0;
15882    this.lastEndTime = null;
15883    this.isVideoContiguous = false;
15884  }
15885  destroy() {}
15886  resetTimeStamp(defaultInitPTS) {
15887    this.lastEndTime = null;
15888    const initPTS = this.initPTS;
15889    if (initPTS && defaultInitPTS) {
15890      if (initPTS.baseTime === defaultInitPTS.baseTime && initPTS.timescale === defaultInitPTS.timescale) {
15891        return;
15892      }
15893    }
15894    this.initPTS = defaultInitPTS;
15895  }
15896  resetNextTimestamp() {
15897    this.isVideoContiguous = false;
15898    this.lastEndTime = null;
15899  }
15900  resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata) {
15901    this.audioCodec = audioCodec;
15902    this.videoCodec = videoCodec;
15903    this.generateInitSegment(initSegment, decryptdata);
15904    this.emitInitSegment = true;
15905  }
15906  generateInitSegment(initSegment, decryptdata) {
15907    let {
15908      audioCodec,
15909      videoCodec
15910    } = this;
15911    if (!(initSegment != null && initSegment.byteLength)) {
15912      this.initTracks = undefined;
15913      this.initData = undefined;
15914      return;
15915    }
15916    const {
15917      audio,
15918      video
15919    } = this.initData = parseInitSegment(initSegment);
15920    if (decryptdata) {
15921      patchEncyptionData(initSegment, decryptdata);
15922    } else {
15923      const eitherTrack = audio || video;
15924      if (eitherTrack != null && eitherTrack.encrypted) {
15925        this.warn(`Init segment with encrypted track with has no key ("${eitherTrack.codec}")!`);
15926      }
15927    }
15928
15929    // Get codec from initSegment
15930    if (audio) {
15931      audioCodec = getParsedTrackCodec(audio, ElementaryStreamTypes.AUDIO, this);
15932    }
15933    if (video) {
15934      videoCodec = getParsedTrackCodec(video, ElementaryStreamTypes.VIDEO, this);
15935    }
15936    const tracks = {};
15937    if (audio && video) {
15938      tracks.audiovideo = {
15939        container: 'video/mp4',
15940        codec: audioCodec + ',' + videoCodec,
15941        supplemental: video.supplemental,
15942        encrypted: video.encrypted,
15943        initSegment,
15944        id: 'main'
15945      };
15946    } else if (audio) {
15947      tracks.audio = {
15948        container: 'audio/mp4',
15949        codec: audioCodec,
15950        encrypted: audio.encrypted,
15951        initSegment,
15952        id: 'audio'
15953      };
15954    } else if (video) {
15955      tracks.video = {
15956        container: 'video/mp4',
15957        codec: videoCodec,
15958        supplemental: video.supplemental,
15959        encrypted: video.encrypted,
15960        initSegment,
15961        id: 'main'
15962      };
15963    } else {
15964      this.warn('initSegment does not contain moov or trak boxes.');
15965    }
15966    this.initTracks = tracks;
15967  }
15968  remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset) {
15969    var _initData, _initData2;
15970    let {
15971      initPTS,
15972      lastEndTime
15973    } = this;
15974    const result = {
15975      audio: undefined,
15976      video: undefined,
15977      text: textTrack,
15978      id3: id3Track,
15979      initSegment: undefined
15980    };
15981
15982    // If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the
15983    // lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update
15984    // the media duration (which is what timeOffset is provided as) before we need to process the next chunk.
15985    if (!isFiniteNumber(lastEndTime)) {
15986      lastEndTime = this.lastEndTime = timeOffset || 0;
15987    }
15988
15989    // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only
15990    // audio or video (or both); adding it to video was an arbitrary choice.
15991    const data = videoTrack.samples;
15992    if (!data.length) {
15993      return result;
15994    }
15995    const initSegment = {
15996      initPTS: undefined,
15997      timescale: undefined,
15998      trackId: undefined
15999    };
16000    let initData = this.initData;
16001    if (!((_initData = initData) != null && _initData.length)) {
16002      this.generateInitSegment(data);
16003      initData = this.initData;
16004    }
16005    if (!((_initData2 = initData) != null && _initData2.length)) {
16006      // We can't remux if the initSegment could not be generated
16007      this.warn('Failed to generate initSegment.');
16008      return result;
16009    }
16010    if (this.emitInitSegment) {
16011      initSegment.tracks = this.initTracks;
16012      this.emitInitSegment = false;
16013    }
16014    const trackSampleData = getSampleData(data, initData, this);
16015    const audioSampleTimestamps = initData.audio ? trackSampleData[initData.audio.id] : null;
16016    const videoSampleTimestamps = initData.video ? trackSampleData[initData.video.id] : null;
16017    const videoStartTime = toStartEndOrDefault(videoSampleTimestamps, Infinity);
16018    const audioStartTime = toStartEndOrDefault(audioSampleTimestamps, Infinity);
16019    const videoEndTime = toStartEndOrDefault(videoSampleTimestamps, 0, true);
16020    const audioEndTime = toStartEndOrDefault(audioSampleTimestamps, 0, true);
16021    let decodeTime = timeOffset;
16022    let duration = 0;
16023    const syncOnAudio = audioSampleTimestamps && (!videoSampleTimestamps || !initPTS && audioStartTime < videoStartTime || initPTS && initPTS.trackId === initData.audio.id);
16024    const baseOffsetSamples = syncOnAudio ? audioSampleTimestamps : videoSampleTimestamps;
16025    if (baseOffsetSamples) {
16026      const timescale = baseOffsetSamples.timescale;
16027      const baseTime = baseOffsetSamples.start - timeOffset * timescale;
16028      const trackId = syncOnAudio ? initData.audio.id : initData.video.id;
16029      decodeTime = baseOffsetSamples.start / timescale;
16030      duration = syncOnAudio ? audioEndTime - audioStartTime : videoEndTime - videoStartTime;
16031      if ((accurateTimeOffset || !initPTS) && (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || timescale !== initPTS.timescale)) {
16032        if (initPTS) {
16033          this.warn(`Timestamps at playlist time: ${accurateTimeOffset ? '' : '~'}${timeOffset} ${baseTime / timescale} != initPTS: ${initPTS.baseTime / initPTS.timescale} (${initPTS.baseTime}/${initPTS.timescale}) trackId: ${initPTS.trackId}`);
16034        }
16035        this.log(`Found initPTS at playlist time: ${timeOffset} offset: ${decodeTime - timeOffset} (${baseTime}/${timescale}) trackId: ${trackId}`);
16036        initPTS = null;
16037        initSegment.initPTS = baseTime;
16038        initSegment.timescale = timescale;
16039        initSegment.trackId = trackId;
16040      }
16041    } else {
16042      this.warn(`No audio or video samples found for initPTS at playlist time: ${timeOffset}`);
16043    }
16044    if (!initPTS) {
16045      if (!initSegment.timescale || initSegment.trackId === undefined || initSegment.initPTS === undefined) {
16046        this.warn('Could not set initPTS');
16047        initSegment.initPTS = decodeTime;
16048        initSegment.timescale = 1;
16049        initSegment.trackId = -1;
16050      }
16051      this.initPTS = initPTS = {
16052        baseTime: initSegment.initPTS,
16053        timescale: initSegment.timescale,
16054        trackId: initSegment.trackId
16055      };
16056    } else {
16057      initSegment.initPTS = initPTS.baseTime;
16058      initSegment.timescale = initPTS.timescale;
16059      initSegment.trackId = initPTS.trackId;
16060    }
16061    const startTime = decodeTime - initPTS.baseTime / initPTS.timescale;
16062    const endTime = startTime + duration;
16063    if (duration > 0) {
16064      this.lastEndTime = endTime;
16065    } else {
16066      this.warn('Duration parsed from mp4 should be greater than zero');
16067      this.resetNextTimestamp();
16068    }
16069    const hasAudio = !!initData.audio;
16070    const hasVideo = !!initData.video;
16071    let type = '';
16072    if (hasAudio) {
16073      type += 'audio';
16074    }
16075    if (hasVideo) {
16076      type += 'video';
16077    }
16078    const encrypted = (initData.audio ? initData.audio.encrypted : false) || (initData.video ? initData.video.encrypted : false);
16079    const track = {
16080      data1: data,
16081      startPTS: startTime,
16082      startDTS: startTime,
16083      endPTS: endTime,
16084      endDTS: endTime,
16085      type,
16086      hasAudio,
16087      hasVideo,
16088      nb: 1,
16089      dropped: 0,
16090      encrypted
16091    };
16092    result.audio = hasAudio && !hasVideo ? track : undefined;
16093    result.video = hasVideo ? track : undefined;
16094    const videoSampleCount = videoSampleTimestamps == null ? void 0 : videoSampleTimestamps.sampleCount;
16095    if (videoSampleCount) {
16096      const firstKeyFrame = videoSampleTimestamps.keyFrameIndex;
16097      const independent = firstKeyFrame !== -1;
16098      track.nb = videoSampleCount;
16099      track.dropped = firstKeyFrame === 0 || this.isVideoContiguous ? 0 : independent ? firstKeyFrame : videoSampleCount;
16100      track.independent = independent;
16101      track.firstKeyFrame = firstKeyFrame;
16102      if (independent && videoSampleTimestamps.keyFrameStart) {
16103        track.firstKeyFramePTS = (videoSampleTimestamps.keyFrameStart - initPTS.baseTime) / initPTS.timescale;
16104      }
16105      if (!this.isVideoContiguous) {
16106        result.independent = independent;
16107      }
16108      this.isVideoContiguous || (this.isVideoContiguous = independent);
16109      if (track.dropped) {
16110        this.warn(`fmp4 does not start with IDR: firstIDR ${firstKeyFrame}/${videoSampleCount} dropped: ${track.dropped} start: ${track.firstKeyFramePTS || 'NA'}`);
16111      }
16112    }
16113    result.initSegment = initSegment;
16114    result.id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS);
16115    if (textTrack.samples.length) {
16116      result.text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS);
16117    }
16118    return result;
16119  }
16120}
16121function toStartEndOrDefault(trackTimes, defaultValue, end = false) {
16122  return (trackTimes == null ? void 0 : trackTimes.start) !== undefined ? (trackTimes.start + (end ? trackTimes.duration : 0)) / trackTimes.timescale : defaultValue;
16123}
16124function isInvalidInitPts(initPTS, startDTS, timeOffset, duration) {
16125  if (initPTS === null) {
16126    return true;
16127  }
16128  // InitPTS is invalid when distance from program would be more than segment duration or a minimum of one second
16129  const minDuration = Math.max(duration, 1);
16130  const startTime = startDTS - initPTS.baseTime / initPTS.timescale;
16131  return Math.abs(startTime - timeOffset) > minDuration;
16132}
16133function getParsedTrackCodec(track, type, logger) {
16134  const parsedCodec = track.codec;
16135  if (parsedCodec && parsedCodec.length > 4) {
16136    return parsedCodec;
16137  }
16138  if (type === ElementaryStreamTypes.AUDIO) {
16139    if (parsedCodec === 'ec-3' || parsedCodec === 'ac-3' || parsedCodec === 'alac') {
16140      return parsedCodec;
16141    }
16142    if (parsedCodec === 'fLaC' || parsedCodec === 'Opus') {
16143      // Opting not to get `preferManagedMediaSource` from player config for isSupported() check for simplicity
16144      const preferManagedMediaSource = false;
16145      return getCodecCompatibleName(parsedCodec, preferManagedMediaSource);
16146    }
16147    logger.warn(`Unhandled audio codec "${parsedCodec}" in mp4 MAP`);
16148    return parsedCodec || 'mp4a';
16149  }
16150  // Provide defaults based on codec type
16151  // This allows for some playback of some fmp4 playlists without CODECS defined in manifest
16152  logger.warn(`Unhandled video codec "${parsedCodec}" in mp4 MAP`);
16153  return parsedCodec || 'avc1';
16154}
16155
16156let now;
16157// performance.now() not available on WebWorker, at least on Safari Desktop
16158try {
16159  now = self.performance.now.bind(self.performance);
16160} catch (err) {
16161  now = Date.now;
16162}
16163const muxConfig = [{
16164  demux: MP4Demuxer,
16165  remux: PassThroughRemuxer
16166}, {
16167  demux: TSDemuxer,
16168  remux: MP4Remuxer
16169}, {
16170  demux: AACDemuxer,
16171  remux: MP4Remuxer
16172}, {
16173  demux: MP3Demuxer,
16174  remux: MP4Remuxer
16175}];
16176{
16177  muxConfig.splice(2, 0, {
16178    demux: AC3Demuxer,
16179    remux: MP4Remuxer
16180  });
16181}
16182class Transmuxer {
16183  constructor(observer, typeSupported, config, vendor, id, logger) {
16184    this.asyncResult = false;
16185    this.logger = void 0;
16186    this.observer = void 0;
16187    this.typeSupported = void 0;
16188    this.config = void 0;
16189    this.id = void 0;
16190    this.demuxer = void 0;
16191    this.remuxer = void 0;
16192    this.decrypter = void 0;
16193    this.probe = void 0;
16194    this.decryptionPromise = null;
16195    this.transmuxConfig = void 0;
16196    this.currentTransmuxState = void 0;
16197    this.observer = observer;
16198    this.typeSupported = typeSupported;
16199    this.config = config;
16200    this.id = id;
16201    this.logger = logger;
16202  }
16203  configure(transmuxConfig) {
16204    this.transmuxConfig = transmuxConfig;
16205    if (this.decrypter) {
16206      this.decrypter.reset();
16207    }
16208  }
16209  push(data, decryptdata, chunkMeta, state) {
16210    const stats = chunkMeta.transmuxing;
16211    stats.executeStart = now();
16212    let uintData = new Uint8Array(data);
16213    const {
16214      currentTransmuxState,
16215      transmuxConfig
16216    } = this;
16217    if (state) {
16218      this.currentTransmuxState = state;
16219    }
16220    const {
16221      contiguous,
16222      discontinuity,
16223      trackSwitch,
16224      accurateTimeOffset,
16225      timeOffset,
16226      initSegmentChange
16227    } = state || currentTransmuxState;
16228    const {
16229      audioCodec,
16230      videoCodec,
16231      defaultInitPts,
16232      duration,
16233      initSegmentData
16234    } = transmuxConfig;
16235    const keyData = getEncryptionType(uintData, decryptdata);
16236    if (keyData && isFullSegmentEncryption(keyData.method)) {
16237      const decrypter = this.getDecrypter();
16238      const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
16239
16240      // Software decryption is synchronous; webCrypto is not
16241      if (decrypter.isSync()) {
16242        // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
16243        // data is handled in the flush() call
16244        let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
16245        // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
16246        const loadingParts = chunkMeta.part > -1;
16247        if (loadingParts) {
16248          const _data = decrypter.flush();
16249          decryptedData = _data ? _data.buffer : _data;
16250        }
16251        if (!decryptedData) {
16252          stats.executeEnd = now();
16253          return emptyResult(chunkMeta);
16254        }
16255        uintData = new Uint8Array(decryptedData);
16256      } else {
16257        this.asyncResult = true;
16258        this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
16259          // Calling push here is important; if flush() is called while this is still resolving, this ensures that
16260          // the decrypted data has been transmuxed
16261          const result = this.push(decryptedData, null, chunkMeta);
16262          this.decryptionPromise = null;
16263          return result;
16264        });
16265        return this.decryptionPromise;
16266      }
16267    }
16268    const resetMuxers = this.needsProbing(discontinuity, trackSwitch);
16269    if (resetMuxers) {
16270      const error = this.configureTransmuxer(uintData);
16271      if (error) {
16272        this.logger.warn(`[transmuxer] ${error.message}`);
16273        this.observer.emit(Events.ERROR, Events.ERROR, {
16274          type: ErrorTypes.MEDIA_ERROR,
16275          details: ErrorDetails.FRAG_PARSING_ERROR,
16276          fatal: false,
16277          error,
16278          reason: error.message
16279        });
16280        stats.executeEnd = now();
16281        return emptyResult(chunkMeta);
16282      }
16283    }
16284    if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) {
16285      this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata);
16286    }
16287    if (discontinuity || initSegmentChange || resetMuxers) {
16288      this.resetInitialTimestamp(defaultInitPts);
16289    }
16290    if (!contiguous) {
16291      this.resetContiguity();
16292    }
16293    const result = this.transmux(uintData, keyData, timeOffset, accurateTimeOffset, chunkMeta);
16294    this.asyncResult = isPromise(result);
16295    const currentState = this.currentTransmuxState;
16296    currentState.contiguous = true;
16297    currentState.discontinuity = false;
16298    currentState.trackSwitch = false;
16299    stats.executeEnd = now();
16300    return result;
16301  }
16302
16303  // Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type)
16304  flush(chunkMeta) {
16305    const stats = chunkMeta.transmuxing;
16306    stats.executeStart = now();
16307    const {
16308      decrypter,
16309      currentTransmuxState,
16310      decryptionPromise
16311    } = this;
16312    if (decryptionPromise) {
16313      this.asyncResult = true;
16314      // Upon resolution, the decryption promise calls push() and returns its TransmuxerResult up the stack. Therefore
16315      // only flushing is required for async decryption
16316      return decryptionPromise.then(() => {
16317        return this.flush(chunkMeta);
16318      });
16319    }
16320    const transmuxResults = [];
16321    const {
16322      timeOffset
16323    } = currentTransmuxState;
16324    if (decrypter) {
16325      // The decrypter may have data cached, which needs to be demuxed. In this case we'll have two TransmuxResults
16326      // This happens in the case that we receive only 1 push call for a segment (either for non-progressive downloads,
16327      // or for progressive downloads with small segments)
16328      const decryptedData = decrypter.flush();
16329      if (decryptedData) {
16330        // Push always returns a TransmuxerResult if decryptdata is null
16331        transmuxResults.push(this.push(decryptedData.buffer, null, chunkMeta));
16332      }
16333    }
16334    const {
16335      demuxer,
16336      remuxer
16337    } = this;
16338    if (!demuxer || !remuxer) {
16339      // If probing failed, then Hls.js has been given content its not able to handle
16340      stats.executeEnd = now();
16341      const emptyResults = [emptyResult(chunkMeta)];
16342      if (this.asyncResult) {
16343        return Promise.resolve(emptyResults);
16344      }
16345      return emptyResults;
16346    }
16347    const demuxResultOrPromise = demuxer.flush(timeOffset);
16348    if (isPromise(demuxResultOrPromise)) {
16349      this.asyncResult = true;
16350      // Decrypt final SAMPLE-AES samples
16351      return demuxResultOrPromise.then(demuxResult => {
16352        this.flushRemux(transmuxResults, demuxResult, chunkMeta);
16353        return transmuxResults;
16354      });
16355    }
16356    this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta);
16357    if (this.asyncResult) {
16358      return Promise.resolve(transmuxResults);
16359    }
16360    return transmuxResults;
16361  }
16362  flushRemux(transmuxResults, demuxResult, chunkMeta) {
16363    const {
16364      audioTrack,
16365      videoTrack,
16366      id3Track,
16367      textTrack
16368    } = demuxResult;
16369    const {
16370      accurateTimeOffset,
16371      timeOffset
16372    } = this.currentTransmuxState;
16373    this.logger.log(`[transmuxer.ts]: Flushed ${this.id} sn: ${chunkMeta.sn}${chunkMeta.part > -1 ? ' part: ' + chunkMeta.part : ''} of ${this.id === PlaylistLevelType.MAIN ? 'level' : 'track'} ${chunkMeta.level}`);
16374    const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id);
16375    transmuxResults.push({
16376      remuxResult,
16377      chunkMeta
16378    });
16379    chunkMeta.transmuxing.executeEnd = now();
16380  }
16381  resetInitialTimestamp(defaultInitPts) {
16382    const {
16383      demuxer,
16384      remuxer
16385    } = this;
16386    if (!demuxer || !remuxer) {
16387      return;
16388    }
16389    demuxer.resetTimeStamp(defaultInitPts);
16390    remuxer.resetTimeStamp(defaultInitPts);
16391  }
16392  resetContiguity() {
16393    const {
16394      demuxer,
16395      remuxer
16396    } = this;
16397    if (!demuxer || !remuxer) {
16398      return;
16399    }
16400    demuxer.resetContiguity();
16401    remuxer.resetNextTimestamp();
16402  }
16403  resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata) {
16404    const {
16405      demuxer,
16406      remuxer
16407    } = this;
16408    if (!demuxer || !remuxer) {
16409      return;
16410    }
16411    demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration);
16412    remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata);
16413  }
16414  destroy() {
16415    if (this.demuxer) {
16416      this.demuxer.destroy();
16417      this.demuxer = undefined;
16418    }
16419    if (this.remuxer) {
16420      this.remuxer.destroy();
16421      this.remuxer = undefined;
16422    }
16423  }
16424  transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta) {
16425    let result;
16426    if (keyData && keyData.method === 'SAMPLE-AES') {
16427      result = this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta);
16428    } else {
16429      result = this.transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta);
16430    }
16431    return result;
16432  }
16433  transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta) {
16434    const {
16435      audioTrack,
16436      videoTrack,
16437      id3Track,
16438      textTrack
16439    } = this.demuxer.demux(data, timeOffset, false, !this.config.progressive);
16440    const remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id);
16441    return {
16442      remuxResult,
16443      chunkMeta
16444    };
16445  }
16446  transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta) {
16447    return this.demuxer.demuxSampleAes(data, decryptData, timeOffset).then(demuxResult => {
16448      const remuxResult = this.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, this.id);
16449      return {
16450        remuxResult,
16451        chunkMeta
16452      };
16453    });
16454  }
16455  configureTransmuxer(data) {
16456    const {
16457      config,
16458      observer,
16459      typeSupported
16460    } = this;
16461    // probe for content type
16462    let mux;
16463    for (let i = 0, len = muxConfig.length; i < len; i++) {
16464      var _muxConfig$i$demux;
16465      if ((_muxConfig$i$demux = muxConfig[i].demux) != null && _muxConfig$i$demux.probe(data, this.logger)) {
16466        mux = muxConfig[i];
16467        break;
16468      }
16469    }
16470    if (!mux) {
16471      return new Error('Failed to find demuxer by probing fragment data');
16472    }
16473    // so let's check that current remuxer and demuxer are still valid
16474    const demuxer = this.demuxer;
16475    const remuxer = this.remuxer;
16476    const Remuxer = mux.remux;
16477    const Demuxer = mux.demux;
16478    if (!remuxer || !(remuxer instanceof Remuxer)) {
16479      this.remuxer = new Remuxer(observer, config, typeSupported, this.logger);
16480    }
16481    if (!demuxer || !(demuxer instanceof Demuxer)) {
16482      this.demuxer = new Demuxer(observer, config, typeSupported, this.logger);
16483      this.probe = Demuxer.probe;
16484    }
16485  }
16486  needsProbing(discontinuity, trackSwitch) {
16487    // in case of continuity change, or track switch
16488    // we might switch from content type (AAC container to TS container, or TS to fmp4 for example)
16489    return !this.demuxer || !this.remuxer || discontinuity || trackSwitch;
16490  }
16491  getDecrypter() {
16492    let decrypter = this.decrypter;
16493    if (!decrypter) {
16494      decrypter = this.decrypter = new Decrypter(this.config);
16495    }
16496    return decrypter;
16497  }
16498}
16499function getEncryptionType(data, decryptData) {
16500  let encryptionType = null;
16501  if (data.byteLength > 0 && (decryptData == null ? void 0 : decryptData.key) != null && decryptData.iv !== null && decryptData.method != null) {
16502    encryptionType = decryptData;
16503  }
16504  return encryptionType;
16505}
16506const emptyResult = chunkMeta => ({
16507  remuxResult: {},
16508  chunkMeta
16509});
16510function isPromise(p) {
16511  return 'then' in p && p.then instanceof Function;
16512}
16513class TransmuxConfig {
16514  constructor(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts) {
16515    this.audioCodec = void 0;
16516    this.videoCodec = void 0;
16517    this.initSegmentData = void 0;
16518    this.duration = void 0;
16519    this.defaultInitPts = void 0;
16520    this.audioCodec = audioCodec;
16521    this.videoCodec = videoCodec;
16522    this.initSegmentData = initSegmentData;
16523    this.duration = duration;
16524    this.defaultInitPts = defaultInitPts || null;
16525  }
16526}
16527class TransmuxState {
16528  constructor(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange) {
16529    this.discontinuity = void 0;
16530    this.contiguous = void 0;
16531    this.accurateTimeOffset = void 0;
16532    this.trackSwitch = void 0;
16533    this.timeOffset = void 0;
16534    this.initSegmentChange = void 0;
16535    this.discontinuity = discontinuity;
16536    this.contiguous = contiguous;
16537    this.accurateTimeOffset = accurateTimeOffset;
16538    this.trackSwitch = trackSwitch;
16539    this.timeOffset = timeOffset;
16540    this.initSegmentChange = initSegmentChange;
16541  }
16542}
16543
16544let transmuxerInstanceCount = 0;
16545class TransmuxerInterface {
16546  constructor(_hls, id, onTransmuxComplete, onFlush) {
16547    this.error = null;
16548    this.hls = void 0;
16549    this.id = void 0;
16550    this.instanceNo = transmuxerInstanceCount++;
16551    this.observer = void 0;
16552    this.frag = null;
16553    this.part = null;
16554    this.useWorker = void 0;
16555    this.workerContext = null;
16556    this.transmuxer = null;
16557    this.onTransmuxComplete = void 0;
16558    this.onFlush = void 0;
16559    this.onWorkerMessage = event => {
16560      const data = event.data;
16561      const hls = this.hls;
16562      if (!hls || !(data != null && data.event) || data.instanceNo !== this.instanceNo) {
16563        return;
16564      }
16565      switch (data.event) {
16566        case 'init':
16567          {
16568            var _this$workerContext;
16569            const objectURL = (_this$workerContext = this.workerContext) == null ? void 0 : _this$workerContext.objectURL;
16570            if (objectURL) {
16571              // revoke the Object URL that was used to create transmuxer worker, so as not to leak it
16572              self.URL.revokeObjectURL(objectURL);
16573            }
16574            break;
16575          }
16576        case 'transmuxComplete':
16577          {
16578            this.handleTransmuxComplete(data.data);
16579            break;
16580          }
16581        case 'flush':
16582          {
16583            this.onFlush(data.data);
16584            break;
16585          }
16586
16587        // pass logs from the worker thread to the main logger
16588        case 'workerLog':
16589          {
16590            if (hls.logger[data.data.logType]) {
16591              hls.logger[data.data.logType](data.data.message);
16592            }
16593            break;
16594          }
16595        default:
16596          {
16597            data.data = data.data || {};
16598            data.data.frag = this.frag;
16599            data.data.part = this.part;
16600            data.data.id = this.id;
16601            hls.trigger(data.event, data.data);
16602            break;
16603          }
16604      }
16605    };
16606    this.onWorkerError = event => {
16607      if (!this.hls) {
16608        return;
16609      }
16610      const error = new Error(`${event.message}  (${event.filename}:${event.lineno})`);
16611      this.hls.config.enableWorker = false;
16612      this.hls.logger.warn(`Error in "${this.id}" Web Worker, fallback to inline`);
16613      this.hls.trigger(Events.ERROR, {
16614        type: ErrorTypes.OTHER_ERROR,
16615        details: ErrorDetails.INTERNAL_EXCEPTION,
16616        fatal: false,
16617        event: 'demuxerWorker',
16618        error
16619      });
16620    };
16621    const config = _hls.config;
16622    this.hls = _hls;
16623    this.id = id;
16624    this.useWorker = !!config.enableWorker;
16625    this.onTransmuxComplete = onTransmuxComplete;
16626    this.onFlush = onFlush;
16627    const forwardMessage = (ev, data) => {
16628      data = data || {};
16629      data.frag = this.frag || undefined;
16630      if (ev === Events.ERROR) {
16631        data = data;
16632        data.parent = this.id;
16633        data.part = this.part;
16634        this.error = data.error;
16635      }
16636      this.hls.trigger(ev, data);
16637    };
16638
16639    // forward events to main thread
16640    this.observer = new EventEmitter();
16641    this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
16642    this.observer.on(Events.ERROR, forwardMessage);
16643    const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
16644    if (this.useWorker && typeof Worker !== 'undefined') {
16645      const logger = this.hls.logger;
16646      const canCreateWorker = config.workerPath || hasUMDWorker();
16647      if (canCreateWorker) {
16648        try {
16649          if (config.workerPath) {
16650            logger.log(`loading Web Worker ${config.workerPath} for "${id}"`);
16651            this.workerContext = loadWorker(config.workerPath);
16652          } else {
16653            logger.log(`injecting Web Worker for "${id}"`);
16654            this.workerContext = injectWorker();
16655          }
16656          const {
16657            worker
16658          } = this.workerContext;
16659          worker.addEventListener('message', this.onWorkerMessage);
16660          worker.addEventListener('error', this.onWorkerError);
16661          worker.postMessage({
16662            instanceNo: this.instanceNo,
16663            cmd: 'init',
16664            typeSupported: m2tsTypeSupported,
16665            id,
16666            config: stringify(config)
16667          });
16668        } catch (err) {
16669          logger.warn(`Error setting up "${id}" Web Worker, fallback to inline`, err);
16670          this.terminateWorker();
16671          this.error = null;
16672          this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id, _hls.logger);
16673        }
16674        return;
16675      }
16676    }
16677    this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id, _hls.logger);
16678  }
16679  reset() {
16680    this.frag = null;
16681    this.part = null;
16682    if (this.workerContext) {
16683      const instanceNo = this.instanceNo;
16684      this.instanceNo = transmuxerInstanceCount++;
16685      const config = this.hls.config;
16686      const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
16687      this.workerContext.worker.postMessage({
16688        instanceNo: this.instanceNo,
16689        cmd: 'reset',
16690        resetNo: instanceNo,
16691        typeSupported: m2tsTypeSupported,
16692        id: this.id,
16693        config: stringify(config)
16694      });
16695    }
16696  }
16697  terminateWorker() {
16698    if (this.workerContext) {
16699      const {
16700        worker
16701      } = this.workerContext;
16702      this.workerContext = null;
16703      worker.removeEventListener('message', this.onWorkerMessage);
16704      worker.removeEventListener('error', this.onWorkerError);
16705      removeWorkerFromStore(this.hls.config.workerPath);
16706    }
16707  }
16708  destroy() {
16709    if (this.workerContext) {
16710      this.terminateWorker();
16711      // @ts-ignore
16712      this.onWorkerMessage = this.onWorkerError = null;
16713    } else {
16714      const transmuxer = this.transmuxer;
16715      if (transmuxer) {
16716        transmuxer.destroy();
16717        this.transmuxer = null;
16718      }
16719    }
16720    const observer = this.observer;
16721    if (observer) {
16722      observer.removeAllListeners();
16723    }
16724    this.frag = null;
16725    this.part = null;
16726    // @ts-ignore
16727    this.observer = null;
16728    // @ts-ignore
16729    this.hls = null;
16730  }
16731  push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS) {
16732    var _frag$initSegment, _lastFrag$initSegment;
16733    chunkMeta.transmuxing.start = self.performance.now();
16734    const {
16735      instanceNo,
16736      transmuxer
16737    } = this;
16738    const timeOffset = part ? part.start : frag.start;
16739    // TODO: push "clear-lead" decrypt data for unencrypted fragments in streams with encrypted ones
16740    const decryptdata = frag.decryptdata;
16741    const lastFrag = this.frag;
16742    const discontinuity = !(lastFrag && frag.cc === lastFrag.cc);
16743    const trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level);
16744    const snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1;
16745    const partDiff = this.part ? chunkMeta.part - this.part.index : -1;
16746    const progressive = snDiff === 0 && chunkMeta.id > 1 && chunkMeta.id === (lastFrag == null ? void 0 : lastFrag.stats.chunkCount);
16747    const contiguous = !trackSwitch && (snDiff === 1 || snDiff === 0 && (partDiff === 1 || progressive && partDiff <= 0));
16748    const now = self.performance.now();
16749    if (trackSwitch || snDiff || frag.stats.parsing.start === 0) {
16750      frag.stats.parsing.start = now;
16751    }
16752    if (part && (partDiff || !contiguous)) {
16753      part.stats.parsing.start = now;
16754    }
16755    const initSegmentChange = !(lastFrag && ((_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.url) === ((_lastFrag$initSegment = lastFrag.initSegment) == null ? void 0 : _lastFrag$initSegment.url));
16756    const state = new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange);
16757    if (!contiguous || discontinuity || initSegmentChange) {
16758      this.hls.logger.log(`[transmuxer-interface]: Starting new transmux session for ${frag.type} sn: ${chunkMeta.sn}${chunkMeta.part > -1 ? ' part: ' + chunkMeta.part : ''} ${this.id === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${chunkMeta.level} id: ${chunkMeta.id}
16759        discontinuity: ${discontinuity}
16760        trackSwitch: ${trackSwitch}
16761        contiguous: ${contiguous}
16762        accurateTimeOffset: ${accurateTimeOffset}
16763        timeOffset: ${timeOffset}
16764        initSegmentChange: ${initSegmentChange}`);
16765      const config = new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS);
16766      this.configureTransmuxer(config);
16767    }
16768    this.frag = frag;
16769    this.part = part;
16770
16771    // Frags with sn of 'initSegment' are not transmuxed
16772    if (this.workerContext) {
16773      // post fragment payload as transferable objects for ArrayBuffer (no copy)
16774      this.workerContext.worker.postMessage({
16775        instanceNo,
16776        cmd: 'demux',
16777        data,
16778        decryptdata,
16779        chunkMeta,
16780        state
16781      }, data instanceof ArrayBuffer ? [data] : []);
16782    } else if (transmuxer) {
16783      const transmuxResult = transmuxer.push(data, decryptdata, chunkMeta, state);
16784      if (isPromise(transmuxResult)) {
16785        transmuxResult.then(data => {
16786          this.handleTransmuxComplete(data);
16787        }).catch(error => {
16788          this.transmuxerError(error, chunkMeta, 'transmuxer-interface push error');
16789        });
16790      } else {
16791        this.handleTransmuxComplete(transmuxResult);
16792      }
16793    }
16794  }
16795  flush(chunkMeta) {
16796    chunkMeta.transmuxing.start = self.performance.now();
16797    const {
16798      instanceNo,
16799      transmuxer
16800    } = this;
16801    if (this.workerContext) {
16802      this.workerContext.worker.postMessage({
16803        instanceNo,
16804        cmd: 'flush',
16805        chunkMeta
16806      });
16807    } else if (transmuxer) {
16808      const transmuxResult = transmuxer.flush(chunkMeta);
16809      if (isPromise(transmuxResult)) {
16810        transmuxResult.then(data => {
16811          this.handleFlushResult(data, chunkMeta);
16812        }).catch(error => {
16813          this.transmuxerError(error, chunkMeta, 'transmuxer-interface flush error');
16814        });
16815      } else {
16816        this.handleFlushResult(transmuxResult, chunkMeta);
16817      }
16818    }
16819  }
16820  transmuxerError(error, chunkMeta, reason) {
16821    if (!this.hls) {
16822      return;
16823    }
16824    this.error = error;
16825    this.hls.trigger(Events.ERROR, {
16826      type: ErrorTypes.MEDIA_ERROR,
16827      details: ErrorDetails.FRAG_PARSING_ERROR,
16828      chunkMeta,
16829      frag: this.frag || undefined,
16830      part: this.part || undefined,
16831      fatal: false,
16832      error,
16833      err: error,
16834      reason
16835    });
16836  }
16837  handleFlushResult(results, chunkMeta) {
16838    results.forEach(result => {
16839      this.handleTransmuxComplete(result);
16840    });
16841    this.onFlush(chunkMeta);
16842  }
16843  configureTransmuxer(config) {
16844    const {
16845      instanceNo,
16846      transmuxer
16847    } = this;
16848    if (this.workerContext) {
16849      this.workerContext.worker.postMessage({
16850        instanceNo,
16851        cmd: 'configure',
16852        config
16853      });
16854    } else if (transmuxer) {
16855      transmuxer.configure(config);
16856    }
16857  }
16858  handleTransmuxComplete(result) {
16859    result.chunkMeta.transmuxing.end = self.performance.now();
16860    this.onTransmuxComplete(result);
16861  }
16862}
16863
16864const TICK_INTERVAL$3 = 100; // how often to tick in ms
16865
16866class AudioStreamController extends BaseStreamController {
16867  constructor(hls, fragmentTracker, keyLoader) {
16868    super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
16869    this.mainAnchor = null;
16870    this.mainFragLoading = null;
16871    this.audioOnly = false;
16872    this.bufferedTrack = null;
16873    this.switchingTrack = null;
16874    this.trackId = -1;
16875    this.waitingData = null;
16876    this.mainDetails = null;
16877    this.flushing = false;
16878    this.bufferFlushed = false;
16879    this.cachedTrackLoadedData = null;
16880    this.registerListeners();
16881  }
16882  onHandlerDestroying() {
16883    this.unregisterListeners();
16884    super.onHandlerDestroying();
16885    this.resetItem();
16886  }
16887  resetItem() {
16888    this.mainDetails = this.mainAnchor = this.mainFragLoading = this.bufferedTrack = this.switchingTrack = this.waitingData = this.cachedTrackLoadedData = null;
16889  }
16890  registerListeners() {
16891    super.registerListeners();
16892    const {
16893      hls
16894    } = this;
16895    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16896    hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16897    hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16898    hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
16899    hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
16900    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
16901    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16902    hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
16903    hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
16904    hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
16905    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16906  }
16907  unregisterListeners() {
16908    const {
16909      hls
16910    } = this;
16911    if (!hls) {
16912      return;
16913    }
16914    super.unregisterListeners();
16915    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
16916    hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
16917    hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
16918    hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
16919    hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
16920    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
16921    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
16922    hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
16923    hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
16924    hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
16925    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
16926  }
16927
16928  // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value
16929  onInitPtsFound(event, {
16930    frag,
16931    id,
16932    initPTS,
16933    timescale,
16934    trackId
16935  }) {
16936    // Always update the new INIT PTS
16937    // Can change due level switch
16938    if (id === PlaylistLevelType.MAIN) {
16939      const cc = frag.cc;
16940      const inFlightFrag = this.fragCurrent;
16941      this.initPTS[cc] = {
16942        baseTime: initPTS,
16943        timescale,
16944        trackId
16945      };
16946      this.log(`InitPTS for cc: ${cc} found from main: ${initPTS / timescale} (${initPTS}/${timescale}) trackId: ${trackId}`);
16947      this.mainAnchor = frag;
16948      // If we are waiting, tick immediately to unblock audio fragment transmuxing
16949      if (this.state === State.WAITING_INIT_PTS) {
16950        const waitingData = this.waitingData;
16951        if (!waitingData && !this.loadingParts || waitingData && waitingData.frag.cc !== cc) {
16952          this.syncWithAnchor(frag, waitingData == null ? void 0 : waitingData.frag);
16953        }
16954      } else if (!this.hls.hasEnoughToStart && inFlightFrag && inFlightFrag.cc !== cc) {
16955        inFlightFrag.abortRequests();
16956        this.syncWithAnchor(frag, inFlightFrag);
16957      } else if (this.state === State.IDLE) {
16958        this.tick();
16959      }
16960    }
16961  }
16962  getLoadPosition() {
16963    if (!this.startFragRequested && this.nextLoadPosition >= 0) {
16964      return this.nextLoadPosition;
16965    }
16966    return super.getLoadPosition();
16967  }
16968  syncWithAnchor(mainAnchor, waitingToAppend) {
16969    var _this$mainFragLoading;
16970    // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
16971    const mainFragLoading = ((_this$mainFragLoading = this.mainFragLoading) == null ? void 0 : _this$mainFragLoading.frag) || null;
16972    if (waitingToAppend) {
16973      if ((mainFragLoading == null ? void 0 : mainFragLoading.cc) === waitingToAppend.cc) {
16974        // Wait for loading frag to complete and INIT_PTS_FOUND
16975        return;
16976      }
16977    }
16978    const targetDiscontinuity = (mainFragLoading || mainAnchor).cc;
16979    const trackDetails = this.getLevelDetails();
16980    const pos = this.getLoadPosition();
16981    const syncFrag = findNearestWithCC(trackDetails, targetDiscontinuity, pos);
16982    // Only stop waiting for audioFrag.cc if an audio segment of the same discontinuity domain (cc) is found
16983    if (syncFrag) {
16984      this.log(`Syncing with main frag at ${syncFrag.start} cc ${syncFrag.cc}`);
16985      this.startFragRequested = false;
16986      this.nextLoadPosition = syncFrag.start;
16987      this.resetLoadingState();
16988      if (this.state === State.IDLE) {
16989        this.doTickIdle();
16990      }
16991    }
16992  }
16993  startLoad(startPosition, skipSeekToStartPosition) {
16994    if (!this.levels) {
16995      this.startPosition = startPosition;
16996      this.state = State.STOPPED;
16997      return;
16998    }
16999    const lastCurrentTime = this.lastCurrentTime;
17000    this.stopLoad();
17001    this.setInterval(TICK_INTERVAL$3);
17002    if (lastCurrentTime > 0 && startPosition === -1) {
17003      this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);
17004      startPosition = lastCurrentTime;
17005      this.state = State.IDLE;
17006    } else {
17007      this.state = State.WAITING_TRACK;
17008    }
17009    this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset;
17010    this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
17011    this.tick();
17012  }
17013  doTick() {
17014    switch (this.state) {
17015      case State.IDLE:
17016        this.doTickIdle();
17017        break;
17018      case State.WAITING_TRACK:
17019        {
17020          const {
17021            levels,
17022            trackId
17023          } = this;
17024          const currenTrack = levels == null ? void 0 : levels[trackId];
17025          const details = currenTrack == null ? void 0 : currenTrack.details;
17026          if (details && !this.waitForLive(currenTrack)) {
17027            if (this.waitForCdnTuneIn(details)) {
17028              break;
17029            }
17030            this.state = State.WAITING_INIT_PTS;
17031          }
17032          break;
17033        }
17034      case State.FRAG_LOADING_WAITING_RETRY:
17035        {
17036          this.checkRetryDate();
17037          break;
17038        }
17039      case State.WAITING_INIT_PTS:
17040        {
17041          // Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS
17042          const waitingData = this.waitingData;
17043          if (waitingData) {
17044            const {
17045              frag,
17046              part,
17047              cache,
17048              complete
17049            } = waitingData;
17050            const mainAnchor = this.mainAnchor;
17051            if (this.initPTS[frag.cc] !== undefined) {
17052              this.waitingData = null;
17053              this.state = State.FRAG_LOADING;
17054              const payload = cache.flush().buffer;
17055              const data = {
17056                frag,
17057                part,
17058                payload,
17059                networkDetails: null
17060              };
17061              this._handleFragmentLoadProgress(data);
17062              if (complete) {
17063                super._handleFragmentLoadComplete(data);
17064              }
17065            } else if (mainAnchor && mainAnchor.cc !== waitingData.frag.cc) {
17066              this.syncWithAnchor(mainAnchor, waitingData.frag);
17067            }
17068          } else {
17069            this.state = State.IDLE;
17070          }
17071        }
17072    }
17073    this.onTickEnd();
17074  }
17075  resetLoadingState() {
17076    const waitingData = this.waitingData;
17077    if (waitingData) {
17078      this.fragmentTracker.removeFragment(waitingData.frag);
17079      this.waitingData = null;
17080    }
17081    super.resetLoadingState();
17082  }
17083  onTickEnd() {
17084    const {
17085      media
17086    } = this;
17087    if (!(media != null && media.readyState)) {
17088      // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0)
17089      return;
17090    }
17091    this.lastCurrentTime = media.currentTime;
17092  }
17093  doTickIdle() {
17094    var _this$mainFragLoading2;
17095    const {
17096      hls,
17097      levels,
17098      media,
17099      trackId
17100    } = this;
17101    const config = hls.config;
17102
17103    // 1. if buffering is suspended
17104    // 2. if video not attached AND
17105    //    start fragment already requested OR start frag prefetch not enabled
17106    // 3. if tracks or track not loaded and selected
17107    // then exit loop
17108    // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
17109    if (!this.buffering || !media && !this.primaryPrefetch && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
17110      return;
17111    }
17112    const levelInfo = levels[trackId];
vendor: 5,300 bytes, lines 17113-17241
17113    const trackDetails = levelInfo.details;
17114    if (!trackDetails || this.waitForLive(levelInfo) || this.waitForCdnTuneIn(trackDetails)) {
17115      this.state = State.WAITING_TRACK;
17116      this.startFragRequested = false;
17117      return;
17118    }
17119    const bufferable = this.mediaBuffer ? this.mediaBuffer : this.media;
17120    if (this.bufferFlushed && bufferable) {
17121      this.bufferFlushed = false;
17122      this.afterBufferFlushed(bufferable, ElementaryStreamTypes.AUDIO, PlaylistLevelType.AUDIO);
17123    }
17124    const bufferInfo = this.getFwdBufferInfo(bufferable, PlaylistLevelType.AUDIO);
17125    if (bufferInfo === null) {
17126      return;
17127    }
17128    if (!this.switchingTrack && this._streamEnded(bufferInfo, trackDetails)) {
17129      hls.trigger(Events.BUFFER_EOS, {
17130        type: 'audio'
17131      });
17132      this.state = State.ENDED;
17133      return;
17134    }
17135    const bufferLen = bufferInfo.len;
17136    const maxBufLen = hls.maxBufferLength;
17137    const fragments = trackDetails.fragments;
17138    const start = fragments[0].start;
17139    const loadPosition = this.getLoadPosition();
17140    const targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
17141    if (this.switchingTrack && media) {
17142      const pos = loadPosition;
17143      // if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime
17144      if (trackDetails.PTSKnown && pos < start) {
17145        // if everything is buffered from pos to start or if audio buffer upfront, let's seek to start
17146        if (bufferInfo.end > start || bufferInfo.nextStart) {
17147          this.log('Alt audio track ahead of main track, seek to start of alt audio track');
17148          media.currentTime = start + 0.05;
17149        }
17150      }
17151    }
17152
17153    // if buffer length is less than maxBufLen, or near the end, find a fragment to load
17154    if (bufferLen >= maxBufLen && !this.switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) {
17155      return;
17156    }
17157    let frag = this.getNextFragment(targetBufferTime, trackDetails);
17158    // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
17159    if (frag && this.isLoopLoading(frag, targetBufferTime)) {
17160      frag = this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
17161    }
17162    if (!frag) {
17163      this.bufferFlushed = true;
17164      return;
17165    }
17166    if (this.exceedsMaxBuffer(bufferInfo, maxBufLen, frag)) {
17167      return;
17168    }
17169
17170    // Request audio segments up to one fragment ahead of main stream-controller
17171    let mainFragLoading = ((_this$mainFragLoading2 = this.mainFragLoading) == null ? void 0 : _this$mainFragLoading2.frag) || null;
17172    if (!this.audioOnly && this.startFragRequested && mainFragLoading && isMediaFragment(frag) && !frag.endList && (!trackDetails.live || !this.loadingParts && targetBufferTime < this.hls.liveSyncPosition)) {
17173      if (this.fragmentTracker.getState(mainFragLoading) === FragmentState.OK) {
17174        this.mainFragLoading = mainFragLoading = null;
17175      }
17176      if (mainFragLoading && isMediaFragment(mainFragLoading)) {
17177        if (frag.start > mainFragLoading.end) {
17178          // Get buffered frag at target position from tracker (loaded out of sequence)
17179          const mainFragAtPos = this.fragmentTracker.getFragAtPos(targetBufferTime, PlaylistLevelType.MAIN);
17180          if (mainFragAtPos && mainFragAtPos.end > mainFragLoading.end) {
17181            mainFragLoading = mainFragAtPos;
17182            this.mainFragLoading = {
17183              frag: mainFragAtPos,
17184              targetBufferTime: null
17185            };
17186          }
17187        }
17188        const atBufferSyncLimit = frag.start > mainFragLoading.end;
17189        if (atBufferSyncLimit) {
17190          return;
17191        }
17192      }
17193    }
17194    this.loadFragment(frag, levelInfo, targetBufferTime);
17195  }
17196  onMediaDetaching(event, data) {
17197    this.bufferFlushed = this.flushing = false;
17198    super.onMediaDetaching(event, data);
17199  }
17200  onAudioTracksUpdated(event, {
17201    audioTracks
17202  }) {
17203    // Reset tranxmuxer is essential for large context switches (Content Steering)
17204    this.resetTransmuxer();
17205    this.levels = audioTracks.map(mediaPlaylist => new Level(mediaPlaylist));
17206  }
17207  onAudioTrackSwitching(event, data) {
17208    // if any URL found on new audio track, it is an alternate audio track
17209    const altAudio = !!data.url;
17210    this.trackId = data.id;
17211    const {
17212      fragCurrent
17213    } = this;
17214    if (fragCurrent) {
17215      fragCurrent.abortRequests();
17216      this.removeUnbufferedFrags(fragCurrent.start);
17217    }
17218    this.resetLoadingState();
17219
17220    // should we switch tracks ?
17221    if (altAudio) {
17222      this.switchingTrack = data;
17223      // main audio track are handled by stream-controller, just do something if switching to alt audio track
17224      this.flushAudioIfNeeded(data);
17225      if (this.state !== State.STOPPED) {
17226        // switching to audio track, start timer if not already started
17227        this.setInterval(TICK_INTERVAL$3);
17228        this.state = State.IDLE;
17229        this.tick();
17230      }
17231    } else {
17232      // destroy useless transmuxer when switching audio to main
17233      this.resetTransmuxer();
17234      this.switchingTrack = null;
17235      this.bufferedTrack = data;
17236      this.clearInterval();
17237    }
17238  }
17239  onManifestLoading() {
17240    super.onManifestLoading();
17241    this.bufferFlushed = this.flushing = this.audioOnly = false;
vendor: 40,021 bytes, lines 17242-18308
17242    this.resetItem();
17243    this.trackId = -1;
17244  }
17245  onLevelLoaded(event, data) {
17246    this.mainDetails = data.details;
17247    const cachedTrackLoadedData = this.cachedTrackLoadedData;
17248    if (cachedTrackLoadedData) {
17249      this.cachedTrackLoadedData = null;
17250      this.onAudioTrackLoaded(Events.AUDIO_TRACK_LOADED, cachedTrackLoadedData);
17251    }
17252  }
17253  onAudioTrackLoaded(event, data) {
17254    var _trackLevel$details;
17255    const {
17256      levels
17257    } = this;
17258    const {
17259      details: newDetails,
17260      id: trackId,
17261      groupId,
17262      track
17263    } = data;
17264    if (!levels) {
17265      this.warn(`Audio tracks reset while loading track ${trackId} "${track.name}" of "${groupId}"`);
17266      return;
17267    }
17268    const mainDetails = this.mainDetails;
17269    if (!mainDetails || newDetails.endCC > mainDetails.endCC || mainDetails.expired) {
17270      this.cachedTrackLoadedData = data;
17271      if (this.state !== State.STOPPED) {
17272        this.state = State.WAITING_TRACK;
17273      }
17274      return;
17275    }
17276    this.cachedTrackLoadedData = null;
17277    this.log(`Audio track ${trackId} "${track.name}" of "${groupId}" loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''},duration:${newDetails.totalduration}`);
17278    const trackLevel = levels[trackId];
17279    let sliding = 0;
17280    if (newDetails.live || (_trackLevel$details = trackLevel.details) != null && _trackLevel$details.live) {
17281      this.checkLiveUpdate(newDetails);
17282      if (newDetails.deltaUpdateFailed) {
17283        return;
17284      }
17285      if (trackLevel.details) {
17286        var _this$levelLastLoaded;
17287        sliding = this.alignPlaylists(newDetails, trackLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
17288      }
17289      if (!newDetails.alignedSliding) {
17290        // Align audio rendition with the "main" playlist on discontinuity change
17291        // or program-date-time (PDT)
17292        alignDiscontinuities(newDetails, mainDetails);
17293        if (!newDetails.alignedSliding) {
17294          alignMediaPlaylistByPDT(newDetails, mainDetails);
17295        }
17296        sliding = newDetails.fragmentStart;
17297      }
17298    }
17299    trackLevel.details = newDetails;
17300    this.levelLastLoaded = trackLevel;
17301
17302    // compute start position if we are aligned with the main playlist
17303    if (!this.startFragRequested) {
17304      this.setStartPosition(mainDetails, sliding);
17305    }
17306    this.hls.trigger(Events.AUDIO_TRACK_UPDATED, {
17307      details: newDetails,
17308      id: trackId,
17309      groupId: data.groupId
17310    });
17311
17312    // only switch back to IDLE state if we were waiting for track to start downloading a new fragment
17313    if (this.state === State.WAITING_TRACK && !this.waitForCdnTuneIn(newDetails)) {
17314      this.state = State.IDLE;
17315    }
17316
17317    // trigger handler right now
17318    this.tick();
17319  }
17320  _handleFragmentLoadProgress(data) {
17321    var _frag$initSegment;
17322    const frag = data.frag;
17323    const {
17324      part,
17325      payload
17326    } = data;
17327    const {
17328      config,
17329      trackId,
17330      levels
17331    } = this;
17332    if (!levels) {
17333      this.warn(`Audio tracks were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);
17334      return;
17335    }
17336    const track = levels[trackId];
17337    if (!track) {
17338      this.warn('Audio track is undefined on fragment load progress');
17339      return;
17340    }
17341    const details = track.details;
17342    if (!details) {
17343      this.warn('Audio track details undefined on fragment load progress');
17344      this.removeUnbufferedFrags(frag.start);
17345      return;
17346    }
17347    const audioCodec = config.defaultAudioCodec || track.audioCodec || 'mp4a.40.2';
17348    let transmuxer = this.transmuxer;
17349    if (!transmuxer) {
17350      transmuxer = this.transmuxer = new TransmuxerInterface(this.hls, PlaylistLevelType.AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
17351    }
17352
17353    // Check if we have video initPTS
17354    // If not we need to wait for it
17355    const initPTS = this.initPTS[frag.cc];
17356    const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data;
17357    if (initPTS !== undefined) {
17358      // this.log(`Transmuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`);
17359      // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)
17360      const accurateTimeOffset = false; // details.PTSKnown || !details.live;
17361      const partIndex = part ? part.index : -1;
17362      const partial = partIndex !== -1;
17363      const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
17364      transmuxer.push(payload, initSegmentData, audioCodec, '', frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);
17365    } else {
17366      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}`);
17367      const {
17368        cache
17369      } = this.waitingData = this.waitingData || {
17370        frag,
17371        part,
17372        cache: new ChunkCache(),
17373        complete: false
17374      };
17375      cache.push(new Uint8Array(payload));
17376      if (this.state !== State.STOPPED) {
17377        this.state = State.WAITING_INIT_PTS;
17378      }
17379    }
17380  }
17381  _handleFragmentLoadComplete(fragLoadedData) {
17382    if (this.waitingData) {
17383      this.waitingData.complete = true;
17384      return;
17385    }
17386    super._handleFragmentLoadComplete(fragLoadedData);
17387  }
17388  onBufferReset(/* event: Events.BUFFER_RESET */
17389  ) {
17390    // reset reference to sourcebuffers
17391    this.mediaBuffer = null;
17392  }
17393  onBufferCreated(event, data) {
17394    this.bufferFlushed = this.flushing = false;
17395    const audioTrack = data.tracks.audio;
17396    if (audioTrack) {
17397      this.mediaBuffer = audioTrack.buffer || null;
17398    }
17399  }
17400  onFragLoading(event, data) {
17401    if (!this.audioOnly && data.frag.type === PlaylistLevelType.MAIN && isMediaFragment(data.frag)) {
17402      this.mainFragLoading = data;
17403      if (this.state === State.IDLE) {
17404        this.tick();
17405      }
17406    }
17407  }
17408  onFragBuffered(event, data) {
17409    const {
17410      frag,
17411      part
17412    } = data;
17413    if (frag.type !== PlaylistLevelType.AUDIO) {
17414      if (!this.audioOnly && frag.type === PlaylistLevelType.MAIN && !frag.elementaryStreams.video && !frag.elementaryStreams.audiovideo) {
17415        this.audioOnly = true;
17416        this.mainFragLoading = null;
17417      }
17418      return;
17419    }
17420    if (this.fragContextChanged(frag)) {
17421      // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion
17422      // Avoid setting state back to IDLE or concluding the audio switch; otherwise, the switched-to track will not buffer
17423      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'}`);
17424      return;
17425    }
17426    if (isMediaFragment(frag)) {
17427      this.fragPrevious = frag;
17428      const track = this.switchingTrack;
17429      if (track) {
17430        this.bufferedTrack = track;
17431        this.switchingTrack = null;
17432        this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, track));
17433      }
17434    }
17435    this.fragBufferedComplete(frag, part);
17436    if (this.media) {
17437      this.tick();
17438    }
17439  }
17440  onError(event, data) {
17441    var _data$context;
17442    if (data.fatal) {
17443      this.state = State.ERROR;
17444      return;
17445    }
17446    switch (data.details) {
17447      case ErrorDetails.FRAG_GAP:
17448      case ErrorDetails.FRAG_PARSING_ERROR:
17449      case ErrorDetails.FRAG_DECRYPT_ERROR:
17450      case ErrorDetails.FRAG_LOAD_ERROR:
17451      case ErrorDetails.FRAG_LOAD_TIMEOUT:
17452      case ErrorDetails.KEY_LOAD_ERROR:
17453      case ErrorDetails.KEY_LOAD_TIMEOUT:
17454        this.onFragmentOrKeyLoadError(PlaylistLevelType.AUDIO, data);
17455        break;
17456      case ErrorDetails.AUDIO_TRACK_LOAD_ERROR:
17457      case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT:
17458      case ErrorDetails.LEVEL_PARSING_ERROR:
17459        // in case of non fatal error while loading track, if not retrying to load track, switch back to IDLE
17460        if (!data.levelRetry && this.state === State.WAITING_TRACK && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.AUDIO_TRACK) {
17461          this.state = State.IDLE;
17462        }
17463        break;
17464      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
17465      case ErrorDetails.BUFFER_APPEND_ERROR:
17466        if (data.parent !== 'audio') {
17467          return;
17468        }
17469        if (!this.reduceLengthAndFlushBuffer(data)) {
17470          this.resetLoadingState();
17471        }
17472        break;
17473      case ErrorDetails.BUFFER_FULL_ERROR:
17474        if (data.parent !== 'audio') {
17475          return;
17476        }
17477        if (this.reduceLengthAndFlushBuffer(data)) {
17478          this.bufferedTrack = null;
17479          super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
17480        }
17481        break;
17482      case ErrorDetails.INTERNAL_EXCEPTION:
17483        this.recoverWorkerError(data);
17484        break;
17485    }
17486  }
17487  onBufferFlushing(event, {
17488    type
17489  }) {
17490    if (type !== ElementaryStreamTypes.VIDEO) {
17491      this.flushing = true;
17492    }
17493  }
17494  onBufferFlushed(event, {
17495    type
17496  }) {
17497    if (type !== ElementaryStreamTypes.VIDEO) {
17498      this.flushing = false;
17499      this.bufferFlushed = true;
17500      if (this.state === State.ENDED) {
17501        this.state = State.IDLE;
17502      }
17503      const mediaBuffer = this.mediaBuffer || this.media;
17504      if (mediaBuffer) {
17505        this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.AUDIO);
17506        this.tick();
17507      }
17508    }
17509  }
17510  _handleTransmuxComplete(transmuxResult) {
17511    var _id3$samples;
17512    const id = 'audio';
17513    const {
17514      hls
17515    } = this;
17516    const {
17517      remuxResult,
17518      chunkMeta
17519    } = transmuxResult;
17520    const context = this.getCurrentContext(chunkMeta);
17521    if (!context) {
17522      this.resetWhenMissingContext(chunkMeta);
17523      return;
17524    }
17525    const {
17526      frag,
17527      part,
17528      level
17529    } = context;
17530    const {
17531      details
17532    } = level;
17533    const {
17534      audio,
17535      text,
17536      id3,
17537      initSegment
17538    } = remuxResult;
17539
17540    // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level.
17541    // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed.
17542    if (this.fragContextChanged(frag) || !details) {
17543      this.fragmentTracker.removeFragment(frag);
17544      return;
17545    }
17546    this.state = State.PARSING;
17547    if (this.switchingTrack && audio) {
17548      this.completeAudioSwitch(this.switchingTrack);
17549    }
17550    if (initSegment != null && initSegment.tracks) {
17551      const mapFragment = frag.initSegment || frag;
17552      if (this.unhandledEncryptionError(initSegment, frag)) {
17553        return;
17554      }
17555      this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta);
17556      hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
17557        frag: mapFragment,
17558        id,
17559        tracks: initSegment.tracks
17560      });
17561      // Only flush audio from old audio tracks when PTS is known on new audio track
17562    }
17563    if (audio) {
17564      const {
17565        startPTS,
17566        endPTS,
17567        startDTS,
17568        endDTS
17569      } = audio;
17570      if (part) {
17571        part.elementaryStreams[ElementaryStreamTypes.AUDIO] = {
17572          startPTS,
17573          endPTS,
17574          startDTS,
17575          endDTS
17576        };
17577      }
17578      frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);
17579      this.bufferFragmentData(audio, frag, part, chunkMeta);
17580    }
17581    if (id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) {
17582      const emittedID3 = _extends({
17583        id,
17584        frag,
17585        details
17586      }, id3);
17587      hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);
17588    }
17589    if (text) {
17590      const emittedText = _extends({
17591        id,
17592        frag,
17593        details
17594      }, text);
17595      hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);
17596    }
17597  }
17598  _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) {
17599    if (this.state !== State.PARSING) {
17600      return;
17601    }
17602    // delete any video track found on audio transmuxer
17603    if (tracks.video) {
17604      delete tracks.video;
17605    }
17606    if (tracks.audiovideo) {
17607      delete tracks.audiovideo;
17608    }
17609
17610    // include levelCodec in audio and video tracks
17611    if (!tracks.audio) {
17612      return;
17613    }
17614    const track = tracks.audio;
17615    track.id = PlaylistLevelType.AUDIO;
17616    const variantAudioCodecs = currentLevel.audioCodec;
17617    this.log(`Init audio buffer, container:${track.container}, codecs[level/parsed]=[${variantAudioCodecs}/${track.codec}]`);
17618    // SourceBuffer will use track.levelCodec if defined
17619    if (variantAudioCodecs && variantAudioCodecs.split(',').length === 1) {
17620      track.levelCodec = variantAudioCodecs;
17621    }
17622    this.hls.trigger(Events.BUFFER_CODECS, tracks);
17623    const initSegment = track.initSegment;
17624    if (initSegment != null && initSegment.byteLength) {
17625      const segment = {
17626        type: 'audio',
17627        frag,
17628        part: null,
17629        chunkMeta,
17630        parent: frag.type,
17631        data: initSegment
17632      };
17633      this.hls.trigger(Events.BUFFER_APPENDING, segment);
17634    }
17635    // trigger handler right now
17636    this.tickImmediate();
17637  }
17638  loadFragment(frag, track, targetBufferTime) {
17639    // only load if fragment is not loaded or if in audio switch
17640    const fragState = this.fragmentTracker.getState(frag);
17641
17642    // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch
17643    if (this.switchingTrack || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
17644      var _track$details;
17645      if (!isMediaFragment(frag)) {
17646        this._loadInitSegment(frag, track);
17647      } else if ((_track$details = track.details) != null && _track$details.live && !this.initPTS[frag.cc]) {
17648        this.log(`Waiting for video PTS in continuity counter ${frag.cc} of live stream before loading audio fragment ${frag.sn} of level ${this.trackId}`);
17649        this.state = State.WAITING_INIT_PTS;
17650        const mainDetails = this.mainDetails;
17651        if (mainDetails && mainDetails.fragmentStart !== track.details.fragmentStart) {
17652          alignMediaPlaylistByPDT(track.details, mainDetails);
17653        }
17654      } else {
17655        super.loadFragment(frag, track, targetBufferTime);
17656      }
17657    } else {
17658      this.clearTrackerIfNeeded(frag);
17659    }
17660  }
17661  flushAudioIfNeeded(switchingTrack) {
17662    if (this.media && this.bufferedTrack) {
17663      const {
17664        name,
17665        lang,
17666        assocLang,
17667        characteristics,
17668        audioCodec,
17669        channels
17670      } = this.bufferedTrack;
17671      if (!matchesOption({
17672        name,
17673        lang,
17674        assocLang,
17675        characteristics,
17676        audioCodec,
17677        channels
17678      }, switchingTrack, audioMatchPredicate)) {
17679        if (useAlternateAudio(switchingTrack.url, this.hls)) {
17680          this.log('Switching audio track : flushing all audio');
17681          super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
17682          this.bufferedTrack = null;
17683        } else {
17684          // Main is being buffered. Set bufferedTrack so that it is flushed when switching back to alt-audio
17685          this.bufferedTrack = switchingTrack;
17686        }
17687      }
17688    }
17689  }
17690  completeAudioSwitch(switchingTrack) {
17691    const {
17692      hls
17693    } = this;
17694    this.flushAudioIfNeeded(switchingTrack);
17695    this.bufferedTrack = switchingTrack;
17696    this.switchingTrack = null;
17697    hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, switchingTrack));
17698  }
17699}
17700
17701class BasePlaylistController extends Logger {
17702  constructor(hls, logPrefix) {
17703    super(logPrefix, hls.logger);
17704    this.hls = void 0;
17705    this.canLoad = false;
17706    this.timer = -1;
17707    this.hls = hls;
17708  }
17709  destroy() {
17710    this.clearTimer();
17711    // @ts-ignore
17712    this.hls = this.log = this.warn = null;
17713  }
17714  clearTimer() {
17715    if (this.timer !== -1) {
17716      self.clearTimeout(this.timer);
17717      this.timer = -1;
17718    }
17719  }
17720  startLoad() {
17721    this.canLoad = true;
17722    this.loadPlaylist();
17723  }
17724  stopLoad() {
17725    this.canLoad = false;
17726    this.clearTimer();
17727  }
17728  switchParams(playlistUri, previous, current) {
17729    const renditionReports = previous == null ? void 0 : previous.renditionReports;
17730    if (renditionReports) {
17731      let foundIndex = -1;
17732      for (let i = 0; i < renditionReports.length; i++) {
17733        const attr = renditionReports[i];
17734        let uri;
17735        try {
17736          uri = new self.URL(attr.URI, previous.url).href;
17737        } catch (error) {
17738          this.warn(`Could not construct new URL for Rendition Report: ${error}`);
17739          uri = attr.URI || '';
17740        }
17741        // Use exact match. Otherwise, the last partial match, if any, will be used
17742        // (Playlist URI includes a query string that the Rendition Report does not)
17743        if (uri === playlistUri) {
17744          foundIndex = i;
17745          break;
17746        } else if (uri === playlistUri.substring(0, uri.length)) {
17747          foundIndex = i;
17748        }
17749      }
17750      if (foundIndex !== -1) {
17751        const attr = renditionReports[foundIndex];
17752        const msn = parseInt(attr['LAST-MSN']) || previous.lastPartSn;
17753        let part = parseInt(attr['LAST-PART']) || previous.lastPartIndex;
17754        if (this.hls.config.lowLatencyMode) {
17755          const currentGoal = Math.min(previous.age - previous.partTarget, previous.targetduration);
17756          if (part >= 0 && currentGoal > previous.partTarget) {
17757            part += 1;
17758          }
17759        }
17760        const skip = current && getSkipValue(current);
17761        return new HlsUrlParameters(msn, part >= 0 ? part : undefined, skip);
17762      }
17763    }
17764  }
17765  loadPlaylist(hlsUrlParameters) {
17766    // Loading is handled by the subclasses
17767    this.clearTimer();
17768  }
17769  loadingPlaylist(playlist, hlsUrlParameters) {
17770    // Loading is handled by the subclasses
17771    this.clearTimer();
17772  }
17773  shouldLoadPlaylist(playlist) {
17774    return this.canLoad && !!playlist && !!playlist.url && (!playlist.details || playlist.details.live);
17775  }
17776  getUrlWithDirectives(uri, hlsUrlParameters) {
17777    if (hlsUrlParameters) {
17778      try {
17779        return hlsUrlParameters.addDirectives(uri);
17780      } catch (error) {
17781        this.warn(`Could not construct new URL with HLS Delivery Directives: ${error}`);
17782      }
17783    }
17784    return uri;
17785  }
17786  playlistLoaded(index, data, previousDetails) {
17787    const {
17788      details,
17789      stats
17790    } = data;
17791
17792    // Set last updated date-time
17793    const now = self.performance.now();
17794    const elapsed = stats.loading.first ? Math.max(0, now - stats.loading.first) : 0;
17795    details.advancedDateTime = Date.now() - elapsed;
17796
17797    // shift fragment starts with timelineOffset
17798    const timelineOffset = this.hls.config.timelineOffset;
17799    if (timelineOffset !== details.appliedTimelineOffset) {
17800      const offset = Math.max(timelineOffset || 0, 0);
17801      details.appliedTimelineOffset = offset;
17802      details.fragments.forEach(frag => {
17803        frag.setStart(frag.playlistOffset + offset);
17804      });
17805    }
17806
17807    // if current playlist is a live playlist, arm a timer to reload it
17808    if (details.live || previousDetails != null && previousDetails.live) {
17809      const levelOrTrack = 'levelInfo' in data ? data.levelInfo : data.track;
17810      details.reloaded(previousDetails);
17811      // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments
17812      if (previousDetails && details.fragments.length > 0) {
17813        mergeDetails(previousDetails, details, this);
17814        const error = details.playlistParsingError;
17815        if (error) {
17816          this.warn(error);
17817          const hls = this.hls;
17818          if (!hls.config.ignorePlaylistParsingErrors) {
17819            var _details$fragments$;
17820            const {
17821              networkDetails
17822            } = data;
17823            hls.trigger(Events.ERROR, {
17824              type: ErrorTypes.NETWORK_ERROR,
17825              details: ErrorDetails.LEVEL_PARSING_ERROR,
17826              fatal: false,
17827              url: details.url,
17828              error,
17829              reason: error.message,
17830              level: data.level || undefined,
17831              parent: (_details$fragments$ = details.fragments[0]) == null ? void 0 : _details$fragments$.type,
17832              networkDetails,
17833              stats
17834            });
17835            return;
17836          }
17837          details.playlistParsingError = null;
17838        }
17839      }
17840      if (details.requestScheduled === -1) {
17841        details.requestScheduled = stats.loading.start;
17842      }
17843      const bufferInfo = this.hls.mainForwardBufferInfo;
17844      const position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0;
17845      const distanceToLiveEdgeMs = (details.edge - position) * 1000;
17846      const reloadInterval = computeReloadInterval(details, distanceToLiveEdgeMs);
17847      if (details.requestScheduled + reloadInterval < now) {
17848        details.requestScheduled = now;
17849      } else {
17850        details.requestScheduled += reloadInterval;
17851      }
17852      this.log(`live playlist ${index} ${details.advanced ? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex : details.updated ? 'UPDATED' : 'MISSED'}`);
17853      if (!this.canLoad || !details.live) {
17854        return;
17855      }
17856      let deliveryDirectives;
17857      let msn = undefined;
17858      let part = undefined;
17859      if (details.canBlockReload && details.endSN && details.advanced) {
17860        // Load level with LL-HLS delivery directives
17861        const lowLatencyMode = this.hls.config.lowLatencyMode;
17862        const lastPartSn = details.lastPartSn;
17863        const endSn = details.endSN;
17864        const lastPartIndex = details.lastPartIndex;
17865        const hasParts = lastPartIndex !== -1;
17866        const atLastPartOfSegment = lastPartSn === endSn;
17867        if (hasParts) {
17868          // When low latency mode is disabled, request the last part of the next segment
17869          if (atLastPartOfSegment) {
17870            msn = endSn + 1;
17871            part = lowLatencyMode ? 0 : lastPartIndex;
17872          } else {
17873            msn = lastPartSn;
17874            part = lowLatencyMode ? lastPartIndex + 1 : details.maxPartIndex;
17875          }
17876        } else {
17877          msn = endSn + 1;
17878        }
17879        // Low-Latency CDN Tune-in: "age" header and time since load indicates we're behind by more than one part
17880        // Update directives to obtain the Playlist that has the estimated additional duration of media
17881        const lastAdvanced = details.age;
17882        const cdnAge = lastAdvanced + details.ageHeader;
17883        let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
17884        if (currentGoal > 0) {
17885          if (cdnAge > details.targetduration * 3) {
17886            // Omit segment and part directives when the last response was more than 3 target durations ago,
17887            this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
17888            msn = undefined;
17889            part = undefined;
17890          } else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
17891            // If we attempted to get the next or latest playlist update, but currentGoal increased,
17892            // then we either can't catchup, or the "age" header cannot be trusted.
17893            this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
17894            currentGoal = 0;
17895          } else {
17896            const segments = Math.floor(currentGoal / details.targetduration);
17897            msn += segments;
17898            if (part !== undefined) {
17899              const parts = Math.round(currentGoal % details.targetduration / details.partTarget);
17900              part += parts;
17901            }
17902            this.log(`CDN Tune-in age: ${details.ageHeader}s last advanced ${lastAdvanced.toFixed(2)}s goal: ${currentGoal} skip sn ${segments} to part ${part}`);
17903          }
17904          details.tuneInGoal = currentGoal;
17905        }
17906        deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
17907        if (lowLatencyMode || !atLastPartOfSegment) {
17908          details.requestScheduled = now;
17909          this.loadingPlaylist(levelOrTrack, deliveryDirectives);
17910          return;
17911        }
17912      } else if (details.canBlockReload || details.canSkipUntil) {
17913        deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part);
17914      }
17915      if (deliveryDirectives && msn !== undefined && details.canBlockReload) {
17916        details.requestScheduled = stats.loading.first + Math.max(reloadInterval - elapsed * 2, reloadInterval / 2);
17917      }
17918      this.scheduleLoading(levelOrTrack, deliveryDirectives, details);
17919    } else {
17920      this.clearTimer();
17921    }
17922  }
17923  scheduleLoading(levelOrTrack, deliveryDirectives, updatedDetails) {
17924    const details = updatedDetails || levelOrTrack.details;
17925    if (!details) {
17926      this.loadingPlaylist(levelOrTrack, deliveryDirectives);
17927      return;
17928    }
17929    const now = self.performance.now();
17930    const requestScheduled = details.requestScheduled;
17931    if (now >= requestScheduled) {
17932      this.loadingPlaylist(levelOrTrack, deliveryDirectives);
17933      return;
17934    }
17935    const estimatedTimeUntilUpdate = requestScheduled - now;
17936    this.log(`reload live playlist ${levelOrTrack.name || levelOrTrack.bitrate + 'bps'} in ${Math.round(estimatedTimeUntilUpdate)} ms`);
17937    this.clearTimer();
17938    this.timer = self.setTimeout(() => this.loadingPlaylist(levelOrTrack, deliveryDirectives), estimatedTimeUntilUpdate);
17939  }
17940  getDeliveryDirectives(details, previousDeliveryDirectives, msn, part) {
17941    let skip = getSkipValue(details);
17942    if (previousDeliveryDirectives != null && previousDeliveryDirectives.skip && details.deltaUpdateFailed) {
17943      msn = previousDeliveryDirectives.msn;
17944      part = previousDeliveryDirectives.part;
17945      skip = HlsSkip.No;
17946    }
17947    return new HlsUrlParameters(msn, part, skip);
17948  }
17949  checkRetry(errorEvent) {
17950    const errorDetails = errorEvent.details;
17951    const isTimeout = isTimeoutError(errorEvent);
17952    const errorAction = errorEvent.errorAction;
17953    const {
17954      action,
17955      retryCount = 0,
17956      retryConfig
17957    } = errorAction || {};
17958    const retry = !!errorAction && !!retryConfig && (action === NetworkErrorAction.RetryRequest || !errorAction.resolved && action === NetworkErrorAction.SendAlternateToPenaltyBox);
17959    if (retry) {
17960      var _errorEvent$context;
17961      if (retryCount >= retryConfig.maxNumRetry) {
17962        return false;
17963      }
17964      if (isTimeout && (_errorEvent$context = errorEvent.context) != null && _errorEvent$context.deliveryDirectives) {
17965        // The LL-HLS request already timed out so retry immediately
17966        this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" without delivery-directives`);
17967        this.loadPlaylist();
17968      } else {
17969        const delay = getRetryDelay(retryConfig, retryCount);
17970        // Schedule level/track reload
17971        this.clearTimer();
17972        this.timer = self.setTimeout(() => this.loadPlaylist(), delay);
17973        this.warn(`Retrying playlist loading ${retryCount + 1}/${retryConfig.maxNumRetry} after "${errorDetails}" in ${delay}ms`);
17974      }
17975      // `levelRetry = true` used to inform other controllers that a retry is happening
17976      errorEvent.levelRetry = true;
17977      errorAction.resolved = true;
17978    }
17979    return retry;
17980  }
17981}
17982
17983function subtitleOptionsIdentical(trackList1, trackList2) {
17984  if (trackList1.length !== trackList2.length) {
17985    return false;
17986  }
17987  for (let i = 0; i < trackList1.length; i++) {
17988    if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) {
17989      return false;
17990    }
17991  }
17992  return true;
17993}
17994function mediaAttributesIdentical(attrs1, attrs2, customAttributes) {
17995  // Media options with the same rendition ID must be bit identical
17996  const stableRenditionId = attrs1['STABLE-RENDITION-ID'];
17997  if (stableRenditionId && !customAttributes) {
17998    return stableRenditionId === attrs2['STABLE-RENDITION-ID'];
17999  }
18000  // When rendition ID is not present, compare attributes
18001  return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]);
18002}
18003function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) {
18004  return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase());
18005}
18006
18007class AudioTrackController extends BasePlaylistController {
18008  constructor(hls) {
18009    super(hls, 'audio-track-controller');
18010    this.tracks = [];
18011    this.groupIds = null;
18012    this.tracksInGroup = [];
18013    this.trackId = -1;
18014    this.currentTrack = null;
18015    this.selectDefaultTrack = true;
18016    this.registerListeners();
18017  }
18018  registerListeners() {
18019    const {
18020      hls
18021    } = this;
18022    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18023    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18024    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
18025    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
18026    hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
18027    hls.on(Events.ERROR, this.onError, this);
18028  }
18029  unregisterListeners() {
18030    const {
18031      hls
18032    } = this;
18033    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18034    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18035    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
18036    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
18037    hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
18038    hls.off(Events.ERROR, this.onError, this);
18039  }
18040  destroy() {
18041    this.unregisterListeners();
18042    this.tracks.length = 0;
18043    this.tracksInGroup.length = 0;
18044    this.currentTrack = null;
18045    super.destroy();
18046  }
18047  onManifestLoading() {
18048    this.tracks = [];
18049    this.tracksInGroup = [];
18050    this.groupIds = null;
18051    this.currentTrack = null;
18052    this.trackId = -1;
18053    this.selectDefaultTrack = true;
18054  }
18055  onManifestParsed(event, data) {
18056    this.tracks = data.audioTracks || [];
18057  }
18058  onAudioTrackLoaded(event, data) {
18059    const {
18060      id,
18061      groupId,
18062      details
18063    } = data;
18064    const trackInActiveGroup = this.tracksInGroup[id];
18065    if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) {
18066      this.warn(`Audio track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`);
18067      return;
18068    }
18069    const curDetails = trackInActiveGroup.details;
18070    trackInActiveGroup.details = data.details;
18071    this.log(`Audio track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);
18072    if (id === this.trackId) {
18073      this.playlistLoaded(id, data, curDetails);
18074    }
18075  }
18076  onLevelLoading(event, data) {
18077    this.switchLevel(data.level);
18078  }
18079  onLevelSwitching(event, data) {
18080    this.switchLevel(data.level);
18081  }
18082  switchLevel(levelIndex) {
18083    const levelInfo = this.hls.levels[levelIndex];
18084    if (!levelInfo) {
18085      return;
18086    }
18087    const audioGroups = levelInfo.audioGroups || null;
18088    const currentGroups = this.groupIds;
18089    let currentTrack = this.currentTrack;
18090    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)) {
18091      this.groupIds = audioGroups;
18092      this.trackId = -1;
18093      this.currentTrack = null;
18094      const audioTracks = this.tracks.filter(track => !audioGroups || audioGroups.indexOf(track.groupId) !== -1);
18095      if (audioTracks.length) {
18096        // Disable selectDefaultTrack if there are no default tracks
18097        if (this.selectDefaultTrack && !audioTracks.some(track => track.default)) {
18098          this.selectDefaultTrack = false;
18099        }
18100        // track.id should match hls.audioTracks index
18101        audioTracks.forEach((track, i) => {
18102          track.id = i;
18103        });
18104      } else if (!currentTrack && !this.tracksInGroup.length) {
18105        // Do not dispatch AUDIO_TRACKS_UPDATED when there were and are no tracks
18106        return;
18107      }
18108      this.tracksInGroup = audioTracks;
18109
18110      // Find preferred track
18111      const audioPreference = this.hls.config.audioPreference;
18112      if (!currentTrack && audioPreference) {
18113        const groupIndex = findMatchingOption(audioPreference, audioTracks, audioMatchPredicate);
18114        if (groupIndex > -1) {
18115          currentTrack = audioTracks[groupIndex];
18116        } else {
18117          const allIndex = findMatchingOption(audioPreference, this.tracks);
18118          currentTrack = this.tracks[allIndex];
18119        }
18120      }
18121
18122      // Select initial track
18123      let trackId = this.findTrackId(currentTrack);
18124      if (trackId === -1 && currentTrack) {
18125        trackId = this.findTrackId(null);
18126      }
18127
18128      // Dispatch events and load track if needed
18129      const audioTracksUpdated = {
18130        audioTracks
18131      };
18132      this.log(`Updating audio tracks, ${audioTracks.length} track(s) found in group(s): ${audioGroups == null ? void 0 : audioGroups.join(',')}`);
18133      this.hls.trigger(Events.AUDIO_TRACKS_UPDATED, audioTracksUpdated);
18134      const selectedTrackId = this.trackId;
18135      if (trackId !== -1 && selectedTrackId === -1) {
18136        this.setAudioTrack(trackId);
18137      } else if (audioTracks.length && selectedTrackId === -1) {
18138        var _this$groupIds;
18139        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}`);
18140        this.warn(error.message);
18141        this.hls.trigger(Events.ERROR, {
18142          type: ErrorTypes.MEDIA_ERROR,
18143          details: ErrorDetails.AUDIO_TRACK_LOAD_ERROR,
18144          fatal: true,
18145          error
18146        });
18147      }
18148    }
18149  }
18150  onError(event, data) {
18151    if (data.fatal || !data.context) {
18152      return;
18153    }
18154    if (data.context.type === PlaylistContextType.AUDIO_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) {
18155      this.checkRetry(data);
18156    }
18157  }
18158  get allAudioTracks() {
18159    return this.tracks;
18160  }
18161  get audioTracks() {
18162    return this.tracksInGroup;
18163  }
18164  get audioTrack() {
18165    return this.trackId;
18166  }
18167  set audioTrack(newId) {
18168    // If audio track is selected from API then don't choose from the manifest default track
18169    this.selectDefaultTrack = false;
18170    this.setAudioTrack(newId);
18171  }
18172  setAudioOption(audioOption) {
18173    const hls = this.hls;
18174    hls.config.audioPreference = audioOption;
18175    if (audioOption) {
18176      const allAudioTracks = this.allAudioTracks;
18177      this.selectDefaultTrack = false;
18178      if (allAudioTracks.length) {
18179        // First see if current option matches (no switch op)
18180        const currentTrack = this.currentTrack;
18181        if (currentTrack && matchesOption(audioOption, currentTrack, audioMatchPredicate)) {
18182          return currentTrack;
18183        }
18184        // Find option in available tracks (tracksInGroup)
18185        const groupIndex = findMatchingOption(audioOption, this.tracksInGroup, audioMatchPredicate);
18186        if (groupIndex > -1) {
18187          const track = this.tracksInGroup[groupIndex];
18188          this.setAudioTrack(groupIndex);
18189          return track;
18190        } else if (currentTrack) {
18191          // Find option in nearest level audio group
18192          let searchIndex = hls.loadLevel;
18193          if (searchIndex === -1) {
18194            searchIndex = hls.firstAutoLevel;
18195          }
18196          const switchIndex = findClosestLevelWithAudioGroup(audioOption, hls.levels, allAudioTracks, searchIndex, audioMatchPredicate);
18197          if (switchIndex === -1) {
18198            // could not find matching variant
18199            return null;
18200          }
18201          // and switch level to acheive the audio group switch
18202          hls.nextLoadLevel = switchIndex;
18203        }
18204        if (audioOption.channels || audioOption.audioCodec) {
18205          // Could not find a match with codec / channels predicate
18206          // Find a match without channels or codec
18207          const withoutCodecAndChannelsMatch = findMatchingOption(audioOption, allAudioTracks);
18208          if (withoutCodecAndChannelsMatch > -1) {
18209            return allAudioTracks[withoutCodecAndChannelsMatch];
18210          }
18211        }
18212      }
18213    }
18214    return null;
18215  }
18216  setAudioTrack(newId) {
18217    const tracks = this.tracksInGroup;
18218
18219    // check if level idx is valid
18220    if (newId < 0 || newId >= tracks.length) {
18221      this.warn(`Invalid audio track id: ${newId}`);
18222      return;
18223    }
18224    this.selectDefaultTrack = false;
18225    const lastTrack = this.currentTrack;
18226    const track = tracks[newId];
18227    const trackLoaded = track.details && !track.details.live;
18228    if (newId === this.trackId && track === lastTrack && trackLoaded) {
18229      return;
18230    }
18231    this.log(`Switching to audio-track ${newId} "${track.name}" lang:${track.lang} group:${track.groupId} channels:${track.channels}`);
18232    this.trackId = newId;
18233    this.currentTrack = track;
18234    this.hls.trigger(Events.AUDIO_TRACK_SWITCHING, _objectSpread2({}, track));
18235    // Do not reload track unless live
18236    if (trackLoaded) {
18237      return;
18238    }
18239    const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details);
18240    this.loadPlaylist(hlsUrlParameters);
18241  }
18242  findTrackId(currentTrack) {
18243    const audioTracks = this.tracksInGroup;
18244    for (let i = 0; i < audioTracks.length; i++) {
18245      const track = audioTracks[i];
18246      if (this.selectDefaultTrack && !track.default) {
18247        continue;
18248      }
18249      if (!currentTrack || matchesOption(currentTrack, track, audioMatchPredicate)) {
18250        return i;
18251      }
18252    }
18253    if (currentTrack) {
18254      const {
18255        name,
18256        lang,
18257        assocLang,
18258        characteristics,
18259        audioCodec,
18260        channels
18261      } = currentTrack;
18262      for (let i = 0; i < audioTracks.length; i++) {
18263        const track = audioTracks[i];
18264        if (matchesOption({
18265          name,
18266          lang,
18267          assocLang,
18268          characteristics,
18269          audioCodec,
18270          channels
18271        }, track, audioMatchPredicate)) {
18272          return i;
18273        }
18274      }
18275      for (let i = 0; i < audioTracks.length; i++) {
18276        const track = audioTracks[i];
18277        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) {
18278          return i;
18279        }
18280      }
18281      for (let i = 0; i < audioTracks.length; i++) {
18282        const track = audioTracks[i];
18283        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])) {
18284          return i;
18285        }
18286      }
18287    }
18288    return -1;
18289  }
18290  loadPlaylist(hlsUrlParameters) {
18291    super.loadPlaylist();
18292    const audioTrack = this.currentTrack;
18293    if (!this.shouldLoadPlaylist(audioTrack)) {
18294      return;
18295    }
18296    // Do not load audio rendition with URI matching main variant URI
18297    if (useAlternateAudio(audioTrack.url, this.hls)) {
18298      this.scheduleLoading(audioTrack, hlsUrlParameters);
18299    }
18300  }
18301  loadingPlaylist(audioTrack, hlsUrlParameters) {
18302    super.loadingPlaylist(audioTrack, hlsUrlParameters);
18303    const id = audioTrack.id;
18304    const groupId = audioTrack.groupId;
18305    const url = this.getUrlWithDirectives(audioTrack.url, hlsUrlParameters);
18306    const details = audioTrack.details;
18307    const age = details == null ? void 0 : details.age;
18308    this.log(`Loading audio-track ${id} "${audioTrack.name}" lang:${audioTrack.lang} group:${groupId}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== undefined ? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part : ''}${age && details.live ? ' age ' + age.toFixed(1) + (details.type ? ' ' + details.type || 
183080
vendor: 191,085 bytes, lines 18308-23967
18308 : '') : ''} ${url}`);
18309    this.hls.trigger(Events.AUDIO_TRACK_LOADING, {
18310      url,
18311      id,
18312      groupId,
18313      deliveryDirectives: hlsUrlParameters || null,
18314      track: audioTrack
18315    });
18316  }
18317}
18318
18319class BufferOperationQueue {
18320  constructor(sourceBufferReference) {
18321    this.tracks = void 0;
18322    this.queues = {
18323      video: [],
18324      audio: [],
18325      audiovideo: []
18326    };
18327    this.tracks = sourceBufferReference;
18328  }
18329  destroy() {
18330    this.tracks = this.queues = null;
18331  }
18332  append(operation, type, pending) {
18333    if (this.queues === null || this.tracks === null) {
18334      return;
18335    }
18336    const queue = this.queues[type];
18337    queue.push(operation);
18338    if (queue.length === 1 && !pending) {
18339      this.executeNext(type);
18340    }
18341  }
18342  appendBlocker(type) {
18343    return new Promise(resolve => {
18344      const operation = {
18345        label: 'async-blocker',
18346        execute: resolve,
18347        onStart: () => {},
18348        onComplete: () => {},
18349        onError: () => {}
18350      };
18351      this.append(operation, type);
18352    });
18353  }
18354  prependBlocker(type) {
18355    return new Promise(resolve => {
18356      if (this.queues) {
18357        const operation = {
18358          label: 'async-blocker-prepend',
18359          execute: resolve,
18360          onStart: () => {},
18361          onComplete: () => {},
18362          onError: () => {}
18363        };
18364        this.queues[type].unshift(operation);
18365      }
18366    });
18367  }
18368  removeBlockers() {
18369    if (this.queues === null) {
18370      return;
18371    }
18372    [this.queues.video, this.queues.audio, this.queues.audiovideo].forEach(queue => {
18373      var _queue$;
18374      const label = (_queue$ = queue[0]) == null ? void 0 : _queue$.label;
18375      if (label === 'async-blocker' || label === 'async-blocker-prepend') {
18376        queue[0].execute();
18377        queue.splice(0, 1);
18378      }
18379    });
18380  }
18381  unblockAudio(op) {
18382    if (this.queues === null) {
18383      return;
18384    }
18385    const queue = this.queues.audio;
18386    if (queue[0] === op) {
18387      this.shiftAndExecuteNext('audio');
18388    }
18389  }
18390  executeNext(type) {
18391    if (this.queues === null || this.tracks === null) {
18392      return;
18393    }
18394    const queue = this.queues[type];
18395    if (queue.length) {
18396      const operation = queue[0];
18397      try {
18398        // Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations
18399        // which do not end with this event must call _onSBUpdateEnd manually
18400        operation.execute();
18401      } catch (error) {
18402        var _this$tracks$type;
18403        operation.onError(error);
18404        if (this.queues === null || this.tracks === null) {
18405          return;
18406        }
18407
18408        // Only shift the current operation off, otherwise the updateend handler will do this for us
18409        const sb = (_this$tracks$type = this.tracks[type]) == null ? void 0 : _this$tracks$type.buffer;
18410        if (!(sb != null && sb.updating)) {
18411          this.shiftAndExecuteNext(type);
18412        }
18413      }
18414    }
18415  }
18416  shiftAndExecuteNext(type) {
18417    if (this.queues === null) {
18418      return;
18419    }
18420    this.queues[type].shift();
18421    this.executeNext(type);
18422  }
18423  current(type) {
18424    var _this$queues;
18425    return ((_this$queues = this.queues) == null ? void 0 : _this$queues[type][0]) || null;
18426  }
18427  toString() {
18428    const {
18429      queues,
18430      tracks
18431    } = this;
18432    if (queues === null || tracks === null) {
18433      return `<destroyed>`;
18434    }
18435    return `
18436${this.list('video')}
18437${this.list('audio')}
18438${this.list('audiovideo')}}`;
18439  }
18440  list(type) {
18441    var _this$queues2, _this$tracks;
18442    return (_this$queues2 = this.queues) != null && _this$queues2[type] || (_this$tracks = this.tracks) != null && _this$tracks[type] ? `${type}: (${this.listSbInfo(type)}) ${this.listOps(type)}` : '';
18443  }
18444  listSbInfo(type) {
18445    var _this$tracks2;
18446    const track = (_this$tracks2 = this.tracks) == null ? void 0 : _this$tracks2[type];
18447    const sb = track == null ? void 0 : track.buffer;
18448    if (!sb) {
18449      return 'none';
18450    }
18451    return `SourceBuffer${sb.updating ? ' updating' : ''}${track.ended ? ' ended' : ''}${track.ending ? ' ending' : ''}`;
18452  }
18453  listOps(type) {
18454    var _this$queues3;
18455    return ((_this$queues3 = this.queues) == null ? void 0 : _this$queues3[type].map(op => op.label).join(', ')) || '';
18456  }
18457}
18458
18459const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
18460const TRACK_REMOVED_ERROR_NAME = 'HlsJsTrackRemovedError';
18461class HlsJsTrackRemovedError extends Error {
18462  constructor(message) {
18463    super(message);
18464    this.name = TRACK_REMOVED_ERROR_NAME;
18465  }
18466}
18467class BufferController extends Logger {
18468  constructor(hls, fragmentTracker) {
18469    super('buffer-controller', hls.logger);
18470    this.hls = void 0;
18471    this.fragmentTracker = void 0;
18472    // The level details used to determine duration, target-duration and live
18473    this.details = null;
18474    // cache the self generated object url to detect hijack of video tag
18475    this._objectUrl = null;
18476    // A queue of buffer operations which require the SourceBuffer to not be updating upon execution
18477    this.operationQueue = null;
18478    // The total number track codecs expected before any sourceBuffers are created (2: audio and video or 1: audiovideo | audio | video)
18479    this.bufferCodecEventsTotal = 0;
18480    // A reference to the attached media element
18481    this.media = null;
18482    // A reference to the active media source
18483    this.mediaSource = null;
18484    // Last MP3 audio chunk appended
18485    this.lastMpegAudioChunk = null;
18486    // Audio fragment blocked from appending until corresponding video appends or context changes
18487    this.blockedAudioAppend = null;
18488    // Keep track of video append position for unblocking audio
18489    this.lastVideoAppendEnd = 0;
18490    // Whether or not to use ManagedMediaSource API and append source element to media element.
18491    this.appendSource = void 0;
18492    // Transferred MediaSource information used to detmerine if duration end endstream may be appended
18493    this.transferData = void 0;
18494    // Directives used to override default MediaSource handling
18495    this.overrides = void 0;
18496    // Error counters
18497    this.appendErrors = {
18498      audio: 0,
18499      video: 0,
18500      audiovideo: 0
18501    };
18502    // Record of required or created buffers by type. SourceBuffer is stored in Track.buffer once created.
18503    this.tracks = {};
18504    // Array of SourceBuffer type and SourceBuffer (or null). One entry per TrackSet in this.tracks.
18505    this.sourceBuffers = [[null, null], [null, null]];
18506    this._onEndStreaming = event => {
18507      var _this$mediaSource;
18508      if (!this.hls) {
18509        return;
18510      }
18511      if (((_this$mediaSource = this.mediaSource) == null ? void 0 : _this$mediaSource.readyState) !== 'open') {
18512        return;
18513      }
18514      this.hls.pauseBuffering();
18515    };
18516    this._onStartStreaming = event => {
18517      if (!this.hls) {
18518        return;
18519      }
18520      this.hls.resumeBuffering();
18521    };
18522    // Keep as arrow functions so that we can directly reference these functions directly as event listeners
18523    this._onMediaSourceOpen = e => {
18524      const {
18525        media,
18526        mediaSource
18527      } = this;
18528      if (e) {
18529        this.log('Media source opened');
18530      }
18531      if (!media || !mediaSource) {
18532        return;
18533      }
18534      // once received, don't listen anymore to sourceopen event
18535      mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
18536      media.removeEventListener('emptied', this._onMediaEmptied);
18537      this.updateDuration();
18538      this.hls.trigger(Events.MEDIA_ATTACHED, {
18539        media,
18540        mediaSource: mediaSource
18541      });
18542      if (this.mediaSource !== null) {
18543        this.checkPendingTracks();
18544      }
18545    };
18546    this._onMediaSourceClose = () => {
18547      this.log('Media source closed');
18548    };
18549    this._onMediaSourceEnded = () => {
18550      this.log('Media source ended');
18551    };
18552    this._onMediaEmptied = () => {
18553      const {
18554        mediaSrc,
18555        _objectUrl
18556      } = this;
18557      if (mediaSrc !== _objectUrl) {
18558        this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
18559      }
18560    };
18561    this.hls = hls;
18562    this.fragmentTracker = fragmentTracker;
18563    this.appendSource = isManagedMediaSource(getMediaSource(hls.config.preferManagedMediaSource));
18564    this.initTracks();
18565    this.registerListeners();
18566  }
18567  hasSourceTypes() {
18568    return Object.keys(this.tracks).length > 0;
18569  }
18570  destroy() {
18571    this.unregisterListeners();
18572    this.details = null;
18573    this.lastMpegAudioChunk = this.blockedAudioAppend = null;
18574    this.transferData = this.overrides = undefined;
18575    if (this.operationQueue) {
18576      this.operationQueue.destroy();
18577      this.operationQueue = null;
18578    }
18579    // @ts-ignore
18580    this.hls = this.fragmentTracker = null;
18581    // @ts-ignore
18582    this._onMediaSourceOpen = this._onMediaSourceClose = null;
18583    // @ts-ignore
18584    this._onMediaSourceEnded = null;
18585    // @ts-ignore
18586    this._onStartStreaming = this._onEndStreaming = null;
18587  }
18588  registerListeners() {
18589    const {
18590      hls
18591    } = this;
18592    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
18593    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18594    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18595    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18596    hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
18597    hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this);
18598    hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
18599    hls.on(Events.BUFFER_EOS, this.onBufferEos, this);
18600    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
18601    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
18602    hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
18603    hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
18604    hls.on(Events.ERROR, this.onError, this);
18605  }
18606  unregisterListeners() {
18607    const {
18608      hls
18609    } = this;
18610    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
18611    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
18612    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
18613    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
18614    hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
18615    hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this);
18616    hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
18617    hls.off(Events.BUFFER_EOS, this.onBufferEos, this);
18618    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
18619    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
18620    hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
18621    hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
18622    hls.off(Events.ERROR, this.onError, this);
18623  }
18624  transferMedia() {
18625    const {
18626      media,
18627      mediaSource
18628    } = this;
18629    if (!media) {
18630      return null;
18631    }
18632    const tracks = {};
18633    if (this.operationQueue) {
18634      const updating = this.isUpdating();
18635      if (!updating) {
18636        this.operationQueue.removeBlockers();
18637      }
18638      const queued = this.isQueued();
18639      if (updating || queued) {
18640        this.warn(`Transfering MediaSource with${queued ? ' operations in queue' : ''}${updating ? ' updating SourceBuffer(s)' : ''} ${this.operationQueue}`);
18641      }
18642      this.operationQueue.destroy();
18643    }
18644    const transferData = this.transferData;
18645    if (!this.sourceBufferCount && transferData && transferData.mediaSource === mediaSource) {
18646      _extends(tracks, transferData.tracks);
18647    } else {
18648      this.sourceBuffers.forEach(tuple => {
18649        const [type] = tuple;
18650        if (type) {
18651          tracks[type] = _extends({}, this.tracks[type]);
18652          this.removeBuffer(type);
18653        }
18654        tuple[0] = tuple[1] = null;
18655      });
18656    }
18657    return {
18658      media,
18659      mediaSource,
18660      tracks
18661    };
18662  }
18663  initTracks() {
18664    const tracks = {};
18665    this.sourceBuffers = [[null, null], [null, null]];
18666    this.tracks = tracks;
18667    this.resetQueue();
18668    this.resetAppendErrors();
18669    this.lastMpegAudioChunk = this.blockedAudioAppend = null;
18670    this.lastVideoAppendEnd = 0;
18671  }
18672  onManifestLoading() {
18673    this.bufferCodecEventsTotal = 0;
18674    this.details = null;
18675  }
18676  onManifestParsed(event, data) {
18677    var _this$transferData;
18678    // in case of alt audio 2 BUFFER_CODECS events will be triggered, one per stream controller
18679    // sourcebuffers will be created all at once when the expected nb of tracks will be reached
18680    // in case alt audio is not used, only one BUFFER_CODEC event will be fired from main stream controller
18681    // it will contain the expected nb of source buffers, no need to compute it
18682    let codecEvents = 2;
18683    if (data.audio && !data.video || !data.altAudio) {
18684      codecEvents = 1;
18685    }
18686    this.bufferCodecEventsTotal = codecEvents;
18687    this.log(`${codecEvents} bufferCodec event(s) expected.`);
18688    if ((_this$transferData = this.transferData) != null && _this$transferData.mediaSource && this.sourceBufferCount && codecEvents) {
18689      this.bufferCreated();
18690    }
18691  }
18692  onMediaAttaching(event, data) {
18693    const media = this.media = data.media;
18694    this.transferData = this.overrides = undefined;
18695    const MediaSource = getMediaSource(this.appendSource);
18696    if (MediaSource) {
18697      const transferringMedia = !!data.mediaSource;
18698      if (transferringMedia || data.overrides) {
18699        this.transferData = data;
18700        this.overrides = data.overrides;
18701      }
18702      const ms = this.mediaSource = data.mediaSource || new MediaSource();
18703      this.assignMediaSource(ms);
18704      if (transferringMedia) {
18705        this._objectUrl = media.src;
18706        this.attachTransferred();
18707      } else {
18708        // cache the locally generated object url
18709        const objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
18710        // link video and media Source
18711        if (this.appendSource) {
18712          try {
18713            media.removeAttribute('src');
18714            // ManagedMediaSource will not open without disableRemotePlayback set to false or source alternatives
18715            const MMS = self.ManagedMediaSource;
18716            media.disableRemotePlayback = media.disableRemotePlayback || MMS && ms instanceof MMS;
18717            removeSourceChildren(media);
18718            addSource(media, objectUrl);
18719            media.load();
18720          } catch (error) {
18721            media.src = objectUrl;
18722          }
18723        } else {
18724          media.src = objectUrl;
18725        }
18726      }
18727      media.addEventListener('emptied', this._onMediaEmptied);
18728    }
18729  }
18730  assignMediaSource(ms) {
18731    var _this$transferData2, _ms$constructor;
18732    this.log(`${((_this$transferData2 = this.transferData) == null ? void 0 : _this$transferData2.mediaSource) === ms ? 'transferred' : 'created'} media source: ${(_ms$constructor = ms.constructor) == null ? void 0 : _ms$constructor.name}`);
18733    // MediaSource listeners are arrow functions with a lexical scope, and do not need to be bound
18734    ms.addEventListener('sourceopen', this._onMediaSourceOpen);
18735    ms.addEventListener('sourceended', this._onMediaSourceEnded);
18736    ms.addEventListener('sourceclose', this._onMediaSourceClose);
18737    if (this.appendSource) {
18738      ms.addEventListener('startstreaming', this._onStartStreaming);
18739      ms.addEventListener('endstreaming', this._onEndStreaming);
18740    }
18741  }
18742  attachTransferred() {
18743    const media = this.media;
18744    const data = this.transferData;
18745    if (!data || !media) {
18746      return;
18747    }
18748    const requiredTracks = this.tracks;
18749    const transferredTracks = data.tracks;
18750    const trackNames = transferredTracks ? Object.keys(transferredTracks) : null;
18751    const trackCount = trackNames ? trackNames.length : 0;
18752    const mediaSourceOpenCallback = () => {
18753      // eslint-disable-next-line @typescript-eslint/no-floating-promises
18754      Promise.resolve().then(() => {
18755        if (this.media && this.mediaSourceOpenOrEnded) {
18756          this._onMediaSourceOpen();
18757        }
18758      });
18759    };
18760    if (transferredTracks && trackNames && trackCount) {
18761      if (!this.tracksReady) {
18762        // Wait for CODECS event(s)
18763        this.hls.config.startFragPrefetch = true;
18764        this.log(`attachTransferred: waiting for SourceBuffer track info`);
18765        return;
18766      }
18767      this.log(`attachTransferred: (bufferCodecEventsTotal ${this.bufferCodecEventsTotal})
18768required tracks: ${stringify(requiredTracks, (key, value) => key === 'initSegment' ? undefined : value)};
18769transfer tracks: ${stringify(transferredTracks, (key, value) => key === 'initSegment' ? undefined : value)}}`);
18770      if (!isCompatibleTrackChange(transferredTracks, requiredTracks)) {
18771        // destroy attaching media source
18772        data.mediaSource = null;
18773        data.tracks = undefined;
18774        const currentTime = media.currentTime;
18775        const details = this.details;
18776        const startTime = Math.max(currentTime, (details == null ? void 0 : details.fragments[0].start) || 0);
18777        if (startTime - currentTime > 1) {
18778          this.log(`attachTransferred: waiting for playback to reach new tracks start time ${currentTime} -> ${startTime}`);
18779          return;
18780        }
18781        this.warn(`attachTransferred: resetting MediaSource for incompatible tracks ("${Object.keys(transferredTracks)}"->"${Object.keys(requiredTracks)}") start time: ${startTime} currentTime: ${currentTime}`);
18782        this.onMediaDetaching(Events.MEDIA_DETACHING, {});
18783        this.onMediaAttaching(Events.MEDIA_ATTACHING, data);
18784        media.currentTime = startTime;
18785        return;
18786      }
18787      this.transferData = undefined;
18788      trackNames.forEach(trackName => {
18789        const type = trackName;
18790        const track = transferredTracks[type];
18791        if (track) {
18792          const sb = track.buffer;
18793          if (sb) {
18794            // Purge fragment tracker of ejected segments for existing buffer
18795            const fragmentTracker = this.fragmentTracker;
18796            const playlistType = track.id;
18797            if (fragmentTracker.hasFragments(playlistType) || fragmentTracker.hasParts(playlistType)) {
18798              const bufferedTimeRanges = BufferHelper.getBuffered(sb);
18799              fragmentTracker.detectEvictedFragments(type, bufferedTimeRanges, playlistType, null, true);
18800            }
18801            // Transfer SourceBuffer
18802            const sbIndex = sourceBufferNameToIndex(type);
18803            const sbTuple = [type, sb];
18804            this.sourceBuffers[sbIndex] = sbTuple;
18805            if (sb.updating && this.operationQueue) {
18806              // eslint-disable-next-line @typescript-eslint/no-floating-promises
18807              this.operationQueue.prependBlocker(type);
18808            }
18809            this.trackSourceBuffer(type, track);
18810          }
18811        }
18812      });
18813      mediaSourceOpenCallback();
18814      this.bufferCreated();
18815    } else {
18816      this.log(`attachTransferred: MediaSource w/o SourceBuffers`);
18817      mediaSourceOpenCallback();
18818    }
18819  }
18820  get mediaSourceOpenOrEnded() {
18821    var _this$mediaSource2;
18822    const readyState = (_this$mediaSource2 = this.mediaSource) == null ? void 0 : _this$mediaSource2.readyState;
18823    return readyState === 'open' || readyState === 'ended';
18824  }
18825  onMediaDetaching(event, data) {
18826    const transferringMedia = !!data.transferMedia;
18827    this.transferData = this.overrides = undefined;
18828    const {
18829      media,
18830      mediaSource,
18831      _objectUrl
18832    } = this;
18833    if (mediaSource) {
18834      this.log(`media source ${transferringMedia ? 'transferring' : 'detaching'}`);
18835      if (transferringMedia) {
18836        // Detach SourceBuffers without removing from MediaSource
18837        // and leave `tracks` (required SourceBuffers configuration)
18838        this.sourceBuffers.forEach(([type]) => {
18839          if (type) {
18840            this.removeBuffer(type);
18841          }
18842        });
18843        this.resetQueue();
18844      } else {
18845        if (this.mediaSourceOpenOrEnded) {
18846          const open = mediaSource.readyState === 'open';
18847          try {
18848            const sourceBuffers = mediaSource.sourceBuffers;
18849            for (let i = sourceBuffers.length; i--;) {
18850              if (open) {
18851                sourceBuffers[i].abort();
18852              }
18853              mediaSource.removeSourceBuffer(sourceBuffers[i]);
18854            }
18855            if (open) {
18856              // endOfStream could trigger exception if any sourcebuffer is in updating state
18857              // we don't really care about checking sourcebuffer state here,
18858              // as we are anyway detaching the MediaSource
18859              // let's just avoid this exception to propagate
18860              mediaSource.endOfStream();
18861            }
18862          } catch (err) {
18863            this.warn(`onMediaDetaching: ${err.message} while calling endOfStream`);
18864          }
18865        }
18866        // Clean up the SourceBuffers by invoking onBufferReset
18867        if (this.sourceBufferCount) {
18868          this.onBufferReset();
18869        }
18870      }
18871      mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
18872      mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);
18873      mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);
18874      if (this.appendSource) {
18875        mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
18876        mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
18877      }
18878      this.mediaSource = null;
18879      this._objectUrl = null;
18880    }
18881
18882    // Detach properly the MediaSource from the HTMLMediaElement as
18883    // suggested in https://github.com/w3c/media-source/issues/53.
18884    if (media) {
18885      media.removeEventListener('emptied', this._onMediaEmptied);
18886      if (!transferringMedia) {
18887        if (_objectUrl) {
18888          self.URL.revokeObjectURL(_objectUrl);
18889        }
18890
18891        // clean up video tag src only if it's our own url. some external libraries might
18892        // hijack the video tag and change its 'src' without destroying the Hls instance first
18893        if (this.mediaSrc === _objectUrl) {
18894          media.removeAttribute('src');
18895          if (this.appendSource) {
18896            removeSourceChildren(media);
18897          }
18898          media.load();
18899        } else {
18900          this.warn('media|source.src was changed by a third party - skip cleanup');
18901        }
18902      }
18903      this.media = null;
18904    }
18905    this.hls.trigger(Events.MEDIA_DETACHED, data);
18906  }
18907  onBufferReset() {
18908    this.sourceBuffers.forEach(([type]) => {
18909      if (type) {
18910        this.resetBuffer(type);
18911      }
18912    });
18913    this.initTracks();
18914  }
18915  resetBuffer(type) {
18916    var _this$tracks$type;
18917    const sb = (_this$tracks$type = this.tracks[type]) == null ? void 0 : _this$tracks$type.buffer;
18918    this.removeBuffer(type);
18919    if (sb) {
18920      try {
18921        var _this$mediaSource3;
18922        if ((_this$mediaSource3 = this.mediaSource) != null && _this$mediaSource3.sourceBuffers.length) {
18923          this.mediaSource.removeSourceBuffer(sb);
18924        }
18925      } catch (err) {
18926        this.warn(`onBufferReset ${type}`, err);
18927      }
18928    }
18929    delete this.tracks[type];
18930  }
18931  removeBuffer(type) {
18932    this.removeBufferListeners(type);
18933    this.sourceBuffers[sourceBufferNameToIndex(type)] = [null, null];
18934    const track = this.tracks[type];
18935    if (track) {
18936      track.buffer = undefined;
18937    }
18938  }
18939  resetQueue() {
18940    if (this.operationQueue) {
18941      this.operationQueue.destroy();
18942    }
18943    this.operationQueue = new BufferOperationQueue(this.tracks);
18944  }
18945  onBufferCodecs(event, data) {
18946    var _data$audio;
18947    const tracks = this.tracks;
18948    const trackNames = Object.keys(data);
18949    this.log(`BUFFER_CODECS: "${trackNames}" (current SB count ${this.sourceBufferCount})`);
18950    const unmuxedToMuxed = 'audiovideo' in data && (tracks.audio || tracks.video) || tracks.audiovideo && ('audio' in data || 'video' in data);
18951    const muxedToUnmuxed = !unmuxedToMuxed && this.sourceBufferCount && this.media && trackNames.some(sbName => !tracks[sbName]);
18952    if (unmuxedToMuxed || muxedToUnmuxed) {
18953      this.warn(`Unsupported transition between "${Object.keys(tracks)}" and "${trackNames}" SourceBuffers`);
18954      // Do not add incompatible track ('audiovideo' <-> 'video'/'audio').
18955      // Allow following onBufferAppending handle to trigger BUFFER_APPEND_ERROR.
18956      // This will either be resolved by level switch or could be handled with recoverMediaError().
18957      return;
18958    }
18959    trackNames.forEach(trackName => {
18960      var _this$transferData3, _trackCodec;
18961      const parsedTrack = data[trackName];
18962      const {
18963        id,
18964        codec,
18965        levelCodec,
18966        container,
18967        metadata,
18968        supplemental
18969      } = parsedTrack;
18970      let track = tracks[trackName];
18971      const transferredTrack = (_this$transferData3 = this.transferData) == null || (_this$transferData3 = _this$transferData3.tracks) == null ? void 0 : _this$transferData3[trackName];
18972      const sbTrack = transferredTrack != null && transferredTrack.buffer ? transferredTrack : track;
18973      const sbCodec = (sbTrack == null ? void 0 : sbTrack.pendingCodec) || (sbTrack == null ? void 0 : sbTrack.codec);
18974      const trackLevelCodec = sbTrack == null ? void 0 : sbTrack.levelCodec;
18975      if (!track) {
18976        track = tracks[trackName] = {
18977          buffer: undefined,
18978          listeners: [],
18979          codec,
18980          supplemental,
18981          container,
18982          levelCodec,
18983          metadata,
18984          id
18985        };
18986      }
18987      // check if SourceBuffer codec needs to change
18988      const currentCodecFull = pickMostCompleteCodecName(sbCodec, trackLevelCodec);
18989      const currentCodec = currentCodecFull == null ? void 0 : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
18990      let trackCodec = pickMostCompleteCodecName(codec, levelCodec);
18991      const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
18992      if (trackCodec && currentCodecFull && currentCodec !== nextCodec) {
18993        if (trackName.slice(0, 5) === 'audio') {
18994          trackCodec = getCodecCompatibleName(trackCodec, this.appendSource);
18995        }
18996        this.log(`switching codec ${sbCodec} to ${trackCodec}`);
18997        if (trackCodec !== (track.pendingCodec || track.codec)) {
18998          track.pendingCodec = trackCodec;
18999        }
19000        track.container = container;
19001        this.appendChangeType(trackName, container, trackCodec);
19002      }
19003    });
19004    if (this.tracksReady || this.sourceBufferCount) {
19005      data.tracks = this.sourceBufferTracks;
19006    }
19007
19008    // if sourcebuffers already created, do nothing ...
19009    if (this.sourceBufferCount) {
19010      return;
19011    }
19012    if (this.bufferCodecEventsTotal > 1 && !this.tracks.video && !data.video && ((_data$audio = data.audio) == null ? void 0 : _data$audio.id) === 'main') {
19013      // MVP is missing CODECS and only audio was found in main segment (#7524)
19014      this.log(`Main audio-only`);
19015      this.bufferCodecEventsTotal = 1;
19016    }
19017    if (this.mediaSourceOpenOrEnded) {
19018      this.checkPendingTracks();
19019    }
19020  }
19021  get sourceBufferTracks() {
19022    return Object.keys(this.tracks).reduce((baseTracks, type) => {
19023      const track = this.tracks[type];
19024      baseTracks[type] = {
19025        id: track.id,
19026        container: track.container,
19027        codec: track.codec,
19028        levelCodec: track.levelCodec
19029      };
19030      return baseTracks;
19031    }, {});
19032  }
19033  appendChangeType(type, container, codec) {
19034    const mimeType = `${container};codecs=${codec}`;
19035    const operation = {
19036      label: `change-type=${mimeType}`,
19037      execute: () => {
19038        const track = this.tracks[type];
19039        if (track) {
19040          const sb = track.buffer;
19041          if (sb != null && sb.changeType) {
19042            this.log(`changing ${type} sourceBuffer type to ${mimeType}`);
19043            sb.changeType(mimeType);
19044            track.codec = codec;
19045            track.container = container;
19046          }
19047        }
19048        this.shiftAndExecuteNext(type);
19049      },
19050      onStart: () => {},
19051      onComplete: () => {},
19052      onError: error => {
19053        this.warn(`Failed to change ${type} SourceBuffer type`, error);
19054      }
19055    };
19056    this.append(operation, type, this.isPending(this.tracks[type]));
19057  }
19058  blockAudio(partOrFrag) {
19059    var _this$fragmentTracker;
19060    const pStart = partOrFrag.start;
19061    const pTime = pStart + partOrFrag.duration * 0.05;
19062    const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
19063    if (atGap) {
19064      return;
19065    }
19066    const op = {
19067      label: 'block-audio',
19068      execute: () => {
19069        var _this$fragmentTracker2;
19070        const videoTrack = this.tracks.video;
19071        if (this.lastVideoAppendEnd > pTime || videoTrack != null && videoTrack.buffer && BufferHelper.isBuffered(videoTrack.buffer, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
19072          this.blockedAudioAppend = null;
19073          this.shiftAndExecuteNext('audio');
19074        }
19075      },
19076      onStart: () => {},
19077      onComplete: () => {},
19078      onError: error => {
19079        this.warn('Error executing block-audio operation', error);
19080      }
19081    };
19082    this.blockedAudioAppend = {
19083      op,
19084      frag: partOrFrag
19085    };
19086    this.append(op, 'audio', true);
19087  }
19088  unblockAudio() {
19089    const {
19090      blockedAudioAppend,
19091      operationQueue
19092    } = this;
19093    if (blockedAudioAppend && operationQueue) {
19094      this.blockedAudioAppend = null;
19095      operationQueue.unblockAudio(blockedAudioAppend.op);
19096    }
19097  }
19098  onBufferAppending(event, eventData) {
19099    const {
19100      tracks
19101    } = this;
19102    const {
19103      data,
19104      type,
19105      parent,
19106      frag,
19107      part,
19108      chunkMeta,
19109      offset
19110    } = eventData;
19111    const chunkStats = chunkMeta.buffering[type];
19112    const {
19113      sn,
19114      cc
19115    } = frag;
19116    const bufferAppendingStart = self.performance.now();
19117    chunkStats.start = bufferAppendingStart;
19118    const fragBuffering = frag.stats.buffering;
19119    const partBuffering = part ? part.stats.buffering : null;
19120    if (fragBuffering.start === 0) {
19121      fragBuffering.start = bufferAppendingStart;
19122    }
19123    if (partBuffering && partBuffering.start === 0) {
19124      partBuffering.start = bufferAppendingStart;
19125    }
19126
19127    // TODO: Only update timestampOffset when audio/mpeg fragment or part is not contiguous with previously appended
19128    // Adjusting `SourceBuffer.timestampOffset` (desired point in the timeline where the next frames should be appended)
19129    // in Chrome browser when we detect MPEG audio container and time delta between level PTS and `SourceBuffer.timestampOffset`
19130    // is greater than 100ms (this is enough to handle seek for VOD or level change for LIVE videos).
19131    // More info here: https://github.com/video-dev/hls.js/issues/332#issuecomment-257986486
19132    const audioTrack = tracks.audio;
19133    let checkTimestampOffset = false;
19134    if (type === 'audio' && (audioTrack == null ? void 0 : audioTrack.container) === 'audio/mpeg') {
19135      checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
19136      this.lastMpegAudioChunk = chunkMeta;
19137    }
19138
19139    // Block audio append until overlapping video append
19140    const videoTrack = tracks.video;
19141    const videoSb = videoTrack == null ? void 0 : videoTrack.buffer;
19142    if (videoSb && sn !== 'initSegment') {
19143      const partOrFrag = part || frag;
19144      const blockedAudioAppend = this.blockedAudioAppend;
19145      if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend && !(videoTrack.ending || videoTrack.ended)) {
19146        const pStart = partOrFrag.start;
19147        const pTime = pStart + partOrFrag.duration * 0.05;
19148        const vbuffered = videoSb.buffered;
19149        const vappending = this.currentOp('video');
19150        if (!vbuffered.length && !vappending) {
19151          // wait for video before appending audio
19152          this.blockAudio(partOrFrag);
19153        } else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
19154          // audio is ahead of video
19155          this.blockAudio(partOrFrag);
19156        }
19157      } else if (type === 'video') {
19158        const videoAppendEnd = partOrFrag.end;
19159        if (blockedAudioAppend) {
19160          const audioStart = blockedAudioAppend.frag.start;
19161          if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
19162            this.unblockAudio();
19163          }
19164        }
19165        this.lastVideoAppendEnd = videoAppendEnd;
19166      }
19167    }
19168    const fragStart = (part || frag).start;
19169    const operation = {
19170      label: `append-${type}`,
19171      execute: () => {
19172        var _this$tracks$type2;
19173        chunkStats.executeStart = self.performance.now();
19174        const sb = (_this$tracks$type2 = this.tracks[type]) == null ? void 0 : _this$tracks$type2.buffer;
19175        if (sb) {
19176          if (checkTimestampOffset) {
19177            this.updateTimestampOffset(sb, fragStart, 0.1, type, sn, cc);
19178          } else if (offset !== undefined && isFiniteNumber(offset)) {
19179            this.updateTimestampOffset(sb, offset, 0.000001, type, sn, cc);
19180          }
19181        }
19182        this.appendExecutor(data, type);
19183      },
19184      onStart: () => {
19185        // logger.debug(`[buffer-controller]: ${type} SourceBuffer updatestart`);
19186      },
19187      onComplete: () => {
19188        // logger.debug(`[buffer-controller]: ${type} SourceBuffer updateend`);
19189        const end = self.performance.now();
19190        chunkStats.executeEnd = chunkStats.end = end;
19191        if (fragBuffering.first === 0) {
19192          fragBuffering.first = end;
19193        }
19194        if (partBuffering && partBuffering.first === 0) {
19195          partBuffering.first = end;
19196        }
19197        const timeRanges = {};
19198        this.sourceBuffers.forEach(([type, sb]) => {
19199          if (type) {
19200            timeRanges[type] = BufferHelper.getBuffered(sb);
19201          }
19202        });
19203        this.appendErrors[type] = 0;
19204        if (type === 'audio' || type === 'video') {
19205          this.appendErrors.audiovideo = 0;
19206        } else {
19207          this.appendErrors.audio = 0;
19208          this.appendErrors.video = 0;
19209        }
19210        this.hls.trigger(Events.BUFFER_APPENDED, {
19211          type,
19212          frag,
19213          part,
19214          chunkMeta,
19215          parent: frag.type,
19216          timeRanges
19217        });
19218      },
19219      onError: error => {
19220        var _this$media;
19221        // in case any error occured while appending, put back segment in segments table
19222        const event = {
19223          type: ErrorTypes.MEDIA_ERROR,
19224          parent: frag.type,
19225          details: ErrorDetails.BUFFER_APPEND_ERROR,
19226          sourceBufferName: type,
19227          frag,
19228          part,
19229          chunkMeta,
19230          error,
19231          err: error,
19232          fatal: false
19233        };
19234        const mediaError = (_this$media = this.media) == null ? void 0 : _this$media.error;
19235        if (error.code === DOMException.QUOTA_EXCEEDED_ERR || error.name == 'QuotaExceededError' || `quota` in error) {
19236          // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror
19237          // let's stop appending any segments, and report BUFFER_FULL_ERROR error
19238          event.details = ErrorDetails.BUFFER_FULL_ERROR;
19239        } else if (error.code === DOMException.INVALID_STATE_ERR && this.mediaSourceOpenOrEnded && !mediaError) {
19240          // Allow retry for "Failed to execute 'appendBuffer' on 'SourceBuffer': This SourceBuffer is still processing" errors
19241          event.errorAction = createDoNothingErrorAction(true);
19242        } else if (error.name === TRACK_REMOVED_ERROR_NAME && this.sourceBufferCount === 0) {
19243          // Do nothing if sourceBuffers were removed (media is detached and append was not aborted)
19244          event.errorAction = createDoNothingErrorAction(true);
19245        } else {
19246          const appendErrorCount = ++this.appendErrors[type];
19247          /* with UHD content, we could get loop of quota exceeded error until
19248            browser is able to evict some data from sourcebuffer. Retrying can help recover.
19249          */
19250          this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer (${mediaError ? mediaError : 'no media error'})`);
19251          if (appendErrorCount >= this.hls.config.appendErrorMaxRetry || !!mediaError) {
19252            event.fatal = true;
19253          }
19254        }
19255        this.hls.trigger(Events.ERROR, event);
19256      }
19257    };
19258    this.log(`queuing "${type}" append sn: ${sn}${part ? ' p: ' + part.index : ''} of ${frag.type === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level} cc: ${cc}`);
19259    this.append(operation, type, this.isPending(this.tracks[type]));
19260  }
19261  getFlushOp(type, start, end) {
19262    this.log(`queuing "${type}" remove ${start}-${end}`);
19263    return {
19264      label: 'remove',
19265      execute: () => {
19266        this.removeExecutor(type, start, end);
19267      },
19268      onStart: () => {
19269        // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
19270      },
19271      onComplete: () => {
19272        // logger.debug(`[buffer-controller]: Finished flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
19273        this.hls.trigger(Events.BUFFER_FLUSHED, {
19274          type
19275        });
19276      },
19277      onError: error => {
19278        this.warn(`Failed to remove ${start}-${end} from "${type}" SourceBuffer`, error);
19279      }
19280    };
19281  }
19282  onBufferFlushing(event, data) {
19283    const {
19284      type,
19285      startOffset,
19286      endOffset
19287    } = data;
19288    if (type) {
19289      this.append(this.getFlushOp(type, startOffset, endOffset), type);
19290    } else {
19291      this.sourceBuffers.forEach(([type]) => {
19292        if (type) {
19293          this.append(this.getFlushOp(type, startOffset, endOffset), type);
19294        }
19295      });
19296    }
19297  }
19298  onFragParsed(event, data) {
19299    const {
19300      frag,
19301      part
19302    } = data;
19303    const buffersAppendedTo = [];
19304    const elementaryStreams = part ? part.elementaryStreams : frag.elementaryStreams;
19305    if (elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]) {
19306      buffersAppendedTo.push('audiovideo');
19307    } else {
19308      if (elementaryStreams[ElementaryStreamTypes.AUDIO]) {
19309        buffersAppendedTo.push('audio');
19310      }
19311      if (elementaryStreams[ElementaryStreamTypes.VIDEO]) {
19312        buffersAppendedTo.push('video');
19313      }
19314    }
19315    const onUnblocked = () => {
19316      const now = self.performance.now();
19317      frag.stats.buffering.end = now;
19318      if (part) {
19319        part.stats.buffering.end = now;
19320      }
19321      const stats = part ? part.stats : frag.stats;
19322      this.hls.trigger(Events.FRAG_BUFFERED, {
19323        frag,
19324        part,
19325        stats,
19326        id: frag.type
19327      });
19328    };
19329    if (buffersAppendedTo.length === 0) {
19330      this.warn(`Fragments must have at least one ElementaryStreamType set. type: ${frag.type} level: ${frag.level} sn: ${frag.sn}`);
19331    }
19332    this.blockBuffers(onUnblocked, buffersAppendedTo).catch(error => {
19333      this.warn(`Fragment buffered callback ${error}`);
19334      this.stepOperationQueue(this.sourceBufferTypes);
19335    });
19336  }
19337  onFragChanged(event, data) {
19338    this.trimBuffers();
19339  }
19340  get bufferedToEnd() {
19341    return this.sourceBufferCount > 0 && !this.sourceBuffers.some(([type]) => {
19342      if (type) {
19343        const track = this.tracks[type];
19344        if (track) {
19345          return !track.ended || track.ending;
19346        }
19347      }
19348      return false;
19349    });
19350  }
19351
19352  // on BUFFER_EOS mark matching sourcebuffer(s) as "ending" and "ended" and queue endOfStream after remaining operations(s)
19353  // an undefined data.type will mark all buffers as EOS.
19354  onBufferEos(event, data) {
19355    var _this$overrides;
19356    this.sourceBuffers.forEach(([type]) => {
19357      if (type) {
19358        const track = this.tracks[type];
19359        if (!data.type || data.type === type) {
19360          track.ending = true;
19361          if (!track.ended) {
19362            track.ended = true;
19363            this.log(`${type} buffer reached EOS`);
19364          }
19365        }
19366      }
19367    });
19368    const allowEndOfStream = ((_this$overrides = this.overrides) == null ? void 0 : _this$overrides.endOfStream) !== false;
19369    const allTracksEnding = this.sourceBufferCount > 0 && !this.sourceBuffers.some(([type]) => {
19370      var _this$tracks$type3;
19371      return type && !((_this$tracks$type3 = this.tracks[type]) != null && _this$tracks$type3.ended);
19372    });
19373    if (allTracksEnding) {
19374      if (allowEndOfStream) {
19375        this.log(`Queueing EOS`);
19376        this.blockUntilOpen(() => {
19377          this.tracksEnded();
19378          const {
19379            mediaSource
19380          } = this;
19381          if (!mediaSource || mediaSource.readyState !== 'open') {
19382            if (mediaSource) {
19383              this.log(`Could not call mediaSource.endOfStream(). mediaSource.readyState: ${mediaSource.readyState}`);
19384            }
19385            return;
19386          }
19387          this.log(`Calling mediaSource.endOfStream()`);
19388          // Allow this to throw and be caught by the enqueueing function
19389          mediaSource.endOfStream();
19390          this.hls.trigger(Events.BUFFERED_TO_END, undefined);
19391        });
19392      } else {
19393        this.tracksEnded();
19394        this.hls.trigger(Events.BUFFERED_TO_END, undefined);
19395      }
19396    } else if (data.type === 'video') {
19397      // Make sure pending audio appends are unblocked when video reaches end
19398      this.unblockAudio();
19399    }
19400  }
19401  tracksEnded() {
19402    this.sourceBuffers.forEach(([type]) => {
19403      if (type !== null) {
19404        const track = this.tracks[type];
19405        if (track) {
19406          track.ending = false;
19407        }
19408      }
19409    });
19410  }
19411  onLevelUpdated(event, {
19412    details
19413  }) {
19414    if (!details.fragments.length) {
19415      return;
19416    }
19417    this.details = details;
19418    this.updateDuration();
19419  }
19420  updateDuration() {
19421    this.blockUntilOpen(() => {
19422      const durationAndRange = this.getDurationAndRange();
19423      if (!durationAndRange) {
19424        return;
19425      }
19426      this.updateMediaSource(durationAndRange);
19427    });
19428  }
19429  onError(event, data) {
19430    if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && data.frag) {
19431      var _data$errorAction;
19432      const nextAutoLevel = (_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.nextAutoLevel;
19433      if (isFiniteNumber(nextAutoLevel) && nextAutoLevel !== data.frag.level) {
19434        this.resetAppendErrors();
19435      }
19436    }
19437  }
19438  resetAppendErrors() {
19439    this.appendErrors = {
19440      audio: 0,
19441      video: 0,
19442      audiovideo: 0
19443    };
19444  }
19445  trimBuffers() {
19446    const {
19447      hls,
19448      details,
19449      media
19450    } = this;
19451    if (!media || details === null) {
19452      return;
19453    }
19454    if (!this.sourceBufferCount) {
19455      return;
19456    }
19457    const config = hls.config;
19458    const currentTime = media.currentTime;
19459    const targetDuration = details.levelTargetDuration;
19460
19461    // Support for deprecated liveBackBufferLength
19462    const backBufferLength = details.live && config.liveBackBufferLength !== null ? config.liveBackBufferLength : config.backBufferLength;
19463    if (isFiniteNumber(backBufferLength) && backBufferLength >= 0) {
19464      const maxBackBufferLength = Math.max(backBufferLength, targetDuration);
19465      const targetBackBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength;
19466      this.flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition);
19467    }
19468    const frontBufferFlushThreshold = config.frontBufferFlushThreshold;
19469    if (isFiniteNumber(frontBufferFlushThreshold) && frontBufferFlushThreshold > 0) {
19470      const frontBufferLength = Math.max(config.maxBufferLength, frontBufferFlushThreshold);
19471      const maxFrontBufferLength = Math.max(frontBufferLength, targetDuration);
19472      const targetFrontBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration + maxFrontBufferLength;
19473      this.flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition);
19474    }
19475  }
19476  flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition) {
19477    this.sourceBuffers.forEach(([type, sb]) => {
19478      if (sb) {
19479        const buffered = BufferHelper.getBuffered(sb);
19480        // when target buffer start exceeds actual buffer start
19481        if (buffered.length > 0 && targetBackBufferPosition > buffered.start(0)) {
19482          var _this$details;
19483          this.hls.trigger(Events.BACK_BUFFER_REACHED, {
19484            bufferEnd: targetBackBufferPosition
19485          });
19486
19487          // Support for deprecated event:
19488          const track = this.tracks[type];
19489          if ((_this$details = this.details) != null && _this$details.live) {
19490            this.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, {
19491              bufferEnd: targetBackBufferPosition
19492            });
19493          } else if (track != null && track.ended) {
19494            this.log(`Cannot flush ${type} back buffer while SourceBuffer is in ended state`);
19495            return;
19496          }
19497          this.hls.trigger(Events.BUFFER_FLUSHING, {
19498            startOffset: 0,
19499            endOffset: targetBackBufferPosition,
19500            type
19501          });
19502        }
19503      }
19504    });
19505  }
19506  flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition) {
19507    this.sourceBuffers.forEach(([type, sb]) => {
19508      if (sb) {
19509        const buffered = BufferHelper.getBuffered(sb);
19510        const numBufferedRanges = buffered.length;
19511        // The buffer is either empty or contiguous
19512        if (numBufferedRanges < 2) {
19513          return;
19514        }
19515        const bufferStart = buffered.start(numBufferedRanges - 1);
19516        const bufferEnd = buffered.end(numBufferedRanges - 1);
19517        // No flush if we can tolerate the current buffer length or the current buffer range we would flush is contiguous with current position
19518        if (targetFrontBufferPosition > bufferStart || currentTime >= bufferStart && currentTime <= bufferEnd) {
19519          return;
19520        }
19521        this.hls.trigger(Events.BUFFER_FLUSHING, {
19522          startOffset: bufferStart,
19523          endOffset: Infinity,
19524          type
19525        });
19526      }
19527    });
19528  }
19529
19530  /**
19531   * Update Media Source duration to current level duration or override to Infinity if configuration parameter
19532   * 'liveDurationInfinity` is set to `true`
19533   * More details: https://github.com/video-dev/hls.js/issues/355
19534   */
19535  getDurationAndRange() {
19536    var _this$overrides2;
19537    const {
19538      details,
19539      mediaSource
19540    } = this;
19541    if (!details || !this.media || (mediaSource == null ? void 0 : mediaSource.readyState) !== 'open') {
19542      return null;
19543    }
19544    const playlistEnd = details.edge;
19545    if (details.live && this.hls.config.liveDurationInfinity) {
19546      const len = details.fragments.length;
19547      if (len && !!mediaSource.setLiveSeekableRange) {
19548        const start = Math.max(0, details.fragmentStart);
19549        const end = Math.max(start, playlistEnd);
19550        return {
19551          duration: Infinity,
19552          start,
19553          end
19554        };
19555      }
19556      return {
19557        duration: Infinity
19558      };
19559    }
19560    const overrideDuration = (_this$overrides2 = this.overrides) == null ? void 0 : _this$overrides2.duration;
19561    if (overrideDuration) {
19562      if (!isFiniteNumber(overrideDuration)) {
19563        return null;
19564      }
19565      return {
19566        duration: overrideDuration
19567      };
19568    }
19569    const mediaDuration = this.media.duration;
19570    const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : 0;
19571    if (playlistEnd > msDuration && playlistEnd > mediaDuration || !isFiniteNumber(mediaDuration)) {
19572      return {
19573        duration: playlistEnd
19574      };
19575    }
19576    return null;
19577  }
19578  updateMediaSource({
19579    duration,
19580    start,
19581    end
19582  }) {
19583    const mediaSource = this.mediaSource;
19584    if (!this.media || !mediaSource || mediaSource.readyState !== 'open') {
19585      return;
19586    }
19587    if (mediaSource.duration !== duration) {
19588      if (isFiniteNumber(duration)) {
19589        this.log(`Updating MediaSource duration to ${duration.toFixed(3)}`);
19590      }
19591      mediaSource.duration = duration;
19592    }
19593    if (start !== undefined && end !== undefined) {
19594      this.log(`MediaSource duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
19595      mediaSource.setLiveSeekableRange(start, end);
19596    }
19597  }
19598  get tracksReady() {
19599    const pendingTrackCount = this.pendingTrackCount;
19600    return pendingTrackCount > 0 && (pendingTrackCount >= this.bufferCodecEventsTotal || this.isPending(this.tracks.audiovideo));
19601  }
19602  checkPendingTracks() {
19603    const {
19604      bufferCodecEventsTotal,
19605      pendingTrackCount,
19606      tracks
19607    } = this;
19608    this.log(`checkPendingTracks (pending: ${pendingTrackCount} codec events expected: ${bufferCodecEventsTotal}) ${stringify(tracks)}`);
19609    // Check if we've received all of the expected bufferCodec events. When none remain, create all the sourceBuffers at once.
19610    // This is important because the MSE spec allows implementations to throw QuotaExceededErrors if creating new sourceBuffers after
19611    // data has been appended to existing ones.
19612    // 2 tracks is the max (one for audio, one for video). If we've reach this max go ahead and create the buffers.
19613    if (this.tracksReady) {
19614      var _this$transferData4;
19615      const transferredTracks = (_this$transferData4 = this.transferData) == null ? void 0 : _this$transferData4.tracks;
19616      if (transferredTracks && Object.keys(transferredTracks).length) {
19617        this.attachTransferred();
19618      } else {
19619        // ok, let's create them now !
19620        this.createSourceBuffers();
19621      }
19622    }
19623  }
19624  bufferCreated() {
19625    if (this.sourceBufferCount) {
19626      const tracks = {};
19627      this.sourceBuffers.forEach(([type, buffer]) => {
19628        if (type) {
19629          const track = this.tracks[type];
19630          tracks[type] = {
19631            buffer,
19632            container: track.container,
19633            codec: track.codec,
19634            supplemental: track.supplemental,
19635            levelCodec: track.levelCodec,
19636            id: track.id,
19637            metadata: track.metadata
19638          };
19639        }
19640      });
19641      this.hls.trigger(Events.BUFFER_CREATED, {
19642        tracks
19643      });
19644      this.log(`SourceBuffers created. Running queue: ${this.operationQueue}`);
19645      this.sourceBuffers.forEach(([type]) => {
19646        this.executeNext(type);
19647      });
19648    } else {
19649      const error = new Error('could not create source buffer for media codec(s)');
19650      this.hls.trigger(Events.ERROR, {
19651        type: ErrorTypes.MEDIA_ERROR,
19652        details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR,
19653        fatal: true,
19654        error,
19655        reason: error.message
19656      });
19657    }
19658  }
19659  createSourceBuffers() {
19660    const {
19661      tracks,
19662      sourceBuffers,
19663      mediaSource
19664    } = this;
19665    if (!mediaSource) {
19666      throw new Error('createSourceBuffers called when mediaSource was null');
19667    }
19668    for (const trackName in tracks) {
19669      const type = trackName;
19670      const track = tracks[type];
19671      if (this.isPending(track)) {
19672        const codec = this.getTrackCodec(track, type);
19673        const mimeType = `${track.container};codecs=${codec}`;
19674        track.codec = codec;
19675        this.log(`creating sourceBuffer(${mimeType})${this.currentOp(type) ? ' Queued' : ''} ${stringify(track)}`);
19676        try {
19677          const sb = mediaSource.addSourceBuffer(mimeType);
19678          const sbIndex = sourceBufferNameToIndex(type);
19679          const sbTuple = [type, sb];
19680          sourceBuffers[sbIndex] = sbTuple;
19681          track.buffer = sb;
19682        } catch (error) {
19683          var _this$operationQueue;
19684          this.error(`error while trying to add sourceBuffer: ${error.message}`);
19685          // remove init segment from queue and delete track info
19686          this.shiftAndExecuteNext(type);
19687          (_this$operationQueue = this.operationQueue) == null || _this$operationQueue.removeBlockers();
19688          delete this.tracks[type];
19689          this.hls.trigger(Events.ERROR, {
19690            type: ErrorTypes.MEDIA_ERROR,
19691            details: ErrorDetails.BUFFER_ADD_CODEC_ERROR,
19692            fatal: false,
19693            error,
19694            sourceBufferName: type,
19695            mimeType: mimeType,
19696            parent: track.id
19697          });
19698          return;
19699        }
19700        this.trackSourceBuffer(type, track);
19701      }
19702    }
19703    this.bufferCreated();
19704  }
19705  getTrackCodec(track, trackName) {
19706    // Use supplemental video codec when supported when adding SourceBuffer (#5558)
19707    const supplementalCodec = track.supplemental;
19708    let trackCodec = track.codec;
19709    if (supplementalCodec && (trackName === 'video' || trackName === 'audiovideo') && areCodecsMediaSourceSupported(supplementalCodec, 'video')) {
19710      trackCodec = replaceVideoCodec(trackCodec, supplementalCodec);
19711    }
19712    const codec = pickMostCompleteCodecName(trackCodec, track.levelCodec);
19713    if (codec) {
19714      if (trackName.slice(0, 5) === 'audio') {
19715        return getCodecCompatibleName(codec, this.appendSource);
19716      }
19717      return codec;
19718    }
19719    return '';
19720  }
19721  trackSourceBuffer(type, track) {
19722    const buffer = track.buffer;
19723    if (!buffer) {
19724      return;
19725    }
19726    const codec = this.getTrackCodec(track, type);
19727    this.tracks[type] = {
19728      buffer,
19729      codec,
19730      container: track.container,
19731      levelCodec: track.levelCodec,
19732      supplemental: track.supplemental,
19733      metadata: track.metadata,
19734      id: track.id,
19735      listeners: []
19736    };
19737    this.removeBufferListeners(type);
19738    this.addBufferListener(type, 'updatestart', this.onSBUpdateStart);
19739    this.addBufferListener(type, 'updateend', this.onSBUpdateEnd);
19740    this.addBufferListener(type, 'error', this.onSBUpdateError);
19741    // ManagedSourceBuffer bufferedchange event
19742    if (this.appendSource) {
19743      this.addBufferListener(type, 'bufferedchange', (type, event) => {
19744        // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
19745        const removedRanges = event.removedRanges;
19746        if (removedRanges != null && removedRanges.length) {
19747          this.hls.trigger(Events.BUFFER_FLUSHED, {
19748            type: type
19749          });
19750        }
19751      });
19752    }
19753  }
19754  get mediaSrc() {
19755    var _this$media2, _this$media2$querySel;
19756    const media = ((_this$media2 = this.media) == null || (_this$media2$querySel = _this$media2.querySelector) == null ? void 0 : _this$media2$querySel.call(_this$media2, 'source')) || this.media;
19757    return media == null ? void 0 : media.src;
19758  }
19759  onSBUpdateStart(type) {
19760    const operation = this.currentOp(type);
19761    if (!operation) {
19762      return;
19763    }
19764    operation.onStart();
19765  }
19766  onSBUpdateEnd(type) {
19767    var _this$mediaSource4;
19768    if (((_this$mediaSource4 = this.mediaSource) == null ? void 0 : _this$mediaSource4.readyState) === 'closed') {
19769      this.resetBuffer(type);
19770      return;
19771    }
19772    const operation = this.currentOp(type);
19773    if (!operation) {
19774      return;
19775    }
19776    operation.onComplete();
19777    this.shiftAndExecuteNext(type);
19778  }
19779  onSBUpdateError(type, event) {
19780    var _this$mediaSource5;
19781    const error = new Error(`${type} SourceBuffer error. MediaSource readyState: ${(_this$mediaSource5 = this.mediaSource) == null ? void 0 : _this$mediaSource5.readyState}`);
19782    this.error(`${error}`, event);
19783    // according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error
19784    // SourceBuffer errors are not necessarily fatal; if so, the HTMLMediaElement will fire an error event
19785    this.hls.trigger(Events.ERROR, {
19786      type: ErrorTypes.MEDIA_ERROR,
19787      details: ErrorDetails.BUFFER_APPENDING_ERROR,
19788      sourceBufferName: type,
19789      error,
19790      fatal: false
19791    });
19792    // updateend is always fired after error, so we'll allow that to shift the current operation off of the queue
19793    const operation = this.currentOp(type);
19794    if (operation) {
19795      operation.onError(error);
19796    }
19797  }
19798  updateTimestampOffset(sb, timestampOffset, tolerance, type, sn, cc) {
19799    const delta = timestampOffset - sb.timestampOffset;
19800    if (Math.abs(delta) >= tolerance) {
19801      this.log(`Updating ${type} SourceBuffer timestampOffset to ${timestampOffset} (sn: ${sn} cc: ${cc})`);
19802      sb.timestampOffset = timestampOffset;
19803    }
19804  }
19805
19806  // This method must result in an updateend event; if remove is not called, onSBUpdateEnd must be called manually
19807  removeExecutor(type, startOffset, endOffset) {
19808    const {
19809      media,
19810      mediaSource
19811    } = this;
19812    const track = this.tracks[type];
19813    const sb = track == null ? void 0 : track.buffer;
19814    if (!media || !mediaSource || !sb) {
19815      this.warn(`Attempting to remove from the ${type} SourceBuffer, but it does not exist`);
19816      this.shiftAndExecuteNext(type);
19817      return;
19818    }
19819    const mediaDuration = isFiniteNumber(media.duration) ? media.duration : Infinity;
19820    const msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : Infinity;
19821    const removeStart = Math.max(0, startOffset);
19822    const removeEnd = Math.min(endOffset, mediaDuration, msDuration);
19823    if (removeEnd > removeStart && (!track.ending || track.ended)) {
19824      track.ended = false;
19825      this.log(`Removing [${removeStart},${removeEnd}] from the ${type} SourceBuffer`);
19826      sb.remove(removeStart, removeEnd);
19827    } else {
19828      // Cycle the queue
19829      this.shiftAndExecuteNext(type);
19830    }
19831  }
19832
19833  // This method must result in an updateend event; if append is not called, onSBUpdateEnd must be called manually
19834  appendExecutor(data, type) {
19835    const track = this.tracks[type];
19836    const sb = track == null ? void 0 : track.buffer;
19837    if (!sb) {
19838      throw new HlsJsTrackRemovedError(`Attempting to append to the ${type} SourceBuffer, but it does not exist`);
19839    }
19840    track.ending = false;
19841    track.ended = false;
19842    sb.appendBuffer(data);
19843  }
19844  blockUntilOpen(callback) {
19845    if (this.isUpdating() || this.isQueued()) {
19846      this.blockBuffers(callback).catch(error => {
19847        this.warn(`SourceBuffer blocked callback ${error}`);
19848        this.stepOperationQueue(this.sourceBufferTypes);
19849      });
19850    } else {
19851      try {
19852        callback();
19853      } catch (error) {
19854        this.warn(`Callback run without blocking ${this.operationQueue} ${error}`);
19855      }
19856    }
19857  }
19858  isUpdating() {
19859    return this.sourceBuffers.some(([type, sb]) => type && sb.updating);
19860  }
19861  isQueued() {
19862    return this.sourceBuffers.some(([type]) => type && !!this.currentOp(type));
19863  }
19864  isPending(track) {
19865    return !!track && !track.buffer;
19866  }
19867
19868  // Enqueues an operation to each SourceBuffer queue which, upon execution, resolves a promise. When all promises
19869  // resolve, the onUnblocked function is executed. Functions calling this method do not need to unblock the queue
19870  // upon completion, since we already do it here
19871  blockBuffers(onUnblocked, bufferNames = this.sourceBufferTypes) {
19872    if (!bufferNames.length) {
19873      this.log('Blocking operation requested, but no SourceBuffers exist');
19874      return Promise.resolve().then(onUnblocked);
19875    }
19876    const {
19877      operationQueue
19878    } = this;
19879
19880    // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
19881    const blockingOperations = bufferNames.map(type => this.appendBlocker(type));
19882    const audioBlocked = bufferNames.length > 1 && !!this.blockedAudioAppend;
19883    if (audioBlocked) {
19884      this.unblockAudio();
19885    }
19886    return Promise.all(blockingOperations).then(result => {
19887      if (operationQueue !== this.operationQueue) {
19888        return;
19889      }
19890      // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
19891      onUnblocked();
19892      this.stepOperationQueue(this.sourceBufferTypes);
19893    });
19894  }
19895  stepOperationQueue(bufferNames) {
19896    bufferNames.forEach(type => {
19897      var _this$tracks$type4;
19898      const sb = (_this$tracks$type4 = this.tracks[type]) == null ? void 0 : _this$tracks$type4.buffer;
19899      // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
19900      // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
19901      // While this is a workaround, it's probably useful to have around
19902      if (!sb || sb.updating) {
19903        return;
19904      }
19905      this.shiftAndExecuteNext(type);
19906    });
19907  }
19908  append(operation, type, pending) {
19909    if (this.operationQueue) {
19910      this.operationQueue.append(operation, type, pending);
19911    }
19912  }
19913  appendBlocker(type) {
19914    if (this.operationQueue) {
19915      return this.operationQueue.appendBlocker(type);
19916    }
19917  }
19918  currentOp(type) {
19919    if (this.operationQueue) {
19920      return this.operationQueue.current(type);
19921    }
19922    return null;
19923  }
19924  executeNext(type) {
19925    if (type && this.operationQueue) {
19926      this.operationQueue.executeNext(type);
19927    }
19928  }
19929  shiftAndExecuteNext(type) {
19930    if (this.operationQueue) {
19931      this.operationQueue.shiftAndExecuteNext(type);
19932    }
19933  }
19934  get pendingTrackCount() {
19935    return Object.keys(this.tracks).reduce((acc, type) => acc + (this.isPending(this.tracks[type]) ? 1 : 0), 0);
19936  }
19937  get sourceBufferCount() {
19938    return this.sourceBuffers.reduce((acc, [type]) => acc + (type ? 1 : 0), 0);
19939  }
19940  get sourceBufferTypes() {
19941    return this.sourceBuffers.map(([type]) => type).filter(type => !!type);
19942  }
19943  addBufferListener(type, event, fn) {
19944    const track = this.tracks[type];
19945    if (!track) {
19946      return;
19947    }
19948    const buffer = track.buffer;
19949    if (!buffer) {
19950      return;
19951    }
19952    const listener = fn.bind(this, type);
19953    track.listeners.push({
19954      event,
19955      listener
19956    });
19957    buffer.addEventListener(event, listener);
19958  }
19959  removeBufferListeners(type) {
19960    const track = this.tracks[type];
19961    if (!track) {
19962      return;
19963    }
19964    const buffer = track.buffer;
19965    if (!buffer) {
19966      return;
19967    }
19968    track.listeners.forEach(l => {
19969      buffer.removeEventListener(l.event, l.listener);
19970    });
19971    track.listeners.length = 0;
19972  }
19973}
19974function removeSourceChildren(node) {
19975  const sourceChildren = node.querySelectorAll('source');
19976  [].slice.call(sourceChildren).forEach(source => {
19977    node.removeChild(source);
19978  });
19979}
19980function addSource(media, url) {
19981  const source = self.document.createElement('source');
19982  source.type = 'video/mp4';
19983  source.src = url;
19984  media.appendChild(source);
19985}
19986function sourceBufferNameToIndex(type) {
19987  return type === 'audio' ? 1 : 0;
19988}
19989
19990class CapLevelController {
19991  constructor(hls) {
19992    this.hls = void 0;
19993    this.autoLevelCapping = void 0;
19994    this.firstLevel = void 0;
19995    this.media = void 0;
19996    this.restrictedLevels = void 0;
19997    this.timer = void 0;
19998    this.clientRect = void 0;
19999    this.streamController = void 0;
20000    this.hls = hls;
20001    this.autoLevelCapping = Number.POSITIVE_INFINITY;
20002    this.firstLevel = -1;
20003    this.media = null;
20004    this.restrictedLevels = [];
20005    this.timer = undefined;
20006    this.clientRect = null;
20007    this.registerListeners();
20008  }
20009  setStreamController(streamController) {
20010    this.streamController = streamController;
20011  }
20012  destroy() {
20013    if (this.hls) {
20014      this.unregisterListener();
20015    }
20016    if (this.timer) {
20017      this.stopCapping();
20018    }
20019    this.media = null;
20020    this.clientRect = null;
20021    // @ts-ignore
20022    this.hls = this.streamController = null;
20023  }
20024  registerListeners() {
20025    const {
20026      hls
20027    } = this;
20028    hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
20029    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
20030    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
20031    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
20032    hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
20033    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
20034  }
20035  unregisterListener() {
20036    const {
20037      hls
20038    } = this;
20039    hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this);
20040    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
20041    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
20042    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
20043    hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
20044    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
20045  }
20046  onFpsDropLevelCapping(event, data) {
20047    // Don't add a restricted level more than once
20048    const level = this.hls.levels[data.droppedLevel];
20049    if (this.isLevelAllowed(level)) {
20050      this.restrictedLevels.push({
20051        bitrate: level.bitrate,
20052        height: level.height,
20053        width: level.width
20054      });
20055    }
20056  }
20057  onMediaAttaching(event, data) {
20058    this.media = data.media instanceof HTMLVideoElement ? data.media : null;
20059    this.clientRect = null;
20060    if (this.timer && this.hls.levels.length) {
20061      this.detectPlayerSize();
20062    }
20063  }
20064  onManifestParsed(event, data) {
20065    const hls = this.hls;
20066    this.restrictedLevels = [];
20067    this.firstLevel = data.firstLevel;
20068    if (hls.config.capLevelToPlayerSize && data.video) {
20069      // Start capping immediately if the manifest has signaled video codecs
20070      this.startCapping();
20071    }
20072  }
20073  onLevelsUpdated(event, data) {
20074    if (this.timer && isFiniteNumber(this.autoLevelCapping)) {
20075      this.detectPlayerSize();
20076    }
20077  }
20078
20079  // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted
20080  // to the first level
20081  onBufferCodecs(event, data) {
20082    const hls = this.hls;
20083    if (hls.config.capLevelToPlayerSize && data.video) {
20084      // If the manifest did not signal a video codec capping has been deferred until we're certain video is present
20085      this.startCapping();
20086    }
20087  }
20088  onMediaDetaching() {
20089    this.stopCapping();
20090    this.media = null;
20091  }
20092  detectPlayerSize() {
20093    if (this.media) {
20094      if (this.mediaHeight <= 0 || this.mediaWidth <= 0) {
20095        this.clientRect = null;
20096        return;
20097      }
20098      const levels = this.hls.levels;
20099      if (levels.length) {
20100        const hls = this.hls;
20101        const maxLevel = this.getMaxLevel(levels.length - 1);
20102        if (maxLevel !== this.autoLevelCapping) {
20103          hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
20104        }
20105        hls.autoLevelCapping = maxLevel;
20106        if (hls.autoLevelEnabled && hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
20107          // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
20108          // usually happen when the user go to the fullscreen mode.
20109          this.streamController.nextLevelSwitch();
20110        }
20111        this.autoLevelCapping = hls.autoLevelCapping;
20112      }
20113    }
20114  }
20115
20116  /*
20117   * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled)
20118   */
20119  getMaxLevel(capLevelIndex) {
20120    const levels = this.hls.levels;
20121    if (!levels.length) {
20122      return -1;
20123    }
20124    const validLevels = levels.filter((level, index) => this.isLevelAllowed(level) && index <= capLevelIndex);
20125    this.clientRect = null;
20126    return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight);
20127  }
20128  startCapping() {
20129    if (this.timer) {
20130      // Don't reset capping if started twice; this can happen if the manifest signals a video codec
20131      return;
20132    }
20133    this.autoLevelCapping = Number.POSITIVE_INFINITY;
20134    self.clearInterval(this.timer);
20135    this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000);
20136    this.detectPlayerSize();
20137  }
20138  stopCapping() {
20139    this.restrictedLevels = [];
20140    this.firstLevel = -1;
20141    this.autoLevelCapping = Number.POSITIVE_INFINITY;
20142    if (this.timer) {
20143      self.clearInterval(this.timer);
20144      this.timer = undefined;
20145    }
20146  }
20147  getDimensions() {
20148    if (this.clientRect) {
20149      return this.clientRect;
20150    }
20151    const media = this.media;
20152    const boundsRect = {
20153      width: 0,
20154      height: 0
20155    };
20156    if (media) {
20157      const clientRect = media.getBoundingClientRect();
20158      boundsRect.width = clientRect.width;
20159      boundsRect.height = clientRect.height;
20160      if (!boundsRect.width && !boundsRect.height) {
20161        // When the media element has no width or height (equivalent to not being in the DOM),
20162        // then use its width and height attributes (media.width, media.height)
20163        boundsRect.width = clientRect.right - clientRect.left || media.width || 0;
20164        boundsRect.height = clientRect.bottom - clientRect.top || media.height || 0;
20165      }
20166    }
20167    this.clientRect = boundsRect;
20168    return boundsRect;
20169  }
20170  get mediaWidth() {
20171    return this.getDimensions().width * this.contentScaleFactor;
20172  }
20173  get mediaHeight() {
20174    return this.getDimensions().height * this.contentScaleFactor;
20175  }
20176  get contentScaleFactor() {
20177    let pixelRatio = 1;
20178    if (!this.hls.config.ignoreDevicePixelRatio) {
20179      try {
20180        pixelRatio = self.devicePixelRatio;
20181      } catch (e) {
20182        /* no-op */
20183      }
20184    }
20185    return Math.min(pixelRatio, this.hls.config.maxDevicePixelRatio);
20186  }
20187  isLevelAllowed(level) {
20188    const restrictedLevels = this.restrictedLevels;
20189    return !restrictedLevels.some(restrictedLevel => {
20190      return level.bitrate === restrictedLevel.bitrate && level.width === restrictedLevel.width && level.height === restrictedLevel.height;
20191    });
20192  }
20193  static getMaxLevelByMediaSize(levels, width, height) {
20194    if (!(levels != null && levels.length)) {
20195      return -1;
20196    }
20197
20198    // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next
20199    // to determine whether we've chosen the greatest bandwidth for the media's dimensions
20200    const atGreatestBandwidth = (curLevel, nextLevel) => {
20201      if (!nextLevel) {
20202        return true;
20203      }
20204      return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height;
20205    };
20206
20207    // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to
20208    // the max level
20209    let maxLevelIndex = levels.length - 1;
20210    // Prevent changes in aspect-ratio from causing capping to toggle back and forth
20211    const squareSize = Math.max(width, height);
20212    for (let i = 0; i < levels.length; i += 1) {
20213      const level = levels[i];
20214      if ((level.width >= squareSize || level.height >= squareSize) && atGreatestBandwidth(level, levels[i + 1])) {
20215        maxLevelIndex = i;
20216        break;
20217      }
20218    }
20219    return maxLevelIndex;
20220  }
20221}
20222
20223/**
20224 * Common Media Object Type
20225 *
20226 * @internal
20227 */
20228const CmObjectType = {
20229  /**
20230   * text file, such as a manifest or playlist
20231   */
20232  MANIFEST: 'm',
20233  /**
20234   * audio only
20235   */
20236  AUDIO: 'a',
20237  /**
20238   * video only
20239   */
20240  VIDEO: 'v',
20241  /**
20242   * muxed audio and video
20243   */
20244  MUXED: 'av',
20245  /**
20246   * init segment
20247   */
20248  INIT: 'i',
20249  /**
20250   * caption or subtitle
20251   */
20252  CAPTION: 'c',
20253  /**
20254   * ISOBMFF timed text track
20255   */
20256  TIMED_TEXT: 'tt',
20257  /**
20258   * cryptographic key, license or certificate.
20259   */
20260  KEY: 'k',
20261  /**
20262   * other
20263   */
20264  OTHER: 'o'
20265};
20266
20267/**
20268 * Common Media Client Data Object Type
20269 *
20270 * @group CMCD
20271 *
20272 * @beta
20273 *
20274 * @enum
20275 */
20276const CmcdObjectType = CmObjectType;
20277
20278/**
20279 * Common Media Streaming Format
20280 *
20281 * @internal
20282 */
20283const CmStreamingFormat = {
20284  /**
20285   * HTTP Live Streaming (HLS)
20286   */
20287  HLS: 'h'};
20288
20289/**
20290 * Common Media Client Data Streaming Format
20291 *
20292 * @group CMCD
20293 *
20294 * @enum
20295 *
20296 * @beta
20297 */
20298const CmcdStreamingFormat = CmStreamingFormat;
20299
20300/**
20301 * Structured Field Item
20302 *
20303 * @group Structured Field
20304 *
20305 * @beta
20306 */
20307class SfItem {
20308  constructor(value, params) {
20309    if (Array.isArray(value)) {
20310      value = value.map(v => v instanceof SfItem ? v : new SfItem(v));
20311    }
20312    this.value = value;
20313    this.params = params;
20314  }
20315}
20316
20317const DICT = 'Dict';
20318
20319function format(value) {
20320  if (Array.isArray(value)) {
20321    return JSON.stringify(value);
20322  }
20323  if (value instanceof Map) {
20324    return 'Map{}';
20325  }
20326  if (value instanceof Set) {
20327    return 'Set{}';
20328  }
20329  if (typeof value === 'object') {
20330    return JSON.stringify(value);
20331  }
20332  return String(value);
20333}
20334function throwError(action, src, type, cause) {
20335  return new Error(`failed to ${action} "${format(src)}" as ${type}`, {
20336    cause
20337  });
20338}
20339
20340function serializeError(src, type, cause) {
20341  return throwError('serialize', src, type, cause);
20342}
20343
20344/**
20345 * A class to represent structured field tokens when `Symbol` is not available.
20346 *
20347 * @group Structured Field
20348 *
20349 * @beta
20350 */
20351class SfToken {
20352  constructor(description) {
20353    this.description = description;
20354  }
20355}
20356
20357const BARE_ITEM = 'Bare Item';
20358
20359const BOOLEAN = 'Boolean';
20360
20361// 4.1.9.  Serializing a Boolean
20362//
20363// Given a Boolean as input_boolean, return an ASCII string suitable for
20364// use in a HTTP field value.
20365//
20366// 1.  If input_boolean is not a boolean, fail serialization.
20367//
20368// 2.  Let output be an empty string.
20369//
20370// 3.  Append "?" to output.
20371//
20372// 4.  If input_boolean is true, append "1" to output.
20373//
20374// 5.  If input_boolean is false, append "0" to output.
20375//
20376// 6.  Return output.
20377function serializeBoolean(value) {
20378  if (typeof value !== 'boolean') {
20379    throw serializeError(value, BOOLEAN);
20380  }
20381  return value ? '?1' : '?0';
20382}
20383
20384/**
20385 * Encodes binary data to base64
20386 *
20387 * @param binary - The binary data to encode
20388 * @returns The base64 encoded string
20389 *
20390 * @group Utils
20391 *
20392 * @beta
20393 */
20394function encodeBase64(binary) {
20395  return btoa(String.fromCharCode(...binary));
20396}
20397
20398const BYTES = 'Byte Sequence';
20399
20400// 4.1.8.  Serializing a Byte Sequence
20401//
20402// Given a Byte Sequence as input_bytes, return an ASCII string suitable
20403// for use in a HTTP field value.
20404//
20405// 1.  If input_bytes is not a sequence of bytes, fail serialization.
20406//
20407// 2.  Let output be an empty string.
20408//
20409// 3.  Append ":" to output.
20410//
20411// 4.  Append the result of base64-encoding input_bytes as per
20412//     [RFC4648], Section 4, taking account of the requirements below.
20413//
20414// 5.  Append ":" to output.
20415//
20416// 6.  Return output.
20417//
20418// The encoded data is required to be padded with "=", as per [RFC4648],
20419// Section 3.2.
20420//
20421// Likewise, encoded data SHOULD have pad bits set to zero, as per
20422// [RFC4648], Section 3.5, unless it is not possible to do so due to
20423// implementation constraints.
20424function serializeByteSequence(value) {
20425  if (ArrayBuffer.isView(value) === false) {
20426    throw serializeError(value, BYTES);
20427  }
20428  return `:${encodeBase64(value)}:`;
20429}
20430
20431const INTEGER = 'Integer';
20432
20433function isInvalidInt(value) {
20434  return value < -999999999999999 || 999999999999999 < value;
20435}
20436
20437// 4.1.4.  Serializing an Integer
20438//
20439// Given an Integer as input_integer, return an ASCII string suitable
20440// for use in a HTTP field value.
20441//
20442// 1.  If input_integer is not an integer in the range of
20443//     -999,999,999,999,999 to 999,999,999,999,999 inclusive, fail
20444//     serialization.
20445//
20446// 2.  Let output be an empty string.
20447//
20448// 3.  If input_integer is less than (but not equal to) 0, append "-" to
20449//     output.
20450//
20451// 4.  Append input_integer's numeric value represented in base 10 using
20452//     only decimal digits to output.
20453//
20454// 5.  Return output.
20455function serializeInteger(value) {
20456  if (isInvalidInt(value)) {
20457    throw serializeError(value, INTEGER);
20458  }
20459  return value.toString();
20460}
20461
20462// 4.1.10.  Serializing a Date
20463//
20464// Given a Date as input_integer, return an ASCII string suitable for
20465// use in an HTTP field value.
20466// 1.  Let output be "@".
20467// 2.  Append to output the result of running Serializing an Integer
20468//     with input_date (Section 4.1.4).
20469// 3.  Return output.
20470function serializeDate(value) {
20471  return `@${serializeInteger(value.getTime() / 1000)}`;
20472}
20473
20474/**
20475 * This implements the rounding procedure described in step 2 of the "Serializing a Decimal" specification.
20476 * This rounding style is known as "even rounding", "banker's rounding", or "commercial rounding".
20477 *
20478 * @param value - The value to round
20479 * @param precision - The number of decimal places to round to
20480 * @returns The rounded value
20481 *
20482 * @group Utils
20483 *
20484 * @beta
20485 */
20486function roundToEven(value, precision) {
20487  if (value < 0) {
20488    return -roundToEven(-value, precision);
20489  }
20490  const decimalShift = Math.pow(10, precision);
20491  const isEquidistant = Math.abs(value * decimalShift % 1 - 0.5) < Number.EPSILON;
20492  if (isEquidistant) {
20493    // If the tail of the decimal place is 'equidistant' we round to the nearest even value
20494    const flooredValue = Math.floor(value * decimalShift);
20495    return (flooredValue % 2 === 0 ? flooredValue : flooredValue + 1) / decimalShift;
20496  } else {
20497    // Otherwise, proceed as normal
20498    return Math.round(value * decimalShift) / decimalShift;
20499  }
20500}
20501
20502const DECIMAL = 'Decimal';
20503
20504// 4.1.5.  Serializing a Decimal
20505//
20506// Given a decimal number as input_decimal, return an ASCII string
20507// suitable for use in a HTTP field value.
20508//
20509// 1.   If input_decimal is not a decimal number, fail serialization.
20510//
20511// 2.   If input_decimal has more than three significant digits to the
20512//      right of the decimal point, round it to three decimal places,
20513//      rounding the final digit to the nearest value, or to the even
20514//      value if it is equidistant.
20515//
20516// 3.   If input_decimal has more than 12 significant digits to the left
20517//      of the decimal point after rounding, fail serialization.
20518//
20519// 4.   Let output be an empty string.
20520//
20521// 5.   If input_decimal is less than (but not equal to) 0, append "-"
20522//      to output.
20523//
20524// 6.   Append input_decimal's integer component represented in base 10
20525//      (using only decimal digits) to output; if it is zero, append
20526//      "0".
20527//
20528// 7.   Append "." to output.
20529//
20530// 8.   If input_decimal's fractional component is zero, append "0" to
20531//      output.
20532//
20533// 9.   Otherwise, append the significant digits of input_decimal's
20534//      fractional component represented in base 10 (using only decimal
20535//      digits) to output.
20536//
20537// 10.  Return output.
20538function serializeDecimal(value) {
20539  const roundedValue = roundToEven(value, 3); // round to 3 decimal places
20540  if (Math.floor(Math.abs(roundedValue)).toString().length > 12) {
20541    throw serializeError(value, DECIMAL);
20542  }
20543  const stringValue = roundedValue.toString();
20544  return stringValue.includes('.') ? stringValue : `${stringValue}.0`;
20545}
20546
20547const STRING = 'String';
20548
20549const STRING_REGEX = /[\x00-\x1f\x7f]+/;
20550
20551// 4.1.6.  Serializing a String
20552//
20553// Given a String as input_string, return an ASCII string suitable for
20554// use in a HTTP field value.
20555//
20556// 1.  Convert input_string into a sequence of ASCII characters; if
20557//     conversion fails, fail serialization.
20558//
20559// 2.  If input_string contains characters in the range %x00-1f or %x7f
20560//     (i.e., not in VCHAR or SP), fail serialization.
20561//
20562// 3.  Let output be the string DQUOTE.
20563//
20564// 4.  For each character char in input_string:
20565//
20566//     1.  If char is "\" or DQUOTE:
20567//
20568//         1.  Append "\" to output.
20569//
20570//     2.  Append char to output.
20571//
20572// 5.  Append DQUOTE to output.
20573//
20574// 6.  Return output.
20575function serializeString(value) {
20576  if (STRING_REGEX.test(value)) {
20577    throw serializeError(value, STRING);
20578  }
20579  return `"${value.replace(/\\/g, `\\\\`).replace(/"/g, `\\"`)}"`;
20580}
20581
20582/**
20583 * Converts a symbol to a string.
20584 *
20585 * @param symbol - The symbol to convert.
20586 *
20587 * @returns The string representation of the symbol.
20588 *
20589 * @internal
20590 */
20591function symbolToStr(symbol) {
20592  return symbol.description || symbol.toString().slice(7, -1);
20593}
20594
20595const TOKEN = 'Token';
20596
20597function serializeToken(token) {
20598  const value = symbolToStr(token);
20599  if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
20600    throw serializeError(value, TOKEN);
20601  }
20602  return value;
20603}
20604
20605// 4.1.3.1.  Serializing a Bare Item
20606//
20607// Given an Item as input_item, return an ASCII string suitable for use
20608// in a HTTP field value.
20609//
20610// 1.  If input_item is an Integer, return the result of running
20611//     Serializing an Integer (Section 4.1.4) with input_item.
20612//
20613// 2.  If input_item is a Decimal, return the result of running
20614//     Serializing a Decimal (Section 4.1.5) with input_item.
20615//
20616// 3.  If input_item is a String, return the result of running
20617//     Serializing a String (Section 4.1.6) with input_item.
20618//
20619// 4.  If input_item is a Token, return the result of running
20620//     Serializing a Token (Section 4.1.7) with input_item.
20621//
20622// 5.  If input_item is a Boolean, return the result of running
20623//     Serializing a Boolean (Section 4.1.9) with input_item.
20624//
20625// 6.  If input_item is a Byte Sequence, return the result of running
20626//     Serializing a Byte Sequence (Section 4.1.8) with input_item.
20627//
20628// 7.  If input_item is a Date, return the result of running Serializing
20629//     a Date (Section 4.1.10) with input_item.
20630//
20631// 8.  Otherwise, fail serialization.
20632function serializeBareItem(value) {
20633  switch (typeof value) {
20634    case 'number':
20635      if (!isFiniteNumber(value)) {
20636        throw serializeError(value, BARE_ITEM);
20637      }
20638      if (Number.isInteger(value)) {
20639        return serializeInteger(value);
20640      }
20641      return serializeDecimal(value);
20642    case 'string':
20643      return serializeString(value);
20644    case 'symbol':
20645      return serializeToken(value);
20646    case 'boolean':
20647      return serializeBoolean(value);
20648    case 'object':
20649      if (value instanceof Date) {
20650        return serializeDate(value);
20651      }
20652      if (value instanceof Uint8Array) {
20653        return serializeByteSequence(value);
20654      }
20655      if (value instanceof SfToken) {
20656        return serializeToken(value);
20657      }
20658    default:
20659      // fail
20660      throw serializeError(value, BARE_ITEM);
20661  }
20662}
20663
20664const KEY = 'Key';
20665
20666// 4.1.1.3.  Serializing a Key
20667//
20668// Given a key as input_key, return an ASCII string suitable for use in
20669// a HTTP field value.
20670//
20671// 1.  Convert input_key into a sequence of ASCII characters; if
20672//     conversion fails, fail serialization.
20673//
20674// 2.  If input_key contains characters not in lcalpha, DIGIT, "_", "-",
20675//     ".", or "*" fail serialization.
20676//
20677// 3.  If the first character of input_key is not lcalpha or "*", fail
20678//     serialization.
20679//
20680// 4.  Let output be an empty string.
20681//
20682// 5.  Append input_key to output.
20683//
20684// 6.  Return output.
20685function serializeKey(value) {
20686  if (/^[a-z*][a-z0-9\-_.*]*$/.test(value) === false) {
20687    throw serializeError(value, KEY);
20688  }
20689  return value;
20690}
20691
20692// 4.1.1.2.  Serializing Parameters
20693//
20694// Given an ordered Dictionary as input_parameters (each member having a
20695// param_name and a param_value), return an ASCII string suitable for
20696// use in a HTTP field value.
20697//
20698// 1.  Let output be an empty string.
20699//
20700// 2.  For each param_name with a value of param_value in
20701//     input_parameters:
20702//
20703//     1.  Append ";" to output.
20704//
20705//     2.  Append the result of running Serializing a Key
20706//         (Section 4.1.1.3) with param_name to output.
20707//
20708//     3.  If param_value is not Boolean true:
20709//
20710//         1.  Append "=" to output.
20711//
20712//         2.  Append the result of running Serializing a bare Item
20713//             (Section 4.1.3.1) with param_value to output.
20714//
20715// 3.  Return output.
20716function serializeParams(params) {
20717  if (params == null) {
20718    return '';
20719  }
20720  return Object.entries(params).map(([key, value]) => {
20721    if (value === true) {
20722      return `;${serializeKey(key)}`; // omit true
20723    }
20724    return `;${serializeKey(key)}=${serializeBareItem(value)}`;
20725  }).join('');
20726}
20727
20728// 4.1.3.  Serializing an Item
20729//
20730// Given an Item as bare_item and Parameters as item_parameters, return
20731// an ASCII string suitable for use in a HTTP field value.
20732//
20733// 1.  Let output be an empty string.
20734//
20735// 2.  Append the result of running Serializing a Bare Item
20736//     Section 4.1.3.1 with bare_item to output.
20737//
20738// 3.  Append the result of running Serializing Parameters
20739//     Section 4.1.1.2 with item_parameters to output.
20740//
20741// 4.  Return output.
20742function serializeItem(value) {
20743  if (value instanceof SfItem) {
20744    return `${serializeBareItem(value.value)}${serializeParams(value.params)}`;
20745  } else {
20746    return serializeBareItem(value);
20747  }
20748}
20749
20750// 4.1.1.1.  Serializing an Inner List
20751//
20752// Given an array of (member_value, parameters) tuples as inner_list,
20753// and parameters as list_parameters, return an ASCII string suitable
20754// for use in a HTTP field value.
20755//
20756// 1.  Let output be the string "(".
20757//
20758// 2.  For each (member_value, parameters) of inner_list:
20759//
20760//     1.  Append the result of running Serializing an Item
20761//         (Section 4.1.3) with (member_value, parameters) to output.
20762//
20763//     2.  If more values remain in inner_list, append a single SP to
20764//         output.
20765//
20766// 3.  Append ")" to output.
20767//
20768// 4.  Append the result of running Serializing Parameters
20769//     (Section 4.1.1.2) with list_parameters to output.
20770//
20771// 5.  Return output.
20772function serializeInnerList(value) {
20773  return `(${value.value.map(serializeItem).join(' ')})${serializeParams(value.params)}`;
20774}
20775
20776// 4.1.2.  Serializing a Dictionary
20777//
20778// Given an ordered Dictionary as input_dictionary (each member having a
20779// member_name and a tuple value of (member_value, parameters)), return
20780// an ASCII string suitable for use in a HTTP field value.
20781//
20782// 1.  Let output be an empty string.
20783//
20784// 2.  For each member_name with a value of (member_value, parameters)
20785//     in input_dictionary:
20786//
20787//     1.  Append the result of running Serializing a Key
20788//         (Section 4.1.1.3) with member's member_name to output.
20789//
20790//     2.  If member_value is Boolean true:
20791//
20792//         1.  Append the result of running Serializing Parameters
20793//             (Section 4.1.1.2) with parameters to output.
20794//
20795//     3.  Otherwise:
20796//
20797//         1.  Append "=" to output.
20798//
20799//         2.  If member_value is an array, append the result of running
20800//             Serializing an Inner List (Section 4.1.1.1) with
20801//             (member_value, parameters) to output.
20802//
20803//         3.  Otherwise, append the result of running Serializing an
20804//             Item (Section 4.1.3) with (member_value, parameters) to
20805//             output.
20806//
20807//     4.  If more members remain in input_dictionary:
20808//
20809//         1.  Append "," to output.
20810//
20811//         2.  Append a single SP to output.
20812//
20813// 3.  Return output.
20814function serializeDict(dict, options = {
20815  whitespace: true
20816}) {
20817  if (typeof dict !== 'object' || dict == null) {
20818    throw serializeError(dict, DICT);
20819  }
20820  const entries = dict instanceof Map ? dict.entries() : Object.entries(dict);
20821  const optionalWhiteSpace = (options === null || options === void 0 ? void 0 : options.whitespace) ? ' ' : '';
20822  return Array.from(entries).map(([key, item]) => {
20823    if (item instanceof SfItem === false) {
20824      item = new SfItem(item);
20825    }
20826    let output = serializeKey(key);
20827    if (item.value === true) {
20828      output += serializeParams(item.params);
20829    } else {
20830      output += '=';
20831      if (Array.isArray(item.value)) {
20832        output += serializeInnerList(item);
20833      } else {
20834        output += serializeItem(item);
20835      }
20836    }
20837    return output;
20838  }).join(`,${optionalWhiteSpace}`);
20839}
20840
20841/**
20842 * Encode an object into a structured field dictionary
20843 *
20844 * @param value - The structured field dictionary to encode
20845 * @param options - Encoding options
20846 *
20847 * @returns The structured field string
20848 *
20849 * @group Structured Field
20850 *
20851 * @beta
20852 */
20853function encodeSfDict(value, options) {
20854  return serializeDict(value, options);
20855}
20856
20857/**
20858 * CMCD object header name.
20859 *
20860 * @group CMCD
20861 *
20862 * @beta
20863 */
20864const CMCD_OBJECT = 'CMCD-Object';
20865
20866/**
20867 * CMCD request header name.
20868 *
20869 * @group CMCD
20870 *
20871 * @beta
20872 */
20873const CMCD_REQUEST = 'CMCD-Request';
20874
20875/**
20876 * CMCD session header name.
20877 *
20878 * @group CMCD
20879 *
20880 * @beta
20881 */
20882const CMCD_SESSION = 'CMCD-Session';
20883
20884/**
20885 * CMCD status header name.
20886 *
20887 * @group CMCD
20888 *
20889 * @beta
20890 */
20891const CMCD_STATUS = 'CMCD-Status';
20892
20893/**
20894 * The map of CMCD keys to their appropriate header shard.
20895 *
20896 * @group CMCD
20897 *
20898 * @internal
20899 */
20900const CMCD_HEADER_MAP = {
20901  // Object
20902  br: CMCD_OBJECT,
20903  ab: CMCD_OBJECT,
20904  d: CMCD_OBJECT,
20905  ot: CMCD_OBJECT,
20906  tb: CMCD_OBJECT,
20907  tpb: CMCD_OBJECT,
20908  lb: CMCD_OBJECT,
20909  tab: CMCD_OBJECT,
20910  lab: CMCD_OBJECT,
20911  url: CMCD_OBJECT,
20912  // Request
20913  pb: CMCD_REQUEST,
20914  bl: CMCD_REQUEST,
20915  tbl: CMCD_REQUEST,
20916  dl: CMCD_REQUEST,
20917  ltc: CMCD_REQUEST,
20918  mtp: CMCD_REQUEST,
20919  nor: CMCD_REQUEST,
20920  nrr: CMCD_REQUEST,
20921  rc: CMCD_REQUEST,
20922  sn: CMCD_REQUEST,
20923  sta: CMCD_REQUEST,
20924  su: CMCD_REQUEST,
20925  ttfb: CMCD_REQUEST,
20926  ttfbb: CMCD_REQUEST,
20927  ttlb: CMCD_REQUEST,
20928  cmsdd: CMCD_REQUEST,
20929  cmsds: CMCD_REQUEST,
20930  smrt: CMCD_REQUEST,
20931  df: CMCD_REQUEST,
20932  cs: CMCD_REQUEST,
20933  // TODO: Which header to put the `ts` field is not defined yet.
20934  ts: CMCD_REQUEST,
20935  // Session
20936  cid: CMCD_SESSION,
20937  pr: CMCD_SESSION,
20938  sf: CMCD_SESSION,
20939  sid: CMCD_SESSION,
20940  st: CMCD_SESSION,
20941  v: CMCD_SESSION,
20942  msd: CMCD_SESSION,
20943  // Status
20944  bs: CMCD_STATUS,
20945  bsd: CMCD_STATUS,
20946  cdn: CMCD_STATUS,
20947  rtp: CMCD_STATUS,
20948  bg: CMCD_STATUS,
20949  pt: CMCD_STATUS,
20950  ec: CMCD_STATUS,
20951  e: CMCD_STATUS
20952};
20953
20954/**
20955 * CMCD header fields.
20956 *
20957 * @group CMCD
20958 *
20959 * @enum
20960 *
20961 * @beta
20962 */
20963const CmcdHeaderField = {
20964  /**
20965   * keys whose values vary with each request.
20966   */
20967  REQUEST: CMCD_REQUEST};
20968
20969function createHeaderMap(headerMap) {
20970  return Object.keys(headerMap).reduce((acc, field) => {
20971    var _a;
20972    (_a = headerMap[field]) === null || _a === void 0 ? void 0 : _a.forEach(key => acc[key] = field);
20973    return acc;
20974  }, {});
20975}
20976/**
20977 * Group a CMCD data object into header shards
20978 *
20979 * @param cmcd - The CMCD data object to convert.
20980 * @param customHeaderMap - A map of CMCD header fields to custom CMCD keys.
20981 *
20982 * @returns The CMCD header shards.
20983 *
20984 * @group CMCD
20985 *
20986 * @beta
20987 */
20988function groupCmcdHeaders(cmcd, customHeaderMap) {
20989  const result = {};
20990  if (!cmcd) {
20991    return result;
20992  }
20993  const keys = Object.keys(cmcd);
20994  const custom = customHeaderMap ? createHeaderMap(customHeaderMap) : {};
20995  return keys.reduce((acc, key) => {
20996    var _a;
20997    const field = CMCD_HEADER_MAP[key] || custom[key] || CmcdHeaderField.REQUEST;
20998    const data = (_a = acc[field]) !== null && _a !== void 0 ? _a : acc[field] = {};
20999    data[key] = cmcd[key];
21000    return acc;
21001  }, result);
21002}
21003
21004/**
21005 * Checks if the given key is a token field.
21006 *
21007 * @param key - The key to check.
21008 *
21009 * @returns `true` if the key is a token field.
21010 *
21011 * @internal
21012 */
21013function isTokenField(key) {
21014  return ['ot', 'sf', 'st', 'e', 'sta'].includes(key);
21015}
21016
21017/**
21018 * Checks if the given value is valid
21019 *
21020 * @param value - The value to check.
21021 *
21022 * @returns `true` if the key is a value is valid.
21023 *
21024 * @internal
21025 */
21026function isValid(value) {
21027  if (typeof value === 'number') {
21028    return isFiniteNumber(value);
21029  }
21030  return value != null && value !== '' && value !== false;
21031}
21032
21033/**
21034 * CMCD event mode variable name.
21035 *
21036 * @group CMCD
21037 *
21038 * @beta
21039 */
21040const CMCD_EVENT_MODE = 'event';
21041
21042/**
21043 * Constructs a relative path from a URL.
21044 *
21045 * @param url - The destination URL
21046 * @param base - The base URL
21047 * @returns The relative path
21048 *
21049 * @group Utils
21050 *
21051 * @beta
21052 */
21053function urlToRelativePath(url, base) {
21054  const to = new URL(url);
21055  const from = new URL(base);
21056  if (to.origin !== from.origin) {
21057    return url;
21058  }
21059  const toPath = to.pathname.split('/').slice(1);
21060  const fromPath = from.pathname.split('/').slice(1, -1);
21061  // remove common parents
21062  while (toPath[0] === fromPath[0]) {
21063    toPath.shift();
21064    fromPath.shift();
21065  }
21066  // add back paths
21067  while (fromPath.length) {
21068    fromPath.shift();
21069    toPath.unshift('..');
21070  }
21071  const relativePath = toPath.join('/');
21072  // preserve query parameters and hash of the destination url
21073  return relativePath + to.search + to.hash;
21074}
21075
21076const toRounded = value => Math.round(value);
21077const toUrlSafe = (value, options) => {
21078  if (Array.isArray(value)) {
21079    return value.map(item => toUrlSafe(item, options));
21080  }
21081  if (value instanceof SfItem && typeof value.value === 'string') {
21082    return new SfItem(toUrlSafe(value.value, options), value.params);
21083  } else {
21084    if (options.baseUrl) {
21085      value = urlToRelativePath(value, options.baseUrl);
21086    }
21087    return options.version === 1 ? encodeURIComponent(value) : value;
21088  }
21089};
21090const toHundred = value => toRounded(value / 100) * 100;
21091const nor = (value, options) => {
21092  let norValue = value;
21093  if (options.version >= 2) {
21094    if (value instanceof SfItem && typeof value.value === 'string') {
21095      norValue = new SfItem([value]);
21096    } else if (typeof value === 'string') {
21097      norValue = [value];
21098    }
21099  }
21100  return toUrlSafe(norValue, options);
21101};
21102/**
21103 * The default formatters for CMCD values.
21104 *
21105 * @group CMCD
21106 *
21107 * @beta
21108 */
21109const CMCD_FORMATTER_MAP = {
21110  /**
21111   * Bitrate (kbps) rounded integer
21112   */
21113  br: toRounded,
21114  /**
21115   * Duration (milliseconds) rounded integer
21116   */
21117  d: toRounded,
21118  /**
21119   * Buffer Length (milliseconds) rounded nearest 100ms
21120   */
21121  bl: toHundred,
21122  /**
21123   * Deadline (milliseconds) rounded nearest 100ms
21124   */
21125  dl: toHundred,
21126  /**
21127   * Measured Throughput (kbps) rounded nearest 100kbps
21128   */
21129  mtp: toHundred,
21130  /**
21131   * Next Object Request URL encoded
21132   */
21133  nor,
21134  /**
21135   * Requested maximum throughput (kbps) rounded nearest 100kbps
21136   */
21137  rtp: toHundred,
21138  /**
21139   * Top Bitrate (kbps) rounded integer
21140   */
21141  tb: toRounded
21142};
21143
21144/**
21145 * CMCD request mode variable name.
21146 *
21147 * @group CMCD
21148 *
21149 * @beta
21150 */
21151const CMCD_REQUEST_MODE = 'request';
21152
21153/**
21154 * CMCD response mode variable name.
21155 *
21156 * @group CMCD
21157 *
21158 * @beta
21159 */
21160const CMCD_RESPONSE_MODE = 'response';
21161
21162/**
21163 * Defines the common keys for CMCD (Common Media Client Data) version 2.
21164 *
21165 * @group CMCD
21166 *
21167 * @beta
21168 */
21169const CMCD_COMMON_KEYS = ['ab', 'bg', 'bl', 'br', 'bs', 'bsd', 'cdn', 'cid', 'cs', 'df', 'ec', 'lab', 'lb', 'ltc', 'msd', 'mtp', 'pb', 'pr', 'pt', 'sf', 'sid', 'sn', 'st', 'sta', 'tab', 'tb', 'tbl', 'tpb', 'ts', 'v'];
21170
21171/**
21172 * Defines the event-specific keys for CMCD (Common Media Client Data) version 2.
21173 *
21174 * @group CMCD
21175 *
21176 * @beta
21177 */
21178const CMCD_EVENT_KEYS = ['e'];
21179
21180const CUSTOM_KEY_REGEX = /^[a-zA-Z0-9-.]+-[a-zA-Z0-9-.]+$/;
21181/**
21182 * Check if a key is a custom key.
21183 *
21184 * @param key - The key to check.
21185 *
21186 * @returns `true` if the key is a custom key, `false` otherwise.
21187 *
21188 * @group CMCD
21189 *
21190 * @beta
21191 */
21192function isCmcdCustomKey(key) {
21193  return CUSTOM_KEY_REGEX.test(key);
21194}
21195
21196/**
21197 * Check if a key is a valid CMCD event key.
21198 *
21199 * @param key - The key to check.
21200 *
21201 * @returns `true` if the key is a valid CMCD event key, `false` otherwise.
21202 *
21203 * @group CMCD
21204 *
21205 * @beta
21206 *
21207 * @example
21208 * {@includeCode ../../test/cmcd/isCmcdEventKey.test.ts#example}
21209 */
21210function isCmcdEventKey(key) {
21211  return CMCD_COMMON_KEYS.includes(key) || CMCD_EVENT_KEYS.includes(key) || isCmcdCustomKey(key);
21212}
21213
21214/**
21215 * Defines the request-specific keys for CMCD (Common Media Client Data) version 2.
21216 *
21217 * @group CMCD
21218 *
21219 * @beta
21220 */
21221const CMCD_REQUEST_KEYS = ['d', 'dl', 'nor', 'ot', 'rtp', 'su'];
21222
21223/**
21224 * Check if a key is a valid CMCD request key.
21225 *
21226 * @param key - The key to check.
21227 *
21228 * @returns `true` if the key is a valid CMCD request key, `false` otherwise.
21229 *
21230 * @group CMCD
21231 *
21232 * @beta
21233 *
21234 * @example
21235 * {@includeCode ../../test/cmcd/isCmcdRequestKey.test.ts#example}
21236 */
21237function isCmcdRequestKey(key) {
21238  return CMCD_COMMON_KEYS.includes(key) || CMCD_REQUEST_KEYS.includes(key) || isCmcdCustomKey(key);
21239}
21240
21241/**
21242 * CMCD v2 - Response-only and timing keys.
21243 *
21244 * @group CMCD
21245 *
21246 * @beta
21247 */
21248const CMCD_RESPONSE_KEYS = ['cmsdd', 'cmsds', 'rc', 'smrt', 'ttfb', 'ttfbb', 'ttlb', 'url'];
21249
21250/**
21251 * Check if a key is a valid CMCD response key.
21252 *
21253 * @param key - The key to check.
21254 *
21255 * @returns `true` if the key is a valid CMCD request key, `false` otherwise.
21256 *
21257 * @group CMCD
21258 *
21259 * @beta
21260 *
21261 * @example
21262 * {@includeCode ../../test/cmcd/isCmcdResponseKey.test.ts#example}
21263 */
21264function isCmcdResponseKey(key) {
21265  return CMCD_COMMON_KEYS.includes(key) || CMCD_REQUEST_KEYS.includes(key) || CMCD_RESPONSE_KEYS.includes(key) || isCmcdCustomKey(key);
21266}
21267
21268/**
21269 * Defines the keys for CMCD (Common Media Client Data) version 1.
21270 *
21271 * @group CMCD
21272 *
21273 * @beta
21274 */
21275const CMCD_V1_KEYS = ['bl', 'br', 'bs', 'cid', 'd', 'dl', 'mtp', 'nor', 'nrr', 'ot', 'pr', 'rtp', 'sf', 'sid', 'st', 'su', 'tb', 'v'];
21276
21277/**
21278 * Filter function for CMCD v1 keys.
21279 *
21280 * @param key - The CMCD key to filter.
21281 *
21282 * @returns `true` if the key should be included, `false` otherwise.
21283 *
21284 * @group CMCD
21285 *
21286 * @beta
21287 *
21288 * @example
21289 * {@includeCode ../../test/cmcd/isCmcdV1Key.test.ts#example}
21290 */
21291function isCmcdV1Key(key) {
21292  return CMCD_V1_KEYS.includes(key) || isCmcdCustomKey(key);
21293}
21294
21295const filterMap = {
21296  [CMCD_RESPONSE_MODE]: isCmcdResponseKey,
21297  [CMCD_EVENT_MODE]: isCmcdEventKey,
21298  [CMCD_REQUEST_MODE]: isCmcdRequestKey
21299};
21300/**
21301 * Convert a generic object to CMCD data.
21302 *
21303 * @param obj - The CMCD object to process.
21304 * @param options - Options for encoding.
21305 *
21306 * @group CMCD
21307 *
21308 * @beta
21309 */
21310function prepareCmcdData(obj, options = {}) {
21311  const results = {};
21312  if (obj == null || typeof obj !== 'object') {
21313    return results;
21314  }
21315  const version = options.version || obj['v'] || 1;
21316  const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
21317  const keyFilter = version === 1 ? isCmcdV1Key : filterMap[reportingMode];
21318  // Filter keys based on the version, reporting mode and options
21319  let keys = Object.keys(obj).filter(keyFilter);
21320  const filter = options.filter;
21321  if (typeof filter === 'function') {
21322    keys = keys.filter(filter);
21323  }
21324  // Ensure all required keys are present before sorting
21325  const needsTimestamp = reportingMode === CMCD_RESPONSE_MODE || reportingMode === CMCD_EVENT_MODE;
21326  if (needsTimestamp && !keys.includes('ts')) {
21327    keys.push('ts');
21328  }
21329  if (version > 1 && !keys.includes('v')) {
21330    keys.push('v');
21331  }
21332  const formatters = _extends({}, CMCD_FORMATTER_MAP, options.formatters);
21333  const formatterOptions = {
21334    version,
21335    reportingMode,
21336    baseUrl: options.baseUrl
21337  };
21338  keys.sort().forEach(key => {
21339    let value = obj[key];
21340    const formatter = formatters[key];
21341    if (typeof formatter === 'function') {
21342      value = formatter(value, formatterOptions);
21343    }
21344    // Version should only be reported if not equal to 1.
21345    if (key === 'v') {
21346      if (version === 1) {
21347        return;
21348      } else {
21349        value = version;
21350      }
21351    }
21352    // Playback rate should only be sent if not equal to 1.
21353    if (key == 'pr' && value === 1) {
21354      return;
21355    }
21356    // Ensure a timestamp is set for response and event modes
21357    if (needsTimestamp && key === 'ts' && !isFiniteNumber(value)) {
21358      value = Date.now();
21359    }
21360    // ignore invalid values
21361    if (!isValid(value)) {
21362      return;
21363    }
21364    if (isTokenField(key) && typeof value === 'string') {
21365      value = new SfToken(value);
21366    }
21367    results[key] = value;
21368  });
21369  return results;
21370}
21371
21372/**
21373 * Convert a CMCD data object to request headers
21374 *
21375 * @param cmcd - The CMCD data object to convert.
21376 * @param options - Options for encoding the CMCD object.
21377 *
21378 * @returns The CMCD header shards.
21379 *
21380 * @group CMCD
21381 *
21382 * @beta
21383 *
21384 * @example
21385 * {@includeCode ../../test/cmcd/toCmcdHeaders.test.ts#example}
21386 */
21387function toCmcdHeaders(cmcd, options = {}) {
21388  const result = {};
21389  if (!cmcd) {
21390    return result;
21391  }
21392  const data = prepareCmcdData(cmcd, options);
21393  const shards = groupCmcdHeaders(data, options === null || options === void 0 ? void 0 : options.customHeaderMap);
21394  return Object.entries(shards).reduce((acc, [field, value]) => {
21395    const shard = encodeSfDict(value, {
21396      whitespace: false
21397    });
21398    if (shard) {
21399      acc[field] = shard;
21400    }
21401    return acc;
21402  }, result);
21403}
21404
21405/**
21406 * Append CMCD query args to a header object.
21407 *
21408 * @param headers - The headers to append to.
21409 * @param cmcd - The CMCD object to append.
21410 * @param options - Encode options.
21411 *
21412 * @returns The headers with the CMCD header shards appended.
21413 *
21414 * @group CMCD
21415 *
21416 * @beta
21417 *
21418 * @example
21419 * {@includeCode ../../test/cmcd/appendCmcdHeaders.test.ts#example}
21420 */
21421function appendCmcdHeaders(headers, cmcd, options) {
21422  return _extends(headers, toCmcdHeaders(cmcd, options));
21423}
21424
21425/**
21426 * CMCD parameter name.
21427 *
21428 * @group CMCD
21429 *
21430 * @beta
21431 */
21432const CMCD_PARAM = 'CMCD';
21433
21434/**
21435 * Encode a CMCD object to a string.
21436 *
21437 * @param cmcd - The CMCD object to encode.
21438 * @param options - Options for encoding.
21439 *
21440 * @returns The encoded CMCD string.
21441 *
21442 * @group CMCD
21443 *
21444 * @beta
21445 *
21446 * @example
21447 * {@includeCode ../../test/cmcd/encodeCmcd.test.ts#example}
21448 */
21449function encodeCmcd(cmcd, options = {}) {
21450  if (!cmcd) {
21451    return '';
21452  }
21453  return encodeSfDict(prepareCmcdData(cmcd, options), {
21454    whitespace: false
21455  });
21456}
21457
21458/**
21459 * Convert a CMCD data object to a URL encoded string.
21460 *
21461 * @param cmcd - The CMCD object to convert.
21462 * @param options - Options for encoding the CMCD object.
21463 *
21464 * @returns The URL encoded CMCD data.
21465 *
21466 * @group CMCD
21467 *
21468 * @beta
21469 */
21470function toCmcdUrl(cmcd, options = {}) {
21471  if (!cmcd) {
21472    return '';
21473  }
21474  const params = encodeCmcd(cmcd, options);
21475  return encodeURIComponent(params);
21476}
21477
21478/**
21479 * Convert a CMCD data object to a query arg.
21480 *
21481 * @param cmcd - The CMCD object to convert.
21482 * @param options - Options for encoding the CMCD object.
21483 *
21484 * @returns The CMCD query arg.
21485 *
21486 * @group CMCD
21487 *
21488 * @beta
21489 *
21490 * @example
21491 * {@includeCode ../../test/cmcd/toCmcdQuery.test.ts#example}
21492 */
21493function toCmcdQuery(cmcd, options = {}) {
21494  if (!cmcd) {
21495    return '';
21496  }
21497  const value = toCmcdUrl(cmcd, options);
21498  return `${CMCD_PARAM}=${value}`;
21499}
21500
21501const REGEX = /CMCD=[^&#]+/;
21502/**
21503 * Append CMCD query args to a URL.
21504 *
21505 * @param url - The URL to append to.
21506 * @param cmcd - The CMCD object to append.
21507 * @param options - Options for encoding the CMCD object.
21508 *
21509 * @returns The URL with the CMCD query args appended.
21510 *
21511 * @group CMCD
21512 *
21513 * @beta
21514 *
21515 * @example
21516 * {@includeCode ../../test/cmcd/appendCmcdQuery.test.ts#example}
21517 */
21518function appendCmcdQuery(url, cmcd, options) {
21519  // TODO: Replace with URLSearchParams once we drop Safari < 10.1 & Chrome < 49 support.
21520  // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
21521  const query = toCmcdQuery(cmcd, options);
21522  if (!query) {
21523    return url;
21524  }
21525  if (REGEX.test(url)) {
21526    return url.replace(REGEX, query);
21527  }
21528  const separator = url.includes('?') ? '&' : '?';
21529  return `${url}${separator}${query}`;
21530}
21531
21532/**
21533 * Controller to deal with Common Media Client Data (CMCD)
21534 * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
21535 */
21536class CMCDController {
21537  constructor(hls) {
21538    this.hls = void 0;
21539    this.config = void 0;
21540    this.media = void 0;
21541    this.sid = void 0;
21542    this.cid = void 0;
21543    this.useHeaders = false;
21544    this.includeKeys = void 0;
21545    this.initialized = false;
21546    this.starved = false;
21547    this.buffering = true;
21548    this.audioBuffer = void 0;
21549    this.videoBuffer = void 0;
21550    this.onWaiting = () => {
21551      if (this.initialized) {
21552        this.starved = true;
21553      }
21554      this.buffering = true;
21555    };
21556    this.onPlaying = () => {
21557      if (!this.initialized) {
21558        this.initialized = true;
21559      }
21560      this.buffering = false;
21561    };
21562    /**
21563     * Apply CMCD data to a manifest request.
21564     */
21565    this.applyPlaylistData = context => {
21566      try {
21567        this.apply(context, {
21568          ot: CmcdObjectType.MANIFEST,
21569          su: !this.initialized
21570        });
21571      } catch (error) {
21572        this.hls.logger.warn('Could not generate manifest CMCD data.', error);
21573      }
21574    };
21575    /**
21576     * Apply CMCD data to a segment request
21577     */
21578    this.applyFragmentData = context => {
21579      try {
21580        const {
21581          frag,
21582          part
21583        } = context;
21584        const level = this.hls.levels[frag.level];
21585        const ot = this.getObjectType(frag);
21586        const data = {
21587          d: (part || frag).duration * 1000,
21588          ot
21589        };
21590        if (ot === CmcdObjectType.VIDEO || ot === CmcdObjectType.AUDIO || ot == CmcdObjectType.MUXED) {
21591          data.br = level.bitrate / 1000;
21592          data.tb = this.getTopBandwidth(ot) / 1000;
21593          data.bl = this.getBufferLength(ot);
21594        }
21595        const next = part ? this.getNextPart(part) : this.getNextFrag(frag);
21596        if (next != null && next.url && next.url !== frag.url) {
21597          data.nor = next.url;
21598        }
21599        this.apply(context, data);
21600      } catch (error) {
21601        this.hls.logger.warn('Could not generate segment CMCD data.', error);
21602      }
21603    };
21604    this.hls = hls;
21605    const config = this.config = hls.config;
21606    const {
21607      cmcd
21608    } = config;
21609    if (cmcd != null) {
21610      config.pLoader = this.createPlaylistLoader();
21611      config.fLoader = this.createFragmentLoader();
21612      this.sid = cmcd.sessionId || hls.sessionId;
21613      this.cid = cmcd.contentId;
21614      this.useHeaders = cmcd.useHeaders === true;
21615      this.includeKeys = cmcd.includeKeys;
21616      this.registerListeners();
21617    }
21618  }
21619  registerListeners() {
21620    const hls = this.hls;
21621    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
21622    hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
21623    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
21624  }
21625  unregisterListeners() {
21626    const hls = this.hls;
21627    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
21628    hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
21629    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
21630  }
21631  destroy() {
21632    this.unregisterListeners();
21633    this.onMediaDetached();
21634
21635    // @ts-ignore
21636    this.hls = this.config = this.audioBuffer = this.videoBuffer = null;
21637    // @ts-ignore
21638    this.onWaiting = this.onPlaying = this.media = null;
21639  }
21640  onMediaAttached(event, data) {
21641    this.media = data.media;
21642    this.media.addEventListener('waiting', this.onWaiting);
21643    this.media.addEventListener('playing', this.onPlaying);
21644  }
21645  onMediaDetached() {
21646    if (!this.media) {
21647      return;
21648    }
21649    this.media.removeEventListener('waiting', this.onWaiting);
21650    this.media.removeEventListener('playing', this.onPlaying);
21651
21652    // @ts-ignore
21653    this.media = null;
21654  }
21655  onBufferCreated(event, data) {
21656    var _data$tracks$audio, _data$tracks$video;
21657    this.audioBuffer = (_data$tracks$audio = data.tracks.audio) == null ? void 0 : _data$tracks$audio.buffer;
21658    this.videoBuffer = (_data$tracks$video = data.tracks.video) == null ? void 0 : _data$tracks$video.buffer;
21659  }
21660  /**
21661   * Create baseline CMCD data
21662   */
21663  createData() {
21664    var _this$media;
21665    return {
21666      v: 1,
21667      sf: CmcdStreamingFormat.HLS,
21668      sid: this.sid,
21669      cid: this.cid,
21670      pr: (_this$media = this.media) == null ? void 0 : _this$media.playbackRate,
21671      mtp: this.hls.bandwidthEstimate / 1000
21672    };
21673  }
21674
21675  /**
21676   * Apply CMCD data to a request.
21677   */
21678  apply(context, data = {}) {
21679    // apply baseline data
21680    _extends(data, this.createData());
21681    const isVideo = data.ot === CmcdObjectType.INIT || data.ot === CmcdObjectType.VIDEO || data.ot === CmcdObjectType.MUXED;
21682    if (this.starved && isVideo) {
21683      data.bs = true;
21684      data.su = true;
21685      this.starved = false;
21686    }
21687    if (data.su == null) {
21688      data.su = this.buffering;
21689    }
21690
21691    // TODO: Implement rtp, nrr, dl
21692
21693    const {
21694      includeKeys
21695    } = this;
21696    if (includeKeys) {
21697      data = Object.keys(data).reduce((acc, key) => {
21698        includeKeys.includes(key) && (acc[key] = data[key]);
21699        return acc;
21700      }, {});
21701    }
21702    const options = {
21703      baseUrl: context.url
21704    };
21705    if (this.useHeaders) {
21706      if (!context.headers) {
21707        context.headers = {};
21708      }
21709      appendCmcdHeaders(context.headers, data, options);
21710    } else {
21711      context.url = appendCmcdQuery(context.url, data, options);
21712    }
21713  }
21714  getNextFrag(fragment) {
21715    var _this$hls$levels$frag;
21716    const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
21717    if (levelDetails) {
21718      const index = fragment.sn - levelDetails.startSN;
21719      return levelDetails.fragments[index + 1];
21720    }
21721    return undefined;
21722  }
21723  getNextPart(part) {
21724    var _this$hls$levels$frag2;
21725    const {
21726      index,
21727      fragment
21728    } = part;
21729    const partList = (_this$hls$levels$frag2 = this.hls.levels[fragment.level]) == null || (_this$hls$levels$frag2 = _this$hls$levels$frag2.details) == null ? void 0 : _this$hls$levels$frag2.partList;
21730    if (partList) {
21731      const {
21732        sn
21733      } = fragment;
21734      for (let i = partList.length - 1; i >= 0; i--) {
21735        const p = partList[i];
21736        if (p.index === index && p.fragment.sn === sn) {
21737          return partList[i + 1];
21738        }
21739      }
21740    }
21741    return undefined;
21742  }
21743
21744  /**
21745   * The CMCD object type.
21746   */
21747  getObjectType(fragment) {
21748    const {
21749      type
21750    } = fragment;
21751    if (type === 'subtitle') {
21752      return CmcdObjectType.TIMED_TEXT;
21753    }
21754    if (fragment.sn === 'initSegment') {
21755      return CmcdObjectType.INIT;
21756    }
21757    if (type === 'audio') {
21758      return CmcdObjectType.AUDIO;
21759    }
21760    if (type === 'main') {
21761      if (!this.hls.audioTracks.length) {
21762        return CmcdObjectType.MUXED;
21763      }
21764      return CmcdObjectType.VIDEO;
21765    }
21766    return undefined;
21767  }
21768
21769  /**
21770   * Get the highest bitrate.
21771   */
21772  getTopBandwidth(type) {
21773    let bitrate = 0;
21774    let levels;
21775    const hls = this.hls;
21776    if (type === CmcdObjectType.AUDIO) {
21777      levels = hls.audioTracks;
21778    } else {
21779      const max = hls.maxAutoLevel;
21780      const len = max > -1 ? max + 1 : hls.levels.length;
21781      levels = hls.levels.slice(0, len);
21782    }
21783    levels.forEach(level => {
21784      if (level.bitrate > bitrate) {
21785        bitrate = level.bitrate;
21786      }
21787    });
21788    return bitrate > 0 ? bitrate : NaN;
21789  }
21790
21791  /**
21792   * Get the buffer length for a media type in milliseconds
21793   */
21794  getBufferLength(type) {
21795    const media = this.media;
21796    const buffer = type === CmcdObjectType.AUDIO ? this.audioBuffer : this.videoBuffer;
21797    if (!buffer || !media) {
21798      return NaN;
21799    }
21800    const info = BufferHelper.bufferInfo(buffer, media.currentTime, this.config.maxBufferHole);
21801    return info.len * 1000;
21802  }
21803
21804  /**
21805   * Create a playlist loader
21806   */
21807  createPlaylistLoader() {
21808    const {
21809      pLoader
21810    } = this.config;
21811    const apply = this.applyPlaylistData;
21812    const Ctor = pLoader || this.config.loader;
21813    return class CmcdPlaylistLoader {
21814      constructor(config) {
21815        this.loader = void 0;
21816        this.loader = new Ctor(config);
21817      }
21818      get stats() {
21819        return this.loader.stats;
21820      }
21821      get context() {
21822        return this.loader.context;
21823      }
21824      destroy() {
21825        this.loader.destroy();
21826      }
21827      abort() {
21828        this.loader.abort();
21829      }
21830      load(context, config, callbacks) {
21831        apply(context);
21832        this.loader.load(context, config, callbacks);
21833      }
21834    };
21835  }
21836
21837  /**
21838   * Create a playlist loader
21839   */
21840  createFragmentLoader() {
21841    const {
21842      fLoader
21843    } = this.config;
21844    const apply = this.applyFragmentData;
21845    const Ctor = fLoader || this.config.loader;
21846    return class CmcdFragmentLoader {
21847      constructor(config) {
21848        this.loader = void 0;
21849        this.loader = new Ctor(config);
21850      }
21851      get stats() {
21852        return this.loader.stats;
21853      }
21854      get context() {
21855        return this.loader.context;
21856      }
21857      destroy() {
21858        this.loader.destroy();
21859      }
21860      abort() {
21861        this.loader.abort();
21862      }
21863      load(context, config, callbacks) {
21864        apply(context);
21865        this.loader.load(context, config, callbacks);
21866      }
21867    };
21868  }
21869}
21870
21871const PATHWAY_PENALTY_DURATION_MS = 300000;
21872class ContentSteeringController extends Logger {
21873  constructor(hls) {
21874    super('content-steering', hls.logger);
21875    this.hls = void 0;
21876    this.loader = null;
21877    this.uri = null;
21878    this.pathwayId = '.';
21879    this._pathwayPriority = null;
21880    this.timeToLoad = 300;
21881    this.reloadTimer = -1;
21882    this.updated = 0;
21883    this.started = false;
21884    this.enabled = true;
21885    this.levels = null;
21886    this.audioTracks = null;
21887    this.subtitleTracks = null;
21888    this.penalizedPathways = {};
21889    this.hls = hls;
21890    this.registerListeners();
21891  }
21892  registerListeners() {
21893    const hls = this.hls;
21894    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
21895    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
21896    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
21897    hls.on(Events.ERROR, this.onError, this);
21898  }
21899  unregisterListeners() {
21900    const hls = this.hls;
21901    if (!hls) {
21902      return;
21903    }
21904    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
21905    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
21906    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
21907    hls.off(Events.ERROR, this.onError, this);
21908  }
21909  pathways() {
21910    return (this.levels || []).reduce((pathways, level) => {
21911      if (pathways.indexOf(level.pathwayId) === -1) {
21912        pathways.push(level.pathwayId);
21913      }
21914      return pathways;
21915    }, []);
21916  }
21917  get pathwayPriority() {
21918    return this._pathwayPriority;
21919  }
21920  set pathwayPriority(pathwayPriority) {
21921    this.updatePathwayPriority(pathwayPriority);
21922  }
21923  startLoad() {
21924    this.started = true;
21925    this.clearTimeout();
21926    if (this.enabled && this.uri) {
21927      if (this.updated) {
21928        const ttl = this.timeToLoad * 1000 - (performance.now() - this.updated);
21929        if (ttl > 0) {
21930          this.scheduleRefresh(this.uri, ttl);
21931          return;
21932        }
21933      }
21934      this.loadSteeringManifest(this.uri);
21935    }
21936  }
21937  stopLoad() {
21938    this.started = false;
21939    if (this.loader) {
21940      this.loader.destroy();
21941      this.loader = null;
21942    }
21943    this.clearTimeout();
21944  }
21945  clearTimeout() {
21946    if (this.reloadTimer !== -1) {
21947      self.clearTimeout(this.reloadTimer);
21948      this.reloadTimer = -1;
21949    }
21950  }
21951  destroy() {
21952    this.unregisterListeners();
21953    this.stopLoad();
21954    // @ts-ignore
21955    this.hls = null;
21956    this.levels = this.audioTracks = this.subtitleTracks = null;
21957  }
21958  removeLevel(levelToRemove) {
21959    const levels = this.levels;
21960    if (levels) {
21961      this.levels = levels.filter(level => level !== levelToRemove);
21962    }
21963  }
21964  onManifestLoading() {
21965    this.stopLoad();
21966    this.enabled = true;
21967    this.timeToLoad = 300;
21968    this.updated = 0;
21969    this.uri = null;
21970    this.pathwayId = '.';
21971    this.levels = this.audioTracks = this.subtitleTracks = null;
21972  }
21973  onManifestLoaded(event, data) {
21974    const {
21975      contentSteering
21976    } = data;
21977    if (contentSteering === null) {
21978      return;
21979    }
21980    this.pathwayId = contentSteering.pathwayId;
21981    this.uri = contentSteering.uri;
21982    if (this.started) {
21983      this.startLoad();
21984    }
21985  }
21986  onManifestParsed(event, data) {
21987    this.audioTracks = data.audioTracks;
21988    this.subtitleTracks = data.subtitleTracks;
21989  }
21990  onError(event, data) {
21991    const {
21992      errorAction
21993    } = data;
21994    if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox && errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost) {
21995      const levels = this.levels;
21996      let pathwayPriority = this._pathwayPriority;
21997      let errorPathway = this.pathwayId;
21998      if (data.context) {
21999        const {
22000          groupId,
22001          pathwayId,
22002          type
22003        } = data.context;
22004        if (groupId && levels) {
22005          errorPathway = this.getPathwayForGroupId(groupId, type, errorPathway);
22006        } else if (pathwayId) {
22007          errorPathway = pathwayId;
22008        }
22009      }
22010      if (!(errorPathway in this.penalizedPathways)) {
22011        this.penalizedPathways[errorPathway] = performance.now();
22012      }
22013      if (!pathwayPriority && levels) {
22014        // If PATHWAY-PRIORITY was not provided, list pathways for error handling
22015        pathwayPriority = this.pathways();
22016      }
22017      if (pathwayPriority && pathwayPriority.length > 1) {
22018        this.updatePathwayPriority(pathwayPriority);
22019        errorAction.resolved = this.pathwayId !== errorPathway;
22020      }
22021      if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && !data.fatal) {
22022        // Error will become fatal in buffer-controller when reaching `appendErrorMaxRetry`
22023        // Stream-controllers are expected to reduce buffer length even if this is not deemed a QuotaExceededError
22024        errorAction.resolved = true;
22025      } else if (!errorAction.resolved) {
22026        this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${stringify(pathwayPriority)} penalized: ${stringify(this.penalizedPathways)}`);
22027      }
22028    }
22029  }
22030  filterParsedLevels(levels) {
22031    // Filter levels to only include those that are in the initial pathway
22032    this.levels = levels;
22033    let pathwayLevels = this.getLevelsForPathway(this.pathwayId);
22034    if (pathwayLevels.length === 0) {
22035      const pathwayId = levels[0].pathwayId;
22036      this.log(`No levels found in Pathway ${this.pathwayId}. Setting initial Pathway to "${pathwayId}"`);
22037      pathwayLevels = this.getLevelsForPathway(pathwayId);
22038      this.pathwayId = pathwayId;
22039    }
22040    if (pathwayLevels.length !== levels.length) {
22041      this.log(`Found ${pathwayLevels.length}/${levels.length} levels in Pathway "${this.pathwayId}"`);
22042    }
22043    return pathwayLevels;
22044  }
22045  getLevelsForPathway(pathwayId) {
22046    if (this.levels === null) {
22047      return [];
22048    }
22049    return this.levels.filter(level => pathwayId === level.pathwayId);
22050  }
22051  updatePathwayPriority(pathwayPriority) {
22052    this._pathwayPriority = pathwayPriority;
22053    let levels;
22054
22055    // Evaluate if we should remove the pathway from the penalized list
22056    const penalizedPathways = this.penalizedPathways;
22057    const now = performance.now();
22058    Object.keys(penalizedPathways).forEach(pathwayId => {
22059      if (now - penalizedPathways[pathwayId] > PATHWAY_PENALTY_DURATION_MS) {
22060        delete penalizedPathways[pathwayId];
22061      }
22062    });
22063    for (let i = 0; i < pathwayPriority.length; i++) {
22064      const pathwayId = pathwayPriority[i];
22065      if (pathwayId in penalizedPathways) {
22066        continue;
22067      }
22068      if (pathwayId === this.pathwayId) {
22069        return;
22070      }
22071      const selectedIndex = this.hls.nextLoadLevel;
22072      const selectedLevel = this.hls.levels[selectedIndex];
22073      levels = this.getLevelsForPathway(pathwayId);
22074      if (levels.length > 0) {
22075        this.log(`Setting Pathway to "${pathwayId}"`);
22076        this.pathwayId = pathwayId;
22077        reassignFragmentLevelIndexes(levels);
22078        this.hls.trigger(Events.LEVELS_UPDATED, {
22079          levels
22080        });
22081        // Set LevelController's level to trigger LEVEL_SWITCHING which loads playlist if needed
22082        const levelAfterChange = this.hls.levels[selectedIndex];
22083        if (selectedLevel && levelAfterChange && this.levels) {
22084          if (levelAfterChange.attrs['STABLE-VARIANT-ID'] !== selectedLevel.attrs['STABLE-VARIANT-ID'] && levelAfterChange.bitrate !== selectedLevel.bitrate) {
22085            this.log(`Unstable Pathways change from bitrate ${selectedLevel.bitrate} to ${levelAfterChange.bitrate}`);
22086          }
22087          this.hls.nextLoadLevel = selectedIndex;
22088        }
22089        break;
22090      }
22091    }
22092  }
22093  getPathwayForGroupId(groupId, type, defaultPathway) {
22094    const levels = this.getLevelsForPathway(defaultPathway).concat(this.levels || []);
22095    for (let i = 0; i < levels.length; i++) {
22096      if (type === PlaylistContextType.AUDIO_TRACK && levels[i].hasAudioGroup(groupId) || type === PlaylistContextType.SUBTITLE_TRACK && levels[i].hasSubtitleGroup(groupId)) {
22097        return levels[i].pathwayId;
22098      }
22099    }
22100    return defaultPathway;
22101  }
22102  clonePathways(pathwayClones) {
22103    const levels = this.levels;
22104    if (!levels) {
22105      return;
22106    }
22107    const audioGroupCloneMap = {};
22108    const subtitleGroupCloneMap = {};
22109    pathwayClones.forEach(pathwayClone => {
22110      const {
22111        ID: cloneId,
22112        'BASE-ID': baseId,
22113        'URI-REPLACEMENT': uriReplacement
22114      } = pathwayClone;
22115      if (levels.some(level => level.pathwayId === cloneId)) {
22116        return;
22117      }
22118      const clonedVariants = this.getLevelsForPathway(baseId).map(baseLevel => {
22119        const attributes = new AttrList(baseLevel.attrs);
22120        attributes['PATHWAY-ID'] = cloneId;
22121        const clonedAudioGroupId = attributes.AUDIO && `${attributes.AUDIO}_clone_${cloneId}`;
22122        const clonedSubtitleGroupId = attributes.SUBTITLES && `${attributes.SUBTITLES}_clone_${cloneId}`;
22123        if (clonedAudioGroupId) {
22124          audioGroupCloneMap[attributes.AUDIO] = clonedAudioGroupId;
22125          attributes.AUDIO = clonedAudioGroupId;
22126        }
22127        if (clonedSubtitleGroupId) {
22128          subtitleGroupCloneMap[attributes.SUBTITLES] = clonedSubtitleGroupId;
22129          attributes.SUBTITLES = clonedSubtitleGroupId;
22130        }
22131        const url = performUriReplacement(baseLevel.uri, attributes['STABLE-VARIANT-ID'], 'PER-VARIANT-URIS', uriReplacement);
22132        const clonedLevel = new Level({
22133          attrs: attributes,
22134          audioCodec: baseLevel.audioCodec,
22135          bitrate: baseLevel.bitrate,
22136          height: baseLevel.height,
22137          name: baseLevel.name,
22138          url,
22139          videoCodec: baseLevel.videoCodec,
22140          width: baseLevel.width
22141        });
22142        if (baseLevel.audioGroups) {
22143          for (let i = 1; i < baseLevel.audioGroups.length; i++) {
22144            clonedLevel.addGroupId('audio', `${baseLevel.audioGroups[i]}_clone_${cloneId}`);
22145          }
22146        }
22147        if (baseLevel.subtitleGroups) {
22148          for (let i = 1; i < baseLevel.subtitleGroups.length; i++) {
22149            clonedLevel.addGroupId('text', `${baseLevel.subtitleGroups[i]}_clone_${cloneId}`);
22150          }
22151        }
22152        return clonedLevel;
22153      });
22154      levels.push(...clonedVariants);
22155      cloneRenditionGroups(this.audioTracks, audioGroupCloneMap, uriReplacement, cloneId);
22156      cloneRenditionGroups(this.subtitleTracks, subtitleGroupCloneMap, uriReplacement, cloneId);
22157    });
22158  }
22159  loadSteeringManifest(uri) {
22160    const config = this.hls.config;
22161    const Loader = config.loader;
22162    if (this.loader) {
22163      this.loader.destroy();
22164    }
22165    this.loader = new Loader(config);
22166    let url;
22167    try {
22168      url = new self.URL(uri);
22169    } catch (error) {
22170      this.enabled = false;
22171      this.log(`Failed to parse Steering Manifest URI: ${uri}`);
22172      return;
22173    }
22174    if (url.protocol !== 'data:') {
22175      const throughput = (this.hls.bandwidthEstimate || config.abrEwmaDefaultEstimate) | 0;
22176      url.searchParams.set('_HLS_pathway', this.pathwayId);
22177      url.searchParams.set('_HLS_throughput', '' + throughput);
22178    }
22179    const context = {
22180      responseType: 'json',
22181      url: url.href
22182    };
22183    const loadPolicy = config.steeringManifestLoadPolicy.default;
22184    const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
22185    const loaderConfig = {
22186      loadPolicy,
22187      timeout: loadPolicy.maxLoadTimeMs,
22188      maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
22189      retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
22190      maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0
22191    };
22192    const callbacks = {
22193      onSuccess: (response, stats, context, networkDetails) => {
22194        this.log(`Loaded steering manifest: "${url}"`);
22195        const steeringData = response.data;
22196        if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
22197          this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
22198          return;
22199        }
22200        this.updated = performance.now();
22201        this.timeToLoad = steeringData.TTL;
22202        const {
22203          'RELOAD-URI': reloadUri,
22204          'PATHWAY-CLONES': pathwayClones,
22205          'PATHWAY-PRIORITY': pathwayPriority
22206        } = steeringData;
22207        if (reloadUri) {
22208          try {
22209            this.uri = new self.URL(reloadUri, url).href;
22210          } catch (error) {
22211            this.enabled = false;
22212            this.log(`Failed to parse Steering Manifest RELOAD-URI: ${reloadUri}`);
22213            return;
22214          }
22215        }
22216        this.scheduleRefresh(this.uri || context.url);
22217        if (pathwayClones) {
22218          this.clonePathways(pathwayClones);
22219        }
22220        const loadedSteeringData = {
22221          steeringManifest: steeringData,
22222          url: url.toString()
22223        };
22224        this.hls.trigger(Events.STEERING_MANIFEST_LOADED, loadedSteeringData);
22225        if (pathwayPriority) {
22226          this.updatePathwayPriority(pathwayPriority);
22227        }
22228      },
22229      onError: (error, context, networkDetails, stats) => {
22230        this.log(`Error loading steering manifest: ${error.code} ${error.text} (${context.url})`);
22231        this.stopLoad();
22232        if (error.code === 410) {
22233          this.enabled = false;
22234          this.log(`Steering manifest ${context.url} no longer available`);
22235          return;
22236        }
22237        let ttl = this.timeToLoad * 1000;
22238        if (error.code === 429) {
22239          const loader = this.loader;
22240          if (typeof (loader == null ? void 0 : loader.getResponseHeader) === 'function') {
22241            const retryAfter = loader.getResponseHeader('Retry-After');
22242            if (retryAfter) {
22243              ttl = parseFloat(retryAfter) * 1000;
22244            }
22245          }
22246          this.log(`Steering manifest ${context.url} rate limited`);
22247          return;
22248        }
22249        this.scheduleRefresh(this.uri || context.url, ttl);
22250      },
22251      onTimeout: (stats, context, networkDetails) => {
22252        this.log(`Timeout loading steering manifest (${context.url})`);
22253        this.scheduleRefresh(this.uri || context.url);
22254      }
22255    };
22256    this.log(`Requesting steering manifest: ${url}`);
22257    this.loader.load(context, loaderConfig, callbacks);
22258  }
22259  scheduleRefresh(uri, ttlMs = this.timeToLoad * 1000) {
22260    this.clearTimeout();
22261    this.reloadTimer = self.setTimeout(() => {
22262      var _this$hls;
22263      const media = (_this$hls = this.hls) == null ? void 0 : _this$hls.media;
22264      if (media && !media.ended) {
22265        this.loadSteeringManifest(uri);
22266        return;
22267      }
22268      this.scheduleRefresh(uri, this.timeToLoad * 1000);
22269    }, ttlMs);
22270  }
22271}
22272function cloneRenditionGroups(tracks, groupCloneMap, uriReplacement, cloneId) {
22273  if (!tracks) {
22274    return;
22275  }
22276  Object.keys(groupCloneMap).forEach(audioGroupId => {
22277    const clonedTracks = tracks.filter(track => track.groupId === audioGroupId).map(track => {
22278      const clonedTrack = _extends({}, track);
22279      clonedTrack.details = undefined;
22280      clonedTrack.attrs = new AttrList(clonedTrack.attrs);
22281      clonedTrack.url = clonedTrack.attrs.URI = performUriReplacement(track.url, track.attrs['STABLE-RENDITION-ID'], 'PER-RENDITION-URIS', uriReplacement);
22282      clonedTrack.groupId = clonedTrack.attrs['GROUP-ID'] = groupCloneMap[audioGroupId];
22283      clonedTrack.attrs['PATHWAY-ID'] = cloneId;
22284      return clonedTrack;
22285    });
22286    tracks.push(...clonedTracks);
22287  });
22288}
22289function performUriReplacement(uri, stableId, perOptionKey, uriReplacement) {
22290  const {
22291    HOST: host,
22292    PARAMS: params,
22293    [perOptionKey]: perOptionUris
22294  } = uriReplacement;
22295  let perVariantUri;
22296  if (stableId) {
22297    perVariantUri = perOptionUris == null ? void 0 : perOptionUris[stableId];
22298    if (perVariantUri) {
22299      uri = perVariantUri;
22300    }
22301  }
22302  const url = new self.URL(uri);
22303  if (host && !perVariantUri) {
22304    url.host = host;
22305  }
22306  if (params) {
22307    Object.keys(params).sort().forEach(key => {
22308      if (key) {
22309        url.searchParams.set(key, params[key]);
22310      }
22311    });
22312  }
22313  return url.href;
22314}
22315
22316/**
22317 * Controller to deal with encrypted media extensions (EME)
22318 * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
22319 *
22320 * @class
22321 * @constructor
22322 */
22323class EMEController extends Logger {
22324  constructor(hls) {
22325    super('eme', hls.logger);
22326    this.hls = void 0;
22327    this.config = void 0;
22328    this.media = null;
22329    this.mediaResolved = void 0;
22330    this.keyFormatPromise = null;
22331    this.keySystemAccessPromises = {};
22332    this._requestLicenseFailureCount = 0;
22333    this.mediaKeySessions = [];
22334    this.keyIdToKeySessionPromise = {};
22335    this.mediaKeys = null;
22336    this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
22337    this.bannedKeyIds = {};
22338    this.onMediaEncrypted = event => {
22339      const {
22340        initDataType,
22341        initData
22342      } = event;
22343      const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
22344      this.debug(logMessage);
22345
22346      // Ignore event when initData is null
22347      if (initData === null) {
22348        return;
22349      }
22350      if (!this.keyFormatPromise) {
22351        let keySystems = Object.keys(this.keySystemAccessPromises);
22352        if (!keySystems.length) {
22353          keySystems = getKeySystemsForConfig(this.config);
22354        }
22355        const keyFormats = keySystems.map(keySystemDomainToKeySystemFormat).filter(k => !!k);
22356        this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
22357      }
22358      this.keyFormatPromise.then(keySystemFormat => {
22359        const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
22360        if (initDataType !== 'sinf' || keySystem !== KeySystems.FAIRPLAY) {
22361          this.log(`Ignoring "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
22362          return;
22363        }
22364
22365        // Match sinf keyId to playlist skd://keyId=
22366        let keyId;
22367        try {
22368          const json = bin2str(new Uint8Array(initData));
22369          const sinf = base64Decode(JSON.parse(json).sinf);
22370          const tenc = parseSinf(sinf);
22371          if (!tenc) {
22372            throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
22373          }
22374          keyId = new Uint8Array(tenc.subarray(8, 24));
22375        } catch (error) {
22376          this.warn(`${logMessage} Failed to parse sinf: ${error}`);
22377          return;
22378        }
22379        const keyIdHex = arrayToHex(keyId);
22380        const {
22381          keyIdToKeySessionPromise,
22382          mediaKeySessions
22383        } = this;
22384        let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
22385        for (let i = 0; i < mediaKeySessions.length; i++) {
22386          // Match playlist key
22387          const keyContext = mediaKeySessions[i];
22388          const decryptdata = keyContext.decryptdata;
22389          if (!decryptdata.keyId) {
22390            continue;
22391          }
22392          const oldKeyIdHex = arrayToHex(decryptdata.keyId);
22393          if (arrayValuesMatch(keyId, decryptdata.keyId) || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
22394            keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
22395            if (!keySessionContextPromise) {
22396              continue;
22397            }
22398            if (decryptdata.pssh) {
22399              break;
22400            }
22401            delete keyIdToKeySessionPromise[oldKeyIdHex];
22402            decryptdata.pssh = new Uint8Array(initData);
22403            decryptdata.keyId = keyId;
22404            keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
22405              return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
22406            });
22407            keySessionContextPromise.catch(error => this.handleError(error));
22408            break;
22409          }
22410        }
22411        if (!keySessionContextPromise) {
22412          this.handleError(new Error(`Key ID ${keyIdHex} not encountered in playlist. Key-system sessions ${mediaKeySessions.length}.`));
22413        }
22414      }).catch(error => this.handleError(error));
22415    };
22416    this.onWaitingForKey = event => {
22417      this.log(`"${event.type}" event`);
22418    };
22419    this.hls = hls;
22420    this.config = hls.config;
22421    this.registerListeners();
22422  }
22423  destroy() {
22424    this.onDestroying();
22425    this.onMediaDetached();
22426    // Remove any references that could be held in config options or callbacks
22427    const config = this.config;
22428    config.requestMediaKeySystemAccessFunc = null;
22429    config.licenseXhrSetup = config.licenseResponseCallback = undefined;
22430    config.drmSystems = config.drmSystemOptions = {};
22431    // @ts-ignore
22432    this.hls = this.config = this.keyIdToKeySessionPromise = null;
22433    // @ts-ignore
22434    this.onMediaEncrypted = this.onWaitingForKey = null;
22435  }
22436  registerListeners() {
22437    this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
22438    this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this);
22439    this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
22440    this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
22441    this.hls.on(Events.DESTROYING, this.onDestroying, this);
22442  }
22443  unregisterListeners() {
22444    this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
22445    this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this);
22446    this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
22447    this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
22448    this.hls.off(Events.DESTROYING, this.onDestroying, this);
22449  }
22450  getLicenseServerUrl(keySystem) {
22451    const {
22452      drmSystems,
22453      widevineLicenseUrl
22454    } = this.config;
22455    const keySystemConfiguration = drmSystems == null ? void 0 : drmSystems[keySystem];
22456    if (keySystemConfiguration) {
22457      return keySystemConfiguration.licenseUrl;
22458    }
22459
22460    // For backward compatibility
22461    if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
22462      return widevineLicenseUrl;
22463    }
22464  }
22465  getLicenseServerUrlOrThrow(keySystem) {
22466    const url = this.getLicenseServerUrl(keySystem);
22467    if (url === undefined) {
22468      throw new Error(`no license server URL configured for key-system "${keySystem}"`);
22469    }
22470    return url;
22471  }
22472  getServerCertificateUrl(keySystem) {
22473    const {
22474      drmSystems
22475    } = this.config;
22476    const keySystemConfiguration = drmSystems == null ? void 0 : drmSystems[keySystem];
22477    if (keySystemConfiguration) {
22478      return keySystemConfiguration.serverCertificateUrl;
22479    } else {
22480      this.log(`No Server Certificate in config.drmSystems["${keySystem}"]`);
22481    }
22482  }
22483  attemptKeySystemAccess(keySystemsToAttempt) {
22484    const levels = this.hls.levels;
22485    const uniqueCodec = (value, i, a) => !!value && a.indexOf(value) === i;
22486    const audioCodecs = levels.map(level => level.audioCodec).filter(uniqueCodec);
22487    const videoCodecs = levels.map(level => level.videoCodec).filter(uniqueCodec);
22488    if (audioCodecs.length + videoCodecs.length === 0) {
22489      videoCodecs.push('avc1.42e01e');
22490    }
22491    return new Promise((resolve, reject) => {
22492      const attempt = keySystems => {
22493        const keySystem = keySystems.shift();
22494        this.getMediaKeysPromise(keySystem, audioCodecs, videoCodecs).then(mediaKeys => resolve({
22495          keySystem,
22496          mediaKeys
22497        })).catch(error => {
22498          if (keySystems.length) {
22499            attempt(keySystems);
22500          } else if (error instanceof EMEKeyError) {
22501            reject(error);
22502          } else {
22503            reject(new EMEKeyError({
22504              type: ErrorTypes.KEY_SYSTEM_ERROR,
22505              details: ErrorDetails.KEY_SYSTEM_NO_ACCESS,
22506              error,
22507              fatal: true
22508            }, error.message));
22509          }
22510        });
22511      };
22512      attempt(keySystemsToAttempt);
22513    });
22514  }
22515  requestMediaKeySystemAccess(keySystem, supportedConfigurations) {
22516    const {
22517      requestMediaKeySystemAccessFunc
22518    } = this.config;
22519    if (!(typeof requestMediaKeySystemAccessFunc === 'function')) {
22520      let errMessage = `Configured requestMediaKeySystemAccess is not a function ${requestMediaKeySystemAccessFunc}`;
22521      if (requestMediaKeySystemAccess === null && self.location.protocol === 'http:') {
22522        errMessage = `navigator.requestMediaKeySystemAccess is not available over insecure protocol ${location.protocol}`;
22523      }
22524      return Promise.reject(new Error(errMessage));
22525    }
22526    return requestMediaKeySystemAccessFunc(keySystem, supportedConfigurations);
22527  }
22528  getMediaKeysPromise(keySystem, audioCodecs, videoCodecs) {
22529    var _keySystemAccessPromi;
22530    // This can throw, but is caught in event handler callpath
22531    const mediaKeySystemConfigs = getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, this.config.drmSystemOptions || {});
22532    let keySystemAccessPromises = this.keySystemAccessPromises[keySystem];
22533    let keySystemAccess = (_keySystemAccessPromi = keySystemAccessPromises) == null ? void 0 : _keySystemAccessPromi.keySystemAccess;
22534    if (!keySystemAccess) {
22535      this.log(`Requesting encrypted media "${keySystem}" key-system access with config: ${stringify(mediaKeySystemConfigs)}`);
22536      keySystemAccess = this.requestMediaKeySystemAccess(keySystem, mediaKeySystemConfigs);
22537      const keySystemAccessPromisesNew = keySystemAccessPromises = this.keySystemAccessPromises[keySystem] = {
22538        keySystemAccess
22539      };
22540      keySystemAccess.catch(error => {
22541        this.log(`Failed to obtain access to key-system "${keySystem}": ${error}`);
22542      });
22543      return keySystemAccess.then(mediaKeySystemAccess => {
22544        this.log(`Access for key-system "${mediaKeySystemAccess.keySystem}" obtained`);
22545        const certificateRequest = this.fetchServerCertificate(keySystem);
22546        this.log(`Create media-keys for "${keySystem}"`);
22547        const mediaKeys = keySystemAccessPromisesNew.mediaKeys = mediaKeySystemAccess.createMediaKeys().then(mediaKeys => {
22548          this.log(`Media-keys created for "${keySystem}"`);
22549          keySystemAccessPromisesNew.hasMediaKeys = true;
22550          return certificateRequest.then(certificate => {
22551            if (certificate) {
22552              return this.setMediaKeysServerCertificate(mediaKeys, keySystem, certificate);
22553            }
22554            return mediaKeys;
22555          });
22556        });
22557        mediaKeys.catch(error => {
22558          this.error(`Failed to create media-keys for "${keySystem}"}: ${error}`);
22559        });
22560        return mediaKeys;
22561      });
22562    }
22563    return keySystemAccess.then(() => keySystemAccessPromises.mediaKeys);
22564  }
22565  createMediaKeySessionContext({
22566    decryptdata,
22567    keySystem,
22568    mediaKeys
22569  }) {
22570    this.log(`Creating key-system session "${keySystem}" keyId: ${arrayToHex(decryptdata.keyId || [])} keyUri: ${decryptdata.uri}`);
22571    const mediaKeysSession = mediaKeys.createSession();
22572    const mediaKeySessionContext = {
22573      decryptdata,
22574      keySystem,
22575      mediaKeys,
22576      mediaKeysSession,
22577      keyStatus: 'status-pending'
22578    };
22579    this.mediaKeySessions.push(mediaKeySessionContext);
22580    return mediaKeySessionContext;
22581  }
22582  renewKeySession(mediaKeySessionContext) {
22583    const decryptdata = mediaKeySessionContext.decryptdata;
22584    if (decryptdata.pssh) {
22585      const keySessionContext = this.createMediaKeySessionContext(mediaKeySessionContext);
22586      const keyId = getKeyIdString(decryptdata);
22587      const scheme = 'cenc';
22588      this.keyIdToKeySessionPromise[keyId] = this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh.buffer, 'expired');
22589    } else {
22590      this.warn(`Could not renew expired session. Missing pssh initData.`);
22591    }
22592    // eslint-disable-next-line @typescript-eslint/no-floating-promises
22593    this.removeSession(mediaKeySessionContext);
22594  }
22595  updateKeySession(mediaKeySessionContext, data) {
22596    const keySession = mediaKeySessionContext.mediaKeysSession;
22597    this.log(`Updating key-session "${keySession.sessionId}" for keyId ${arrayToHex(mediaKeySessionContext.decryptdata.keyId || [])}
22598      } (data length: ${data.byteLength})`);
22599    return keySession.update(data);
22600  }
22601  getSelectedKeySystemFormats() {
22602    return Object.keys(this.keySystemAccessPromises).map(keySystem => ({
22603      keySystem,
22604      hasMediaKeys: this.keySystemAccessPromises[keySystem].hasMediaKeys
22605    })).filter(({
22606      hasMediaKeys
22607    }) => !!hasMediaKeys).map(({
22608      keySystem
22609    }) => keySystemDomainToKeySystemFormat(keySystem)).filter(keySystem => !!keySystem);
22610  }
22611  getKeySystemAccess(keySystemsToAttempt) {
22612    return this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({
22613      keySystem,
22614      mediaKeys
22615    }) => {
22616      return this.attemptSetMediaKeys(keySystem, mediaKeys);
22617    });
22618  }
22619  selectKeySystem(keySystemsToAttempt) {
22620    return new Promise((resolve, reject) => {
22621      this.getKeySystemSelectionPromise(keySystemsToAttempt).then(({
22622        keySystem
22623      }) => {
22624        const keySystemFormat = keySystemDomainToKeySystemFormat(keySystem);
22625        if (keySystemFormat) {
22626          resolve(keySystemFormat);
22627        } else {
22628          reject(new Error(`Unable to find format for key-system "${keySystem}"`));
22629        }
22630      }).catch(reject);
22631    });
22632  }
22633  selectKeySystemFormat(frag) {
22634    const keyFormats = Object.keys(frag.levelkeys || {});
22635    if (!this.keyFormatPromise) {
22636      this.log(`Selecting key-system from fragment (sn: ${frag.sn} ${frag.type}: ${frag.level}) key formats ${keyFormats.join(', ')}`);
22637      this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
22638    }
22639    return this.keyFormatPromise;
22640  }
22641  getKeyFormatPromise(keyFormats) {
22642    const keySystemsInConfig = getKeySystemsForConfig(this.config);
22643    const keySystemsToAttempt = keyFormats.map(keySystemFormatToKeySystemDomain).filter(value => !!value && keySystemsInConfig.indexOf(value) !== -1);
22644    return this.selectKeySystem(keySystemsToAttempt);
22645  }
22646  getKeyStatus(decryptdata) {
22647    const {
22648      mediaKeySessions
22649    } = this;
22650    for (let i = 0; i < mediaKeySessions.length; i++) {
22651      const status = getKeyStatus(decryptdata, mediaKeySessions[i]);
22652      if (status) {
22653        return status;
22654      }
22655    }
22656    return undefined;
22657  }
22658  loadKey(data) {
22659    const decryptdata = data.keyInfo.decryptdata;
22660    const keyId = getKeyIdString(decryptdata);
22661    const badStatus = this.bannedKeyIds[keyId];
22662    if (badStatus || this.getKeyStatus(decryptdata) === 'internal-error') {
22663      const error = getKeyStatusError(badStatus || 'internal-error', decryptdata);
22664      this.handleError(error, data.frag);
22665      return Promise.reject(error);
22666    }
22667    const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`;
22668    this.log(`Starting session for key ${keyDetails}`);
22669    const keyContextPromise = this.keyIdToKeySessionPromise[keyId];
22670    if (!keyContextPromise) {
22671      const keySessionContextPromise = this.getKeySystemForKeyPromise(decryptdata).then(({
22672        keySystem,
22673        mediaKeys
22674      }) => {
22675        this.throwIfDestroyed();
22676        this.log(`Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`);
22677        return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
22678          this.throwIfDestroyed();
22679          return this.createMediaKeySessionContext({
22680            keySystem,
22681            mediaKeys,
22682            decryptdata
22683          });
22684        });
22685      }).then(keySessionContext => {
22686        const scheme = 'cenc';
22687        const initData = decryptdata.pssh ? decryptdata.pssh.buffer : null;
22688        return this.generateRequestWithPreferredKeySession(keySessionContext, scheme, initData, 'playlist-key');
22689      });
22690      keySessionContextPromise.catch(error => this.handleError(error, data.frag));
22691      this.keyIdToKeySessionPromise[keyId] = keySessionContextPromise;
22692      return keySessionContextPromise;
22693    }
22694
22695    // Re-emit error for playlist key loading
22696    keyContextPromise.catch(error => {
22697      if (error instanceof EMEKeyError) {
22698        const errorData = _objectSpread2({}, error.data);
22699        if (this.getKeyStatus(decryptdata) === 'internal-error') {
22700          errorData.decryptdata = decryptdata;
22701        }
22702        const clonedError = new EMEKeyError(errorData, error.message);
22703        this.handleError(clonedError, data.frag);
22704      }
22705    });
22706    return keyContextPromise;
22707  }
22708  throwIfDestroyed(message = 'Invalid state') {
22709    if (!this.hls) {
22710      throw new Error('invalid state');
22711    }
22712  }
22713  handleError(error, frag) {
22714    if (!this.hls) {
22715      return;
22716    }
22717    if (error instanceof EMEKeyError) {
22718      if (frag) {
22719        error.data.frag = frag;
22720      }
22721      const levelKey = error.data.decryptdata;
22722      this.error(`${error.message}${levelKey ? ` (${arrayToHex(levelKey.keyId || [])})` : ''}`);
22723      this.hls.trigger(Events.ERROR, error.data);
22724    } else {
22725      this.error(error.message);
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 = 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 ${stringify({
22754        drmSystems: this.config.drmSystems
22755      })}`);
22756    }
22757    return this.attemptKeySystemAccess(keySystemsToAttempt);
22758  }
22759  attemptSetMediaKeys(keySystem, mediaKeys) {
22760    this.mediaResolved = undefined;
22761    if (this.mediaKeys === mediaKeys) {
22762      return Promise.resolve();
22763    }
22764    const queue = this.setMediaKeysQueue.slice();
22765    this.log(`Setting media-keys for "${keySystem}"`);
22766    // Only one setMediaKeys() can run at one time, and multiple setMediaKeys() operations
22767    // can be queued for execution for multiple key sessions.
22768    const setMediaKeysPromise = Promise.all(queue).then(() => {
22769      if (!this.media) {
22770        return new Promise((resolve, reject) => {
22771          this.mediaResolved = () => {
22772            this.mediaResolved = undefined;
22773            if (!this.media) {
22774              return reject(new Error('Attempted to set mediaKeys without media element attached'));
22775            }
22776            this.mediaKeys = mediaKeys;
22777            this.media.setMediaKeys(mediaKeys).then(resolve).catch(reject);
22778          };
22779        });
22780      }
22781      return this.media.setMediaKeys(mediaKeys);
22782    });
22783    this.mediaKeys = mediaKeys;
22784    this.setMediaKeysQueue.push(setMediaKeysPromise);
22785    return setMediaKeysPromise.then(() => {
22786      this.log(`Media-keys set for "${keySystem}"`);
22787      queue.push(setMediaKeysPromise);
22788      this.setMediaKeysQueue = this.setMediaKeysQueue.filter(p => queue.indexOf(p) === -1);
22789    });
22790  }
22791  generateRequestWithPreferredKeySession(context, initDataType, initData, reason) {
22792    var _this$config$drmSyste;
22793    const generateRequestFilter = (_this$config$drmSyste = this.config.drmSystems) == null || (_this$config$drmSyste = _this$config$drmSyste[context.keySystem]) == null ? void 0 : _this$config$drmSyste.generateRequest;
22794    if (generateRequestFilter) {
22795      try {
22796        const mappedInitData = generateRequestFilter.call(this.hls, initDataType, initData, context);
22797        if (!mappedInitData) {
22798          throw new Error('Invalid response from configured generateRequest filter');
22799        }
22800        initDataType = mappedInitData.initDataType;
22801        initData = mappedInitData.initData ? mappedInitData.initData : null;
22802        context.decryptdata.pssh = initData ? new Uint8Array(initData) : null;
22803      } catch (error) {
22804        this.warn(error.message);
22805        if (this.hls && this.hls.config.debug) {
22806          throw error;
22807        }
22808      }
22809    }
22810    if (initData === null) {
22811      this.log(`Skipping key-session request for "${reason}" (no initData)`);
22812      return Promise.resolve(context);
22813    }
22814    const keyId = getKeyIdString(context.decryptdata);
22815    const keyUri = context.decryptdata.uri;
22816    this.log(`Generating key-session request for "${reason}" keyId: ${keyId} URI: ${keyUri} (init data type: ${initDataType} length: ${initData.byteLength})`);
22817    const licenseStatus = new EventEmitter();
22818    const onmessage = context._onmessage = event => {
22819      const keySession = context.mediaKeysSession;
22820      if (!keySession) {
22821        licenseStatus.emit('error', new Error('invalid state'));
22822        return;
22823      }
22824      const {
22825        messageType,
22826        message
22827      } = event;
22828      this.log(`"${messageType}" message event for session "${keySession.sessionId}" message size: ${message.byteLength}`);
22829      if (messageType === 'license-request' || messageType === 'license-renewal') {
22830        this.renewLicense(context, message).catch(error => {
22831          if (licenseStatus.eventNames().length) {
22832            licenseStatus.emit('error', error);
22833          } else {
22834            this.handleError(error);
22835          }
22836        });
22837      } else if (messageType === 'license-release') {
22838        if (context.keySystem === KeySystems.FAIRPLAY) {
22839          this.updateKeySession(context, strToUtf8array('acknowledged')).then(() => this.removeSession(context)).catch(error => this.handleError(error));
22840        }
22841      } else {
22842        this.warn(`unhandled media key message type "${messageType}"`);
22843      }
22844    };
22845    const handleKeyStatus = (keyStatus, context) => {
22846      context.keyStatus = keyStatus;
22847      let keyError;
22848      if (keyStatus.startsWith('usable')) {
22849        licenseStatus.emit('resolved');
22850      } else if (keyStatus === 'internal-error' || keyStatus === 'output-restricted' || keyStatus === 'output-downscaled') {
22851        keyError = getKeyStatusError(keyStatus, context.decryptdata);
22852      } else if (keyStatus === 'expired') {
22853        keyError = new Error(`key expired (keyId: ${keyId})`);
22854      } else if (keyStatus === 'released') {
22855        keyError = new Error(`key released`);
22856      } else if (keyStatus === 'status-pending') ; else {
22857        this.warn(`unhandled key status change "${keyStatus}" (keyId: ${keyId})`);
22858      }
22859      if (keyError) {
22860        if (licenseStatus.eventNames().length) {
22861          licenseStatus.emit('error', keyError);
22862        } else {
22863          this.handleError(keyError);
22864        }
22865      }
22866    };
22867    const onkeystatuseschange = context._onkeystatuseschange = event => {
22868      const keySession = context.mediaKeysSession;
22869      if (!keySession) {
22870        licenseStatus.emit('error', new Error('invalid state'));
22871        return;
22872      }
22873      const keyStatuses = this.getKeyStatuses(context);
22874      const keyIds = Object.keys(keyStatuses);
22875
22876      // exit if all keys are status-pending
22877      if (!keyIds.some(id => keyStatuses[id] !== 'status-pending')) {
22878        return;
22879      }
22880
22881      // renew when a key status for a levelKey comes back expired
22882      if (keyStatuses[keyId] === 'expired') {
22883        // renew when a key status comes back expired
22884        this.log(`Expired key ${stringify(keyStatuses)} in key-session "${context.mediaKeysSession.sessionId}"`);
22885        this.renewKeySession(context);
22886        return;
22887      }
22888      let keyStatus = keyStatuses[keyId];
22889      if (keyStatus) {
22890        // handle status of current key
22891        handleKeyStatus(keyStatus, context);
22892      } else {
22893        var _context$keyStatusTim;
22894        // Timeout key-status
22895        const timeout = 1000;
22896        context.keyStatusTimeouts || (context.keyStatusTimeouts = {});
22897        (_context$keyStatusTim = context.keyStatusTimeouts)[keyId] || (_context$keyStatusTim[keyId] = self.setTimeout(() => {
22898          if (!context.mediaKeysSession || !this.mediaKeys) {
22899            return;
22900          }
22901
22902          // Find key status in another session if missing (PlayReady #7519 no key-status "single-key" setup with shared key)
22903          const sessionKeyStatus = this.getKeyStatus(context.decryptdata);
22904          if (sessionKeyStatus && sessionKeyStatus !== 'status-pending') {
22905            this.log(`No status for keyId ${keyId} in key-session "${context.mediaKeysSession.sessionId}". Using session key-status ${sessionKeyStatus} from other session.`);
22906            return handleKeyStatus(sessionKeyStatus, context);
22907          }
22908
22909          // Timeout key with internal-error
22910          this.log(`key status for ${keyId} in key-session "${context.mediaKeysSession.sessionId}" timed out after ${timeout}ms`);
22911          keyStatus = 'internal-error';
22912          handleKeyStatus(keyStatus, context);
22913        }, timeout));
22914        this.log(`No status for keyId ${keyId} (${stringify(keyStatuses)}).`);
22915      }
22916    };
22917    addEventListener(context.mediaKeysSession, 'message', onmessage);
22918    addEventListener(context.mediaKeysSession, 'keystatuseschange', onkeystatuseschange);
22919    const keyUsablePromise = new Promise((resolve, reject) => {
22920      licenseStatus.on('error', reject);
22921      licenseStatus.on('resolved', resolve);
22922    });
22923    return context.mediaKeysSession.generateRequest(initDataType, initData).then(() => {
22924      this.log(`Request generated for key-session "${context.mediaKeysSession.sessionId}" keyId: ${keyId} URI: ${keyUri}`);
22925    }).catch(error => {
22926      throw new EMEKeyError({
22927        type: ErrorTypes.KEY_SYSTEM_ERROR,
22928        details: ErrorDetails.KEY_SYSTEM_NO_SESSION,
22929        error,
22930        decryptdata: context.decryptdata,
22931        fatal: false
22932      }, `Error generating key-session request: ${error}`);
22933    }).then(() => keyUsablePromise).catch(error => {
22934      licenseStatus.removeAllListeners();
22935      return this.removeSession(context).then(() => {
22936        throw error;
22937      });
22938    }).then(() => {
22939      licenseStatus.removeAllListeners();
22940      return context;
22941    });
22942  }
22943  getKeyStatuses(mediaKeySessionContext) {
22944    const keyStatuses = {};
22945    mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach((status, keyId) => {
22946      // keyStatuses.forEach is not standard API so the callback value looks weird on xboxone
22947      // xboxone callback(keyId, status) so we need to exchange them
22948      if (typeof keyId === 'string' && typeof status === 'object') {
22949        const temp = keyId;
22950        keyId = status;
22951        status = temp;
22952      }
22953      const keyIdArray = 'buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) : new Uint8Array(keyId);
22954      if (mediaKeySessionContext.keySystem === KeySystems.PLAYREADY && keyIdArray.length === 16) {
22955        // On some devices, the key ID has already been converted for endianness.
22956        // In such cases, this key ID is the one we need to cache.
22957        const originKeyIdWithStatusChange = arrayToHex(keyIdArray);
22958        // Cache the original key IDs to ensure compatibility across all cases.
22959        keyStatuses[originKeyIdWithStatusChange] = status;
22960        changeEndianness(keyIdArray);
22961      }
22962      const keyIdWithStatusChange = arrayToHex(keyIdArray);
22963      // Add to banned keys to prevent playlist usage and license requests
22964      if (status === 'internal-error') {
22965        this.bannedKeyIds[keyIdWithStatusChange] = status;
22966      }
22967      this.log(`key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} key-session "${mediaKeySessionContext.mediaKeysSession.sessionId}"`);
22968      keyStatuses[keyIdWithStatusChange] = status;
22969    });
22970    return keyStatuses;
22971  }
22972  fetchServerCertificate(keySystem) {
22973    const config = this.config;
22974    const Loader = config.loader;
22975    const certLoader = new Loader(config);
22976    const url = this.getServerCertificateUrl(keySystem);
22977    if (!url) {
22978      return Promise.resolve();
22979    }
22980    this.log(`Fetching server certificate for "${keySystem}"`);
22981    return new Promise((resolve, reject) => {
22982      const loaderContext = {
22983        responseType: 'arraybuffer',
22984        url
22985      };
22986      const loadPolicy = config.certLoadPolicy.default;
22987      const loaderConfig = {
22988        loadPolicy,
22989        timeout: loadPolicy.maxLoadTimeMs,
22990        maxRetry: 0,
22991        retryDelay: 0,
22992        maxRetryDelay: 0
22993      };
22994      const loaderCallbacks = {
22995        onSuccess: (response, stats, context, networkDetails) => {
22996          resolve(response.data);
22997        },
22998        onError: (response, contex, networkDetails, stats) => {
22999          reject(new EMEKeyError({
23000            type: ErrorTypes.KEY_SYSTEM_ERROR,
23001            details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,
23002            fatal: true,
23003            networkDetails,
23004            response: _objectSpread2({
23005              url: loaderContext.url,
23006              data: undefined
23007            }, response)
23008          }, `"${keySystem}" certificate request failed (${url}). Status: ${response.code} (${response.text})`));
23009        },
23010        onTimeout: (stats, context, networkDetails) => {
23011          reject(new EMEKeyError({
23012            type: ErrorTypes.KEY_SYSTEM_ERROR,
23013            details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,
23014            fatal: true,
23015            networkDetails,
23016            response: {
23017              url: loaderContext.url,
23018              data: undefined
23019            }
23020          }, `"${keySystem}" certificate request timed out (${url})`));
23021        },
23022        onAbort: (stats, context, networkDetails) => {
23023          reject(new Error('aborted'));
23024        }
23025      };
23026      certLoader.load(loaderContext, loaderConfig, loaderCallbacks);
23027    });
23028  }
23029  setMediaKeysServerCertificate(mediaKeys, keySystem, cert) {
23030    return new Promise((resolve, reject) => {
23031      mediaKeys.setServerCertificate(cert).then(success => {
23032        this.log(`setServerCertificate ${success ? 'success' : 'not supported by CDM'} (${cert.byteLength}) on "${keySystem}"`);
23033        resolve(mediaKeys);
23034      }).catch(error => {
23035        reject(new EMEKeyError({
23036          type: ErrorTypes.KEY_SYSTEM_ERROR,
23037          details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED,
23038          error,
23039          fatal: true
23040        }, error.message));
23041      });
23042    });
23043  }
23044  renewLicense(context, keyMessage) {
23045    return this.requestLicense(context, new Uint8Array(keyMessage)).then(data => {
23046      return this.updateKeySession(context, new Uint8Array(data)).catch(error => {
23047        throw new EMEKeyError({
23048          type: ErrorTypes.KEY_SYSTEM_ERROR,
23049          details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED,
23050          decryptdata: context.decryptdata,
23051          error,
23052          fatal: false
23053        }, error.message);
23054      });
23055    });
23056  }
23057  unpackPlayReadyKeyMessage(xhr, licenseChallenge) {
23058    // On Edge, the raw license message is UTF-16-encoded XML.  We need
23059    // to unpack the Challenge element (base64-encoded string containing the
23060    // actual license request) and any HttpHeader elements (sent as request
23061    // headers).
23062    // For PlayReady CDMs, we need to dig the Challenge out of the XML.
23063    const xmlString = String.fromCharCode.apply(null, new Uint16Array(licenseChallenge.buffer));
23064    if (!xmlString.includes('PlayReadyKeyMessage')) {
23065      // This does not appear to be a wrapped message as on Edge.  Some
23066      // clients do not need this unwrapping, so we will assume this is one of
23067      // them.  Note that "xml" at this point probably looks like random
23068      // garbage, since we interpreted UTF-8 as UTF-16.
23069      xhr.setRequestHeader('Content-Type', 'text/xml; charset=utf-8');
23070      return licenseChallenge;
23071    }
23072    const keyMessageXml = new DOMParser().parseFromString(xmlString, 'application/xml');
23073    // Set request headers.
23074    const headers = keyMessageXml.querySelectorAll('HttpHeader');
23075    if (headers.length > 0) {
23076      let header;
23077      for (let i = 0, len = headers.length; i < len; i++) {
23078        var _header$querySelector, _header$querySelector2;
23079        header = headers[i];
23080        const name = (_header$querySelector = header.querySelector('name')) == null ? void 0 : _header$querySelector.textContent;
23081        const value = (_header$querySelector2 = header.querySelector('value')) == null ? void 0 : _header$querySelector2.textContent;
23082        if (name && value) {
23083          xhr.setRequestHeader(name, value);
23084        }
23085      }
23086    }
23087    const challengeElement = keyMessageXml.querySelector('Challenge');
23088    const challengeText = challengeElement == null ? void 0 : challengeElement.textContent;
23089    if (!challengeText) {
23090      throw new Error(`Cannot find <Challenge> in key message`);
23091    }
23092    return strToUtf8array(atob(challengeText));
23093  }
23094  setupLicenseXHR(xhr, url, keysListItem, licenseChallenge) {
23095    const licenseXhrSetup = this.config.licenseXhrSetup;
23096    if (!licenseXhrSetup) {
23097      xhr.open('POST', url, true);
23098      return Promise.resolve({
23099        xhr,
23100        licenseChallenge
23101      });
23102    }
23103    return Promise.resolve().then(() => {
23104      if (!keysListItem.decryptdata) {
23105        throw new Error('Key removed');
23106      }
23107      return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);
23108    }).catch(error => {
23109      if (!keysListItem.decryptdata) {
23110        // Key session removed. Cancel license request.
23111        throw error;
23112      }
23113      // let's try to open before running setup
23114      xhr.open('POST', url, true);
23115      return licenseXhrSetup.call(this.hls, xhr, url, keysListItem, licenseChallenge);
23116    }).then(licenseXhrSetupResult => {
23117      // if licenseXhrSetup did not yet call open, let's do it now
23118      if (!xhr.readyState) {
23119        xhr.open('POST', url, true);
23120      }
23121      const finalLicenseChallenge = licenseXhrSetupResult ? licenseXhrSetupResult : licenseChallenge;
23122      return {
23123        xhr,
23124        licenseChallenge: finalLicenseChallenge
23125      };
23126    });
23127  }
23128  requestLicense(keySessionContext, licenseChallenge) {
23129    const keyLoadPolicy = this.config.keyLoadPolicy.default;
23130    return new Promise((resolve, reject) => {
23131      const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
23132      this.log(`Sending license request to URL: ${url}`);
23133      const xhr = new XMLHttpRequest();
23134      xhr.responseType = 'arraybuffer';
23135      xhr.onreadystatechange = () => {
23136        if (!this.hls || !keySessionContext.mediaKeysSession) {
23137          return reject(new Error('invalid state'));
23138        }
23139        if (xhr.readyState === 4) {
23140          if (xhr.status === 200) {
23141            this._requestLicenseFailureCount = 0;
23142            let data = xhr.response;
23143            this.log(`License received ${data instanceof ArrayBuffer ? data.byteLength : data}`);
23144            const licenseResponseCallback = this.config.licenseResponseCallback;
23145            if (licenseResponseCallback) {
23146              try {
23147                data = licenseResponseCallback.call(this.hls, xhr, url, keySessionContext);
23148              } catch (error) {
23149                this.error(error);
23150              }
23151            }
23152            resolve(data);
23153          } else {
23154            const retryConfig = keyLoadPolicy.errorRetry;
23155            const maxNumRetry = retryConfig ? retryConfig.maxNumRetry : 0;
23156            this._requestLicenseFailureCount++;
23157            if (this._requestLicenseFailureCount > maxNumRetry || xhr.status >= 400 && xhr.status < 500) {
23158              reject(new EMEKeyError({
23159                type: ErrorTypes.KEY_SYSTEM_ERROR,
23160                details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED,
23161                decryptdata: keySessionContext.decryptdata,
23162                fatal: true,
23163                networkDetails: xhr,
23164                response: {
23165                  url,
23166                  data: undefined,
23167                  code: xhr.status,
23168                  text: xhr.statusText
23169                }
23170              }, `License Request XHR failed (${url}). Status: ${xhr.status} (${xhr.statusText})`));
23171            } else {
23172              const attemptsLeft = maxNumRetry - this._requestLicenseFailureCount + 1;
23173              this.warn(`Retrying license request, ${attemptsLeft} attempts left`);
23174              this.requestLicense(keySessionContext, licenseChallenge).then(resolve, reject);
23175            }
23176          }
23177        }
23178      };
23179      if (keySessionContext.licenseXhr && keySessionContext.licenseXhr.readyState !== XMLHttpRequest.DONE) {
23180        keySessionContext.licenseXhr.abort();
23181      }
23182      keySessionContext.licenseXhr = xhr;
23183      this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then(({
23184        xhr,
23185        licenseChallenge
23186      }) => {
23187        if (keySessionContext.keySystem == KeySystems.PLAYREADY) {
23188          licenseChallenge = this.unpackPlayReadyKeyMessage(xhr, licenseChallenge);
23189        }
23190        xhr.send(licenseChallenge);
23191      }).catch(reject);
23192    });
23193  }
23194  onDestroying() {
23195    this.unregisterListeners();
23196    this._clear();
23197  }
23198  onMediaAttached(event, data) {
23199    if (!this.config.emeEnabled) {
23200      return;
23201    }
23202    const media = data.media;
23203
23204    // keep reference of media
23205    this.media = media;
23206    addEventListener(media, 'encrypted', this.onMediaEncrypted);
23207    addEventListener(media, 'waitingforkey', this.onWaitingForKey);
23208    const mediaResolved = this.mediaResolved;
23209    if (mediaResolved) {
23210      mediaResolved();
23211    } else {
23212      this.mediaKeys = media.mediaKeys;
23213    }
23214  }
23215  onMediaDetached() {
23216    const media = this.media;
23217    if (media) {
23218      removeEventListener(media, 'encrypted', this.onMediaEncrypted);
23219      removeEventListener(media, 'waitingforkey', this.onWaitingForKey);
23220      this.media = null;
23221      this.mediaKeys = null;
23222    }
23223  }
23224  _clear() {
23225    var _media$setMediaKeys;
23226    this._requestLicenseFailureCount = 0;
23227    this.keyIdToKeySessionPromise = {};
23228    this.bannedKeyIds = {};
23229    const mediaResolved = this.mediaResolved;
23230    if (mediaResolved) {
23231      mediaResolved();
23232    }
23233    if (!this.mediaKeys && !this.mediaKeySessions.length) {
23234      return;
23235    }
23236    const media = this.media;
23237    const mediaKeysList = this.mediaKeySessions.slice();
23238    this.mediaKeySessions = [];
23239    this.mediaKeys = null;
23240    LevelKey.clearKeyUriToKeyIdMap();
23241
23242    // Close all sessions and remove media keys from the video element.
23243    const keySessionCount = mediaKeysList.length;
23244    EMEController.CDMCleanupPromise = Promise.all(mediaKeysList.map(mediaKeySessionContext => this.removeSession(mediaKeySessionContext)).concat((media == null || (_media$setMediaKeys = media.setMediaKeys(null)) == null ? void 0 : _media$setMediaKeys.catch(error => {
23245      this.log(`Could not clear media keys: ${error}`);
23246      if (!this.hls) return;
23247      this.hls.trigger(Events.ERROR, {
23248        type: ErrorTypes.OTHER_ERROR,
23249        details: ErrorDetails.KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR,
23250        fatal: false,
23251        error: new Error(`Could not clear media keys: ${error}`)
23252      });
23253    })) || Promise.resolve())).catch(error => {
23254      this.log(`Could not close sessions and clear media keys: ${error}`);
23255      if (!this.hls) return;
23256      this.hls.trigger(Events.ERROR, {
23257        type: ErrorTypes.OTHER_ERROR,
23258        details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR,
23259        fatal: false,
23260        error: new Error(`Could not close sessions and clear media keys: ${error}`)
23261      });
23262    }).then(() => {
23263      if (keySessionCount) {
23264        this.log('finished closing key sessions and clearing media keys');
23265      }
23266    });
23267  }
23268  onManifestLoading() {
23269    this._clear();
23270  }
23271  onManifestLoaded(event, {
23272    sessionKeys
23273  }) {
23274    if (!sessionKeys || !this.config.emeEnabled) {
23275      return;
23276    }
23277    if (!this.keyFormatPromise) {
23278      const keyFormats = sessionKeys.reduce((formats, sessionKey) => {
23279        if (formats.indexOf(sessionKey.keyFormat) === -1) {
23280          formats.push(sessionKey.keyFormat);
23281        }
23282        return formats;
23283      }, []);
23284      this.log(`Selecting key-system from session-keys ${keyFormats.join(', ')}`);
23285      this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
23286    }
23287  }
23288  removeSession(mediaKeySessionContext) {
23289    const {
23290      mediaKeysSession,
23291      licenseXhr,
23292      decryptdata
23293    } = mediaKeySessionContext;
23294    if (mediaKeysSession) {
23295      this.log(`Remove licenses and keys and close session "${mediaKeysSession.sessionId}" keyId: ${arrayToHex((decryptdata == null ? void 0 : decryptdata.keyId) || [])}`);
23296      if (mediaKeySessionContext._onmessage) {
23297        mediaKeysSession.removeEventListener('message', mediaKeySessionContext._onmessage);
23298        mediaKeySessionContext._onmessage = undefined;
23299      }
23300      if (mediaKeySessionContext._onkeystatuseschange) {
23301        mediaKeysSession.removeEventListener('keystatuseschange', mediaKeySessionContext._onkeystatuseschange);
23302        mediaKeySessionContext._onkeystatuseschange = undefined;
23303      }
23304      if (licenseXhr && licenseXhr.readyState !== XMLHttpRequest.DONE) {
23305        licenseXhr.abort();
23306      }
23307      mediaKeySessionContext.mediaKeysSession = mediaKeySessionContext.decryptdata = mediaKeySessionContext.licenseXhr = undefined;
23308      const index = this.mediaKeySessions.indexOf(mediaKeySessionContext);
23309      if (index > -1) {
23310        this.mediaKeySessions.splice(index, 1);
23311      }
23312      const {
23313        keyStatusTimeouts
23314      } = mediaKeySessionContext;
23315      if (keyStatusTimeouts) {
23316        Object.keys(keyStatusTimeouts).forEach(keyId => self.clearTimeout(keyStatusTimeouts[keyId]));
23317      }
23318      const {
23319        drmSystemOptions
23320      } = this.config;
23321      const removePromise = isPersistentSessionType(drmSystemOptions) ? new Promise((resolve, reject) => {
23322        self.setTimeout(() => reject(new Error(`MediaKeySession.remove() timeout`)), 8000);
23323        mediaKeysSession.remove().then(resolve).catch(reject);
23324      }) : Promise.resolve();
23325      return removePromise.catch(error => {
23326        this.log(`Could not remove session: ${error}`);
23327        if (!this.hls) return;
23328        this.hls.trigger(Events.ERROR, {
23329          type: ErrorTypes.OTHER_ERROR,
23330          details: ErrorDetails.KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR,
23331          fatal: false,
23332          error: new Error(`Could not remove session: ${error}`)
23333        });
23334      }).then(() => {
23335        return mediaKeysSession.close();
23336      }).catch(error => {
23337        this.log(`Could not close session: ${error}`);
23338        if (!this.hls) return;
23339        this.hls.trigger(Events.ERROR, {
23340          type: ErrorTypes.OTHER_ERROR,
23341          details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR,
23342          fatal: false,
23343          error: new Error(`Could not close session: ${error}`)
23344        });
23345      });
23346    }
23347    return Promise.resolve();
23348  }
23349}
23350EMEController.CDMCleanupPromise = void 0;
23351function getKeyIdString(decryptdata) {
23352  if (!decryptdata) {
23353    throw new Error('Could not read keyId of undefined decryptdata');
23354  }
23355  if (decryptdata.keyId === null) {
23356    throw new Error('keyId is null');
23357  }
23358  return arrayToHex(decryptdata.keyId);
23359}
23360function getKeyStatus(decryptdata, keyContext) {
23361  if (decryptdata.keyId && keyContext.mediaKeysSession.keyStatuses.has(decryptdata.keyId)) {
23362    return keyContext.mediaKeysSession.keyStatuses.get(decryptdata.keyId);
23363  }
23364  if (decryptdata.matches(keyContext.decryptdata)) {
23365    return keyContext.keyStatus;
23366  }
23367  return undefined;
23368}
23369class EMEKeyError extends Error {
23370  constructor(data, message) {
23371    super(message);
23372    this.data = void 0;
23373    data.error || (data.error = new Error(message));
23374    this.data = data;
23375    data.err = data.error;
23376  }
23377}
23378function getKeyStatusError(keyStatus, decryptdata) {
23379  const outputRestricted = keyStatus === 'output-restricted';
23380  const details = outputRestricted ? ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED : ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR;
23381  return new EMEKeyError({
23382    type: ErrorTypes.KEY_SYSTEM_ERROR,
23383    details,
23384    fatal: false,
23385    decryptdata
23386  }, outputRestricted ? 'HDCP level output restricted' : `key status changed to "${keyStatus}"`);
23387}
23388
23389class FPSController {
23390  constructor(hls) {
23391    this.hls = void 0;
23392    this.isVideoPlaybackQualityAvailable = false;
23393    this.timer = void 0;
23394    this.media = null;
23395    this.lastTime = void 0;
23396    this.lastDroppedFrames = 0;
23397    this.lastDecodedFrames = 0;
23398    // stream controller must be provided as a dependency!
23399    this.streamController = void 0;
23400    this.hls = hls;
23401    this.registerListeners();
23402  }
23403  setStreamController(streamController) {
23404    this.streamController = streamController;
23405  }
23406  registerListeners() {
23407    this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
23408    this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
23409  }
23410  unregisterListeners() {
23411    this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
23412    this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
23413  }
23414  destroy() {
23415    if (this.timer) {
23416      clearInterval(this.timer);
23417    }
23418    this.unregisterListeners();
23419    this.isVideoPlaybackQualityAvailable = false;
23420    this.media = null;
23421  }
23422  onMediaAttaching(event, data) {
23423    const config = this.hls.config;
23424    if (config.capLevelOnFPSDrop) {
23425      const media = data.media instanceof self.HTMLVideoElement ? data.media : null;
23426      this.media = media;
23427      if (media && typeof media.getVideoPlaybackQuality === 'function') {
23428        this.isVideoPlaybackQualityAvailable = true;
23429      }
23430      self.clearInterval(this.timer);
23431      this.timer = self.setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod);
23432    }
23433  }
23434  onMediaDetaching() {
23435    this.media = null;
23436  }
23437  checkFPS(video, decodedFrames, droppedFrames) {
23438    const currentTime = performance.now();
23439    if (decodedFrames) {
23440      if (this.lastTime) {
23441        const currentPeriod = currentTime - this.lastTime;
23442        const currentDropped = droppedFrames - this.lastDroppedFrames;
23443        const currentDecoded = decodedFrames - this.lastDecodedFrames;
23444        const droppedFPS = 1000 * currentDropped / currentPeriod;
23445        const hls = this.hls;
23446        hls.trigger(Events.FPS_DROP, {
23447          currentDropped: currentDropped,
23448          currentDecoded: currentDecoded,
23449          totalDroppedFrames: droppedFrames
23450        });
23451        if (droppedFPS > 0) {
23452          // hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
23453          if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
23454            let currentLevel = hls.currentLevel;
23455            hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
23456            if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
23457              currentLevel = currentLevel - 1;
23458              hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
23459                level: currentLevel,
23460                droppedLevel: hls.currentLevel
23461              });
23462              hls.autoLevelCapping = currentLevel;
23463              this.streamController.nextLevelSwitch();
23464            }
23465          }
23466        }
23467      }
23468      this.lastTime = currentTime;
23469      this.lastDroppedFrames = droppedFrames;
23470      this.lastDecodedFrames = decodedFrames;
23471    }
23472  }
23473  checkFPSInterval() {
23474    const video = this.media;
23475    if (video) {
23476      if (this.isVideoPlaybackQualityAvailable) {
23477        const videoPlaybackQuality = video.getVideoPlaybackQuality();
23478        this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames);
23479      } else {
23480        // HTMLVideoElement doesn't include the webkit types
23481        this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount);
23482      }
23483    }
23484  }
23485}
23486
23487function sendAddTrackEvent(track, videoEl) {
23488  let event;
23489  try {
23490    event = new Event('addtrack');
23491  } catch (err) {
23492    // for IE11
23493    event = document.createEvent('Event');
23494    event.initEvent('addtrack', false, false);
23495  }
23496  event.track = track;
23497  videoEl.dispatchEvent(event);
23498}
23499function addCueToTrack(track, cue) {
23500  // Sometimes there are cue overlaps on segmented vtts so the same
23501  // cue can appear more than once in different vtt files.
23502  // This avoid showing duplicated cues with same timecode and text.
23503  const mode = track.mode;
23504  if (mode === 'disabled') {
23505    track.mode = 'hidden';
23506  }
23507  if (track.cues && !track.cues.getCueById(cue.id)) {
23508    try {
23509      track.addCue(cue);
23510      if (!track.cues.getCueById(cue.id)) {
23511        throw new Error(`addCue is failed for: ${cue}`);
23512      }
23513    } catch (err) {
23514      logger.debug(`[texttrack-utils]: ${err}`);
23515      try {
23516        const textTrackCue = new self.TextTrackCue(cue.startTime, cue.endTime, cue.text);
23517        textTrackCue.id = cue.id;
23518        track.addCue(textTrackCue);
23519      } catch (err2) {
23520        logger.debug(`[texttrack-utils]: Legacy TextTrackCue fallback failed: ${err2}`);
23521      }
23522    }
23523  }
23524  if (mode === 'disabled') {
23525    track.mode = mode;
23526  }
23527}
23528function clearCurrentCues(track, enterHandler) {
23529  // When track.mode is disabled, track.cues will be null.
23530  // To guarantee the removal of cues, we need to temporarily
23531  // change the mode to hidden
23532  const mode = track.mode;
23533  if (mode === 'disabled') {
23534    track.mode = 'hidden';
23535  }
23536  if (track.cues) {
23537    for (let i = track.cues.length; i--;) {
23538      if (enterHandler) {
23539        track.cues[i].removeEventListener('enter', enterHandler);
23540      }
23541      track.removeCue(track.cues[i]);
23542    }
23543  }
23544  if (mode === 'disabled') {
23545    track.mode = mode;
23546  }
23547}
23548function removeCuesInRange(track, start, end, predicate) {
23549  const mode = track.mode;
23550  if (mode === 'disabled') {
23551    track.mode = 'hidden';
23552  }
23553  if (track.cues && track.cues.length > 0) {
23554    const cues = getCuesInRange(track.cues, start, end);
23555    for (let i = 0; i < cues.length; i++) {
23556      if (!predicate || predicate(cues[i])) {
23557        track.removeCue(cues[i]);
23558      }
23559    }
23560  }
23561  if (mode === 'disabled') {
23562    track.mode = mode;
23563  }
23564}
23565
23566// Find first cue starting at or after given time.
23567// Modified version of binary search O(log(n)).
23568function getFirstCueIndexFromTime(cues, time) {
23569  // If first cue starts at or after time, start there
23570  if (time <= cues[0].startTime) {
23571    return 0;
23572  }
23573  // If the last cue ends before time there is no overlap
23574  const len = cues.length - 1;
23575  if (time > cues[len].endTime) {
23576    return -1;
23577  }
23578  let left = 0;
23579  let right = len;
23580  let mid;
23581  while (left <= right) {
23582    mid = Math.floor((right + left) / 2);
23583    if (time < cues[mid].startTime) {
23584      right = mid - 1;
23585    } else if (time > cues[mid].startTime && left < len) {
23586      left = mid + 1;
23587    } else {
23588      // If it's not lower or higher, it must be equal.
23589      return mid;
23590    }
23591  }
23592  // At this point, left and right have swapped.
23593  // No direct match was found, left or right element must be the closest. Check which one has the smallest diff.
23594  return cues[left].startTime - time < time - cues[right].startTime ? left : right;
23595}
23596function getCuesInRange(cues, start, end) {
23597  const cuesFound = [];
23598  const firstCueInRange = getFirstCueIndexFromTime(cues, start);
23599  if (firstCueInRange > -1) {
23600    for (let i = firstCueInRange, len = cues.length; i < len; i++) {
23601      const cue = cues[i];
23602      if (cue.startTime >= start && cue.endTime <= end) {
23603        cuesFound.push(cue);
23604      } else if (cue.startTime > end) {
23605        return cuesFound;
23606      }
23607    }
23608  }
23609  return cuesFound;
23610}
23611function filterSubtitleTracks(textTrackList) {
23612  const tracks = [];
23613  for (let i = 0; i < textTrackList.length; i++) {
23614    const track = textTrackList[i];
23615    // Edge adds a track without a label; we don't want to use it
23616    if ((track.kind === 'subtitles' || track.kind === 'captions') && track.label) {
23617      tracks.push(textTrackList[i]);
23618    }
23619  }
23620  return tracks;
23621}
23622
23623class SubtitleTrackController extends BasePlaylistController {
23624  constructor(hls) {
23625    super(hls, 'subtitle-track-controller');
23626    this.media = null;
23627    this.tracks = [];
23628    this.groupIds = null;
23629    this.tracksInGroup = [];
23630    this.trackId = -1;
23631    this.currentTrack = null;
23632    this.selectDefaultTrack = true;
23633    this.queuedDefaultTrack = -1;
23634    this.useTextTrackPolling = false;
23635    this.subtitlePollingInterval = -1;
23636    this._subtitleDisplay = true;
23637    this.asyncPollTrackChange = () => this.pollTrackChange(0);
23638    this.onTextTracksChanged = () => {
23639      if (!this.useTextTrackPolling) {
23640        self.clearInterval(this.subtitlePollingInterval);
23641      }
23642      // Media is undefined when switching streams via loadSource()
23643      if (!this.media || !this.hls.config.renderTextTracksNatively) {
23644        return;
23645      }
23646      let textTrack = null;
23647      const tracks = filterSubtitleTracks(this.media.textTracks);
23648      for (let i = 0; i < tracks.length; i++) {
23649        if (tracks[i].mode === 'hidden') {
23650          // Do not break in case there is a following track with showing.
23651          textTrack = tracks[i];
23652        } else if (tracks[i].mode === 'showing') {
23653          textTrack = tracks[i];
23654          break;
23655        }
23656      }
23657
23658      // Find internal track index for TextTrack
23659      const trackId = this.findTrackForTextTrack(textTrack);
23660      if (this.subtitleTrack !== trackId) {
23661        this.setSubtitleTrack(trackId);
23662      }
23663    };
23664    this.registerListeners();
23665  }
23666  destroy() {
23667    this.unregisterListeners();
23668    this.tracks.length = 0;
23669    this.tracksInGroup.length = 0;
23670    this.currentTrack = null;
23671    // @ts-ignore
23672    this.onTextTracksChanged = this.asyncPollTrackChange = null;
23673    super.destroy();
23674  }
23675  get subtitleDisplay() {
23676    return this._subtitleDisplay;
23677  }
23678  set subtitleDisplay(value) {
23679    this._subtitleDisplay = value;
23680    if (this.trackId > -1) {
23681      this.toggleTrackModes();
23682    }
23683  }
23684  registerListeners() {
23685    const {
23686      hls
23687    } = this;
23688    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
23689    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
23690    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
23691    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
23692    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
23693    hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
23694    hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
23695    hls.on(Events.ERROR, this.onError, this);
23696  }
23697  unregisterListeners() {
23698    const {
23699      hls
23700    } = this;
23701    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
23702    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
23703    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
23704    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
23705    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
23706    hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this);
23707    hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
23708    hls.off(Events.ERROR, this.onError, this);
23709  }
23710
23711  // Listen for subtitle track change, then extract the current track ID.
23712  onMediaAttached(event, data) {
23713    this.media = data.media;
23714    if (!this.media) {
23715      return;
23716    }
23717    if (this.queuedDefaultTrack > -1) {
23718      this.subtitleTrack = this.queuedDefaultTrack;
23719      this.queuedDefaultTrack = -1;
23720    }
23721    this.useTextTrackPolling = !(this.media.textTracks && 'onchange' in this.media.textTracks);
23722    if (this.useTextTrackPolling) {
23723      this.pollTrackChange(500);
23724    } else {
23725      this.media.textTracks.addEventListener('change', this.asyncPollTrackChange);
23726    }
23727  }
23728  pollTrackChange(timeout) {
23729    self.clearInterval(this.subtitlePollingInterval);
23730    this.subtitlePollingInterval = self.setInterval(this.onTextTracksChanged, timeout);
23731  }
23732  onMediaDetaching(event, data) {
23733    const media = this.media;
23734    if (!media) {
23735      return;
23736    }
23737    const transferringMedia = !!data.transferMedia;
23738    self.clearInterval(this.subtitlePollingInterval);
23739    if (!this.useTextTrackPolling) {
23740      media.textTracks.removeEventListener('change', this.asyncPollTrackChange);
23741    }
23742    if (this.trackId > -1) {
23743      this.queuedDefaultTrack = this.trackId;
23744    }
23745
23746    // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled.
23747    this.subtitleTrack = -1;
23748    this.media = null;
23749    if (transferringMedia) {
23750      return;
23751    }
23752    const textTracks = filterSubtitleTracks(media.textTracks);
23753    // Clear loaded cues on media detachment from tracks
23754    textTracks.forEach(track => {
23755      clearCurrentCues(track);
23756    });
23757  }
23758  onManifestLoading() {
23759    this.tracks = [];
23760    this.groupIds = null;
23761    this.tracksInGroup = [];
23762    this.trackId = -1;
23763    this.currentTrack = null;
23764    this.selectDefaultTrack = true;
23765  }
23766
23767  // Fired whenever a new manifest is loaded.
23768  onManifestParsed(event, data) {
23769    this.tracks = data.subtitleTracks;
23770  }
23771  onSubtitleTrackLoaded(event, data) {
23772    const {
23773      id,
23774      groupId,
23775      details
23776    } = data;
23777    const trackInActiveGroup = this.tracksInGroup[id];
23778    if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) {
23779      this.warn(`Subtitle track with id:${id} and group:${groupId} not found in active group ${trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId}`);
23780      return;
23781    }
23782    const curDetails = trackInActiveGroup.details;
23783    trackInActiveGroup.details = data.details;
23784    this.log(`Subtitle track ${id} "${trackInActiveGroup.name}" lang:${trackInActiveGroup.lang} group:${groupId} loaded [${details.startSN}-${details.endSN}]`);
23785    if (id === this.trackId) {
23786      this.playlistLoaded(id, data, curDetails);
23787    }
23788  }
23789  onLevelLoading(event, data) {
23790    this.switchLevel(data.level);
23791  }
23792  onLevelSwitching(event, data) {
23793    this.switchLevel(data.level);
23794  }
23795  switchLevel(levelIndex) {
23796    const levelInfo = this.hls.levels[levelIndex];
23797    if (!levelInfo) {
23798      return;
23799    }
23800    const subtitleGroups = levelInfo.subtitleGroups || null;
23801    const currentGroups = this.groupIds;
23802    let currentTrack = this.currentTrack;
23803    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)) {
23804      this.groupIds = subtitleGroups;
23805      this.trackId = -1;
23806      this.currentTrack = null;
23807      const subtitleTracks = this.tracks.filter(track => !subtitleGroups || subtitleGroups.indexOf(track.groupId) !== -1);
23808      if (subtitleTracks.length) {
23809        // Disable selectDefaultTrack if there are no default tracks
23810        if (this.selectDefaultTrack && !subtitleTracks.some(track => track.default)) {
23811          this.selectDefaultTrack = false;
23812        }
23813        // track.id should match hls.audioTracks index
23814        subtitleTracks.forEach((track, i) => {
23815          track.id = i;
23816        });
23817      } else if (!currentTrack && !this.tracksInGroup.length) {
23818        // Do not dispatch SUBTITLE_TRACKS_UPDATED when there were and are no tracks
23819        return;
23820      }
23821      this.tracksInGroup = subtitleTracks;
23822
23823      // Find preferred track
23824      const subtitlePreference = this.hls.config.subtitlePreference;
23825      if (!currentTrack && subtitlePreference) {
23826        this.selectDefaultTrack = false;
23827        const groupIndex = findMatchingOption(subtitlePreference, subtitleTracks);
23828        if (groupIndex > -1) {
23829          currentTrack = subtitleTracks[groupIndex];
23830        } else {
23831          const allIndex = findMatchingOption(subtitlePreference, this.tracks);
23832          currentTrack = this.tracks[allIndex];
23833        }
23834      }
23835
23836      // Select initial track
23837      let trackId = this.findTrackId(currentTrack);
23838      if (trackId === -1 && currentTrack) {
23839        trackId = this.findTrackId(null);
23840      }
23841
23842      // Dispatch events and load track if needed
23843      const subtitleTracksUpdated = {
23844        subtitleTracks
23845      };
23846      this.log(`Updating subtitle tracks, ${subtitleTracks.length} track(s) found in "${subtitleGroups == null ? void 0 : subtitleGroups.join(',')}" group-id`);
23847      this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated);
23848      if (trackId !== -1 && this.trackId === -1) {
23849        this.setSubtitleTrack(trackId);
23850      }
23851    }
23852  }
23853  findTrackId(currentTrack) {
23854    const tracks = this.tracksInGroup;
23855    const selectDefault = this.selectDefaultTrack;
23856    for (let i = 0; i < tracks.length; i++) {
23857      const track = tracks[i];
23858      if (selectDefault && !track.default || !selectDefault && !currentTrack) {
23859        continue;
23860      }
23861      if (!currentTrack || matchesOption(track, currentTrack)) {
23862        return i;
23863      }
23864    }
23865    if (currentTrack) {
23866      for (let i = 0; i < tracks.length; i++) {
23867        const track = tracks[i];
23868        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) {
23869          return i;
23870        }
23871      }
23872      for (let i = 0; i < tracks.length; i++) {
23873        const track = tracks[i];
23874        if (mediaAttributesIdentical(currentTrack.attrs, track.attrs, ['LANGUAGE'])) {
23875          return i;
23876        }
23877      }
23878    }
23879    return -1;
23880  }
23881  findTrackForTextTrack(textTrack) {
23882    if (textTrack) {
23883      const tracks = this.tracksInGroup;
23884      for (let i = 0; i < tracks.length; i++) {
23885        const track = tracks[i];
23886        if (subtitleTrackMatchesTextTrack(track, textTrack)) {
23887          return i;
23888        }
23889      }
23890    }
23891    return -1;
23892  }
23893  onError(event, data) {
23894    if (data.fatal || !data.context) {
23895      return;
23896    }
23897    if (data.context.type === PlaylistContextType.SUBTITLE_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) {
23898      this.checkRetry(data);
23899    }
23900  }
23901  get allSubtitleTracks() {
23902    return this.tracks;
23903  }
23904
23905  /** get alternate subtitle tracks list from playlist **/
23906  get subtitleTracks() {
23907    return this.tracksInGroup;
23908  }
23909
23910  /** get/set index of the selected subtitle track (based on index in subtitle track lists) **/
23911  get subtitleTrack() {
23912    return this.trackId;
23913  }
23914  set subtitleTrack(newId) {
23915    this.selectDefaultTrack = false;
23916    this.setSubtitleTrack(newId);
23917  }
23918  setSubtitleOption(subtitleOption) {
23919    this.hls.config.subtitlePreference = subtitleOption;
23920    if (subtitleOption) {
23921      if (subtitleOption.id === -1) {
23922        this.setSubtitleTrack(-1);
23923        return null;
23924      }
23925      const allSubtitleTracks = this.allSubtitleTracks;
23926      this.selectDefaultTrack = false;
23927      if (allSubtitleTracks.length) {
23928        // First see if current option matches (no switch op)
23929        const currentTrack = this.currentTrack;
23930        if (currentTrack && matchesOption(subtitleOption, currentTrack)) {
23931          return currentTrack;
23932        }
23933        // Find option in current group
23934        const groupIndex = findMatchingOption(subtitleOption, this.tracksInGroup);
23935        if (groupIndex > -1) {
23936          const track = this.tracksInGroup[groupIndex];
23937          this.setSubtitleTrack(groupIndex);
23938          return track;
23939        } else if (currentTrack) {
23940          // If this is not the initial selection return null
23941          // option should have matched one in active group
23942          return null;
23943        } else {
23944          // Find the option in all tracks for initial selection
23945          const allIndex = findMatchingOption(subtitleOption, allSubtitleTracks);
23946          if (allIndex > -1) {
23947            return allSubtitleTracks[allIndex];
23948          }
23949        }
23950      }
23951    }
23952    return null;
23953  }
23954  loadPlaylist(hlsUrlParameters) {
23955    super.loadPlaylist();
23956    if (this.shouldLoadPlaylist(this.currentTrack)) {
23957      this.scheduleLoading(this.currentTrack, hlsUrlParameters);
23958    }
23959  }
23960  loadingPlaylist(currentTrack, hlsUrlParameters) {
23961    super.loadingPlaylist(currentTrack, hlsUrlParameters);
23962    const id = currentTrack.id;
23963    const groupId = currentTrack.groupId;
23964    const url = this.getUrlWithDirectives(currentTrack.url, hlsUrlParameters);
23965    const details = currentTrack.details;
23966    const age = details == null ? void 0 : details.age;
23967    this.log(`Loading subtitle ${id} "${currentTrack.name}" lang:${currentTrack.lang} group:${groupId}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== undefined ? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part : ''}${age && details.live ? ' age ' + age.toFixed(1) + (details.type ? ' ' + details.type || 
239670
vendor: 41,995 bytes, lines 23967-25193
23967 : '') : ''} ${url}`);
23968    this.hls.trigger(Events.SUBTITLE_TRACK_LOADING, {
23969      url,
23970      id,
23971      groupId,
23972      deliveryDirectives: hlsUrlParameters || null,
23973      track: currentTrack
23974    });
23975  }
23976
23977  /**
23978   * Disables the old subtitleTrack and sets current mode on the next subtitleTrack.
23979   * This operates on the DOM textTracks.
23980   * A value of -1 will disable all subtitle tracks.
23981   */
23982  toggleTrackModes() {
23983    const {
23984      media
23985    } = this;
23986    if (!media) {
23987      return;
23988    }
23989    const textTracks = filterSubtitleTracks(media.textTracks);
23990    const currentTrack = this.currentTrack;
23991    let nextTrack;
23992    if (currentTrack) {
23993      nextTrack = textTracks.filter(textTrack => subtitleTrackMatchesTextTrack(currentTrack, textTrack))[0];
23994      if (!nextTrack) {
23995        this.warn(`Unable to find subtitle TextTrack with name "${currentTrack.name}" and language "${currentTrack.lang}"`);
23996      }
23997    }
23998    [].slice.call(textTracks).forEach(track => {
23999      if (track.mode !== 'disabled' && track !== nextTrack) {
24000        track.mode = 'disabled';
24001      }
24002    });
24003    if (nextTrack) {
24004      const mode = this.subtitleDisplay ? 'showing' : 'hidden';
24005      if (nextTrack.mode !== mode) {
24006        nextTrack.mode = mode;
24007      }
24008    }
24009  }
24010
24011  /**
24012   * This method is responsible for validating the subtitle index and periodically reloading if live.
24013   * Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track.
24014   */
24015  setSubtitleTrack(newId) {
24016    const tracks = this.tracksInGroup;
24017
24018    // setting this.subtitleTrack will trigger internal logic
24019    // if media has not been attached yet, it will fail
24020    // we keep a reference to the default track id
24021    // and we'll set subtitleTrack when onMediaAttached is triggered
24022    if (!this.media) {
24023      this.queuedDefaultTrack = newId;
24024      return;
24025    }
24026
24027    // exit if track id as already set or invalid
24028    if (newId < -1 || newId >= tracks.length || !isFiniteNumber(newId)) {
24029      this.warn(`Invalid subtitle track id: ${newId}`);
24030      return;
24031    }
24032    this.selectDefaultTrack = false;
24033    const lastTrack = this.currentTrack;
24034    const track = tracks[newId] || null;
24035    this.trackId = newId;
24036    this.currentTrack = track;
24037    this.toggleTrackModes();
24038    if (!track) {
24039      // switch to -1
24040      this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {
24041        id: newId
24042      });
24043      return;
24044    }
24045    const trackLoaded = !!track.details && !track.details.live;
24046    if (newId === this.trackId && track === lastTrack && trackLoaded) {
24047      return;
24048    }
24049    this.log(`Switching to subtitle-track ${newId}` + (track ? ` "${track.name}" lang:${track.lang} group:${track.groupId}` : ''));
24050    const {
24051      id,
24052      groupId = '',
24053      name,
24054      type,
24055      url
24056    } = track;
24057    this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, {
24058      id,
24059      groupId,
24060      name,
24061      type,
24062      url
24063    });
24064    const hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details);
24065    this.loadPlaylist(hlsUrlParameters);
24066  }
24067}
24068
24069/**
24070 * Generate a random v4 UUID
24071 *
24072 * @returns A random v4 UUID
24073 *
24074 * @group Utils
24075 *
24076 * @beta
24077 */
24078function uuid() {
24079  try {
24080    return crypto.randomUUID();
24081  } catch (error) {
24082    try {
24083      const url = URL.createObjectURL(new Blob());
24084      const uuid = url.toString();
24085      URL.revokeObjectURL(url);
24086      return uuid.slice(uuid.lastIndexOf('/') + 1);
24087    } catch (error) {
24088      let dt = new Date().getTime();
24089      const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
24090        const r = (dt + Math.random() * 16) % 16 | 0;
24091        dt = Math.floor(dt / 16);
24092        return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
24093      });
24094      return uuid;
24095    }
24096  }
24097}
24098
24099// From https://github.com/darkskyapp/string-hash
24100function hash(text) {
24101  let hash = 5381;
24102  let i = text.length;
24103  while (i) {
24104    hash = hash * 33 ^ text.charCodeAt(--i);
24105  }
24106  return (hash >>> 0).toString();
24107}
24108
24109const ALIGNED_END_THRESHOLD_SECONDS = 0.025;
24110let TimelineOccupancy = /*#__PURE__*/function (TimelineOccupancy) {
24111  TimelineOccupancy[TimelineOccupancy["Point"] = 0] = "Point";
24112  TimelineOccupancy[TimelineOccupancy["Range"] = 1] = "Range";
24113  return TimelineOccupancy;
24114}({});
24115function generateAssetIdentifier(interstitial, uri, assetListIndex) {
24116  return `${interstitial.identifier}-${assetListIndex + 1}-${hash(uri)}`;
24117}
24118class InterstitialEvent {
24119  constructor(dateRange, base) {
24120    this.base = void 0;
24121    this._duration = null;
24122    this._timelineStart = null;
24123    this.appendInPlaceDisabled = void 0;
24124    this.appendInPlaceStarted = void 0;
24125    this.dateRange = void 0;
24126    this.hasPlayed = false;
24127    this.cumulativeDuration = 0;
24128    this.resumeOffset = NaN;
24129    this.playoutLimit = NaN;
24130    this.restrictions = {
24131      skip: false,
24132      jump: false
24133    };
24134    this.snapOptions = {
24135      out: false,
24136      in: false
24137    };
24138    this.assetList = [];
24139    this.assetListLoader = void 0;
24140    this.assetListResponse = null;
24141    this.resumeAnchor = void 0;
24142    this.error = void 0;
24143    this.resetOnResume = void 0;
24144    this.base = base;
24145    this.dateRange = dateRange;
24146    this.setDateRange(dateRange);
24147  }
24148  setDateRange(dateRange) {
24149    this.dateRange = dateRange;
24150    this.resumeOffset = dateRange.attr.optionalFloat('X-RESUME-OFFSET', this.resumeOffset);
24151    this.playoutLimit = dateRange.attr.optionalFloat('X-PLAYOUT-LIMIT', this.playoutLimit);
24152    this.restrictions = dateRange.attr.enumeratedStringList('X-RESTRICT', this.restrictions);
24153    this.snapOptions = dateRange.attr.enumeratedStringList('X-SNAP', this.snapOptions);
24154  }
24155  reset() {
24156    var _this$assetListLoader;
24157    this.appendInPlaceStarted = false;
24158    (_this$assetListLoader = this.assetListLoader) == null || _this$assetListLoader.destroy();
24159    this.assetListLoader = undefined;
24160    if (!this.supplementsPrimary) {
24161      this.assetListResponse = null;
24162      this.assetList = [];
24163      this._duration = null;
24164    }
24165    // `error?` is reset when seeking back over interstitial `startOffset`
24166    //  using `schedule.resetErrorsInRange(start, end)`.
24167  }
24168  isAssetPastPlayoutLimit(assetIndex) {
24169    var _this$assetList$asset;
24170    if (assetIndex > 0 && assetIndex >= this.assetList.length) {
24171      return true;
24172    }
24173    const playoutLimit = this.playoutLimit;
24174    if (assetIndex <= 0 || isNaN(playoutLimit)) {
24175      return false;
24176    }
24177    if (playoutLimit === 0) {
24178      return true;
24179    }
24180    const assetOffset = ((_this$assetList$asset = this.assetList[assetIndex]) == null ? void 0 : _this$assetList$asset.startOffset) || 0;
24181    return assetOffset > playoutLimit;
24182  }
24183  findAssetIndex(asset) {
24184    const index = this.assetList.indexOf(asset);
24185    return index;
24186  }
24187  get identifier() {
24188    return this.dateRange.id;
24189  }
24190  get startDate() {
24191    return this.dateRange.startDate;
24192  }
24193  get startTime() {
24194    // Primary media timeline start time
24195    const startTime = this.dateRange.startTime;
24196    if (this.snapOptions.out) {
24197      const frag = this.dateRange.tagAnchor;
24198      if (frag) {
24199        return getSnapToFragmentTime(startTime, frag);
24200      }
24201    }
24202    return startTime;
24203  }
24204  get startOffset() {
24205    return this.cue.pre ? 0 : this.startTime;
24206  }
24207  get startIsAligned() {
24208    if (this.startTime === 0 || this.snapOptions.out) {
24209      return true;
24210    }
24211    const frag = this.dateRange.tagAnchor;
24212    if (frag) {
24213      const startTime = this.dateRange.startTime;
24214      const snappedStart = getSnapToFragmentTime(startTime, frag);
24215      return startTime - snappedStart < 0.1;
24216    }
24217    return false;
24218  }
24219  get resumptionOffset() {
24220    const resumeOffset = this.resumeOffset;
24221    const offset = isFiniteNumber(resumeOffset) ? resumeOffset : this.duration;
24222    return this.cumulativeDuration + offset;
24223  }
24224  get resumeTime() {
24225    // Primary media timeline resumption time
24226    const resumeTime = this.startOffset + this.resumptionOffset;
24227    if (this.snapOptions.in) {
24228      const frag = this.resumeAnchor;
24229      if (frag) {
24230        return getSnapToFragmentTime(resumeTime, frag);
24231      }
24232    }
24233    return resumeTime;
24234  }
24235  get appendInPlace() {
24236    if (this.appendInPlaceStarted) {
24237      return true;
24238    }
24239    if (this.appendInPlaceDisabled) {
24240      return false;
24241    }
24242    if (!this.cue.once && !this.cue.pre &&
24243    // preroll starts at startPosition before startPosition is known (live)
24244    this.startIsAligned && (isNaN(this.playoutLimit) && isNaN(this.resumeOffset) || this.resumeOffset && this.duration && Math.abs(this.resumeOffset - this.duration) < ALIGNED_END_THRESHOLD_SECONDS)) {
24245      return true;
24246    }
24247    return false;
24248  }
24249  set appendInPlace(value) {
24250    if (this.appendInPlaceStarted) {
24251      this.resetOnResume = !value;
24252      return;
24253    }
24254    this.appendInPlaceDisabled = !value;
24255  }
24256
24257  // Extended timeline start time
24258  get timelineStart() {
24259    if (this._timelineStart !== null) {
24260      return this._timelineStart;
24261    }
24262    return this.startTime;
24263  }
24264  set timelineStart(value) {
24265    this._timelineStart = value;
24266  }
24267  get duration() {
24268    const playoutLimit = this.playoutLimit;
24269    let duration;
24270    if (this._duration !== null) {
24271      duration = this._duration;
24272    } else if (this.dateRange.duration) {
24273      duration = this.dateRange.duration;
24274    } else {
24275      duration = this.dateRange.plannedDuration || 0;
24276    }
24277    if (!isNaN(playoutLimit) && playoutLimit < duration) {
24278      duration = playoutLimit;
24279    }
24280    return duration;
24281  }
24282  set duration(value) {
24283    this._duration = value;
24284  }
24285  get cue() {
24286    return this.dateRange.cue;
24287  }
24288  get timelineOccupancy() {
24289    if (this.dateRange.attr['X-TIMELINE-OCCUPIES'] === 'RANGE') {
24290      return TimelineOccupancy.Range;
24291    }
24292    return TimelineOccupancy.Point;
24293  }
24294  get supplementsPrimary() {
24295    return this.dateRange.attr['X-TIMELINE-STYLE'] === 'PRIMARY';
24296  }
24297  get contentMayVary() {
24298    return this.dateRange.attr['X-CONTENT-MAY-VARY'] !== 'NO';
24299  }
24300  get assetUrl() {
24301    return this.dateRange.attr['X-ASSET-URI'];
24302  }
24303  get assetListUrl() {
24304    return this.dateRange.attr['X-ASSET-LIST'];
24305  }
24306  get baseUrl() {
24307    return this.base.url;
24308  }
24309  get assetListLoaded() {
24310    return this.assetList.length > 0 || this.assetListResponse !== null;
24311  }
24312  toString() {
24313    return eventToString(this);
24314  }
24315}
24316function getSnapToFragmentTime(time, frag) {
24317  return time - frag.start < frag.duration / 2 && !(Math.abs(time - (frag.start + frag.duration)) < ALIGNED_END_THRESHOLD_SECONDS) ? frag.start : frag.start + frag.duration;
24318}
24319function getInterstitialUrl(uri, sessionId, baseUrl) {
24320  const url = new self.URL(uri, baseUrl);
24321  if (url.protocol !== 'data:') {
24322    url.searchParams.set('_HLS_primary_id', sessionId);
24323  }
24324  return url;
24325}
24326function getNextAssetIndex(interstitial, assetListIndex) {
24327  while ((_interstitial$assetLi = interstitial.assetList[++assetListIndex]) != null && _interstitial$assetLi.error) {
24328    var _interstitial$assetLi;
24329  } /* no-op */
24330  return assetListIndex;
24331}
24332function eventToString(interstitial) {
24333  return `["${interstitial.identifier}" ${interstitial.cue.pre ? '<pre>' : interstitial.cue.post ? '<post>' : ''}${interstitial.timelineStart.toFixed(2)}-${interstitial.resumeTime.toFixed(2)}]`;
24334}
24335function eventAssetToString(asset) {
24336  const start = asset.timelineStart;
24337  const duration = asset.duration || 0;
24338  return `["${asset.identifier}" ${start.toFixed(2)}-${(start + duration).toFixed(2)}]`;
24339}
24340
24341class HlsAssetPlayer {
24342  constructor(HlsPlayerClass, userConfig, interstitial, assetItem) {
24343    this.hls = void 0;
24344    this.interstitial = void 0;
24345    this.assetItem = void 0;
24346    this.tracks = null;
24347    this.hasDetails = false;
24348    this.mediaAttached = null;
24349    this._currentTime = void 0;
24350    this._bufferedEosTime = void 0;
24351    this.checkPlayout = () => {
24352      if (this.reachedPlayout(this.currentTime) && this.hls) {
24353        this.hls.trigger(Events.PLAYOUT_LIMIT_REACHED, {});
24354      }
24355    };
24356    const hls = this.hls = new HlsPlayerClass(userConfig);
24357    this.interstitial = interstitial;
24358    this.assetItem = assetItem;
24359    const detailsLoaded = () => {
24360      this.hasDetails = true;
24361    };
24362    hls.once(Events.LEVEL_LOADED, detailsLoaded);
24363    hls.once(Events.AUDIO_TRACK_LOADED, detailsLoaded);
24364    hls.once(Events.SUBTITLE_TRACK_LOADED, detailsLoaded);
24365    hls.on(Events.MEDIA_ATTACHING, (name, {
24366      media
24367    }) => {
24368      this.removeMediaListeners();
24369      this.mediaAttached = media;
24370      const event = this.interstitial;
24371      if (event.playoutLimit) {
24372        media.addEventListener('timeupdate', this.checkPlayout);
24373        if (this.appendInPlace) {
24374          hls.on(Events.BUFFER_APPENDED, () => {
24375            const bufferedEnd = this.bufferedEnd;
24376            if (this.reachedPlayout(bufferedEnd)) {
24377              this._bufferedEosTime = bufferedEnd;
24378              hls.trigger(Events.BUFFERED_TO_END, undefined);
24379            }
24380          });
24381        }
24382      }
24383    });
24384  }
24385  get appendInPlace() {
24386    return this.interstitial.appendInPlace;
24387  }
24388  loadSource() {
24389    const hls = this.hls;
24390    if (!hls) {
24391      return;
24392    }
24393    if (!hls.url) {
24394      let uri = this.assetItem.uri;
24395      try {
24396        uri = getInterstitialUrl(uri, hls.config.primarySessionId || '').href;
24397      } catch (error) {
24398        // Ignore error parsing ASSET_URI or adding _HLS_primary_id to it. The
24399        // issue should surface as an INTERSTITIAL_ASSET_ERROR loading the asset.
24400      }
24401      hls.loadSource(uri);
24402    } else if (hls.levels.length && !hls.started) {
24403      hls.startLoad(-1, true);
24404    }
24405  }
24406  bufferedInPlaceToEnd(media) {
24407    var _this$hls;
24408    if (!this.appendInPlace) {
24409      return false;
24410    }
24411    if ((_this$hls = this.hls) != null && _this$hls.bufferedToEnd) {
24412      return true;
24413    }
24414    if (!media) {
24415      return false;
24416    }
24417    const duration = Math.min(this._bufferedEosTime || Infinity, this.duration);
24418    const start = this.timelineOffset;
24419    const bufferInfo = BufferHelper.bufferInfo(media, start, 0);
24420    const bufferedEnd = this.getAssetTime(bufferInfo.end);
24421    return bufferedEnd >= duration - 0.02;
24422  }
24423  reachedPlayout(time) {
24424    const interstitial = this.interstitial;
24425    const playoutLimit = interstitial.playoutLimit;
24426    return this.startOffset + time >= playoutLimit;
24427  }
24428  get destroyed() {
24429    var _this$hls2;
24430    return !((_this$hls2 = this.hls) != null && _this$hls2.userConfig);
24431  }
24432  get assetId() {
24433    return this.assetItem.identifier;
24434  }
24435  get interstitialId() {
24436    return this.assetItem.parentIdentifier;
24437  }
24438  get media() {
24439    var _this$hls3;
24440    return ((_this$hls3 = this.hls) == null ? void 0 : _this$hls3.media) || null;
24441  }
24442  get bufferedEnd() {
24443    const media = this.media || this.mediaAttached;
24444    if (!media) {
24445      if (this._bufferedEosTime) {
24446        return this._bufferedEosTime;
24447      }
24448      return this.currentTime;
24449    }
24450    const bufferInfo = BufferHelper.bufferInfo(media, media.currentTime, 0.001);
24451    return this.getAssetTime(bufferInfo.end);
24452  }
24453  get currentTime() {
24454    const media = this.media || this.mediaAttached;
24455    if (!media) {
24456      return this._currentTime || 0;
24457    }
24458    return this.getAssetTime(media.currentTime);
24459  }
24460  get duration() {
24461    const duration = this.assetItem.duration;
24462    if (!duration) {
24463      return 0;
24464    }
24465    const playoutLimit = this.interstitial.playoutLimit;
24466    if (playoutLimit) {
24467      const assetPlayout = playoutLimit - this.startOffset;
24468      if (assetPlayout > 0 && assetPlayout < duration) {
24469        return assetPlayout;
24470      }
24471    }
24472    return duration;
24473  }
24474  get remaining() {
24475    const duration = this.duration;
24476    if (!duration) {
24477      return 0;
24478    }
24479    return Math.max(0, duration - this.currentTime);
24480  }
24481  get startOffset() {
24482    return this.assetItem.startOffset;
24483  }
24484  get timelineOffset() {
24485    var _this$hls4;
24486    return ((_this$hls4 = this.hls) == null ? void 0 : _this$hls4.config.timelineOffset) || 0;
24487  }
24488  set timelineOffset(value) {
24489    const timelineOffset = this.timelineOffset;
24490    if (value !== timelineOffset) {
24491      const diff = value - timelineOffset;
24492      if (Math.abs(diff) > 1 / 90000 && this.hls) {
24493        if (this.hasDetails) {
24494          throw new Error(`Cannot set timelineOffset after playlists are loaded`);
24495        }
24496        this.hls.config.timelineOffset = value;
24497      }
24498    }
24499  }
24500  getAssetTime(time) {
24501    const timelineOffset = this.timelineOffset;
24502    const duration = this.duration;
24503    return Math.min(Math.max(0, time - timelineOffset), duration);
24504  }
24505  removeMediaListeners() {
24506    const media = this.mediaAttached;
24507    if (media) {
24508      this._currentTime = media.currentTime;
24509      this.bufferSnapShot();
24510      media.removeEventListener('timeupdate', this.checkPlayout);
24511    }
24512  }
24513  bufferSnapShot() {
24514    if (this.mediaAttached) {
24515      var _this$hls5;
24516      if ((_this$hls5 = this.hls) != null && _this$hls5.bufferedToEnd) {
24517        this._bufferedEosTime = this.bufferedEnd;
24518      }
24519    }
24520  }
24521  destroy() {
24522    this.removeMediaListeners();
24523    if (this.hls) {
24524      this.hls.destroy();
24525    }
24526    this.hls = null;
24527    // @ts-ignore
24528    this.tracks = this.mediaAttached = this.checkPlayout = null;
24529  }
24530  attachMedia(data) {
24531    var _this$hls6;
24532    this.loadSource();
24533    (_this$hls6 = this.hls) == null || _this$hls6.attachMedia(data);
24534  }
24535  detachMedia() {
24536    var _this$hls7;
24537    this.removeMediaListeners();
24538    this.mediaAttached = null;
24539    (_this$hls7 = this.hls) == null || _this$hls7.detachMedia();
24540  }
24541  resumeBuffering() {
24542    var _this$hls8;
24543    (_this$hls8 = this.hls) == null || _this$hls8.resumeBuffering();
24544  }
24545  pauseBuffering() {
24546    var _this$hls9;
24547    (_this$hls9 = this.hls) == null || _this$hls9.pauseBuffering();
24548  }
24549  transferMedia() {
24550    var _this$hls0;
24551    this.bufferSnapShot();
24552    return ((_this$hls0 = this.hls) == null ? void 0 : _this$hls0.transferMedia()) || null;
24553  }
24554  resetDetails() {
24555    const hls = this.hls;
24556    if (hls && this.hasDetails) {
24557      hls.stopLoad();
24558      const deleteDetails = obj => delete obj.details;
24559      hls.levels.forEach(deleteDetails);
24560      hls.allAudioTracks.forEach(deleteDetails);
24561      hls.allSubtitleTracks.forEach(deleteDetails);
24562      this.hasDetails = false;
24563    }
24564  }
24565  on(event, listener, context) {
24566    var _this$hls1;
24567    (_this$hls1 = this.hls) == null || _this$hls1.on(event, listener);
24568  }
24569  once(event, listener, context) {
24570    var _this$hls10;
24571    (_this$hls10 = this.hls) == null || _this$hls10.once(event, listener);
24572  }
24573  off(event, listener, context) {
24574    var _this$hls11;
24575    (_this$hls11 = this.hls) == null || _this$hls11.off(event, listener);
24576  }
24577  toString() {
24578    var _this$hls12;
24579    return `HlsAssetPlayer: ${eventAssetToString(this.assetItem)} ${(_this$hls12 = this.hls) == null ? void 0 : _this$hls12.sessionId} ${this.appendInPlace ? 'append-in-place' : ''}`;
24580  }
24581}
24582
24583const ABUTTING_THRESHOLD_SECONDS = 0.033;
24584class InterstitialsSchedule extends Logger {
24585  constructor(onScheduleUpdate, logger) {
24586    super('interstitials-sched', logger);
24587    this.onScheduleUpdate = void 0;
24588    this.eventMap = {};
24589    this.events = null;
24590    this.items = null;
24591    this.durations = {
24592      primary: 0,
24593      playout: 0,
24594      integrated: 0
24595    };
24596    this.onScheduleUpdate = onScheduleUpdate;
24597  }
24598  destroy() {
24599    this.reset();
24600    // @ts-ignore
24601    this.onScheduleUpdate = null;
24602  }
24603  reset() {
24604    this.eventMap = {};
24605    this.setDurations(0, 0, 0);
24606    if (this.events) {
24607      this.events.forEach(interstitial => interstitial.reset());
24608    }
24609    this.events = this.items = null;
24610  }
24611  resetErrorsInRange(start, end) {
24612    if (this.events) {
24613      return this.events.reduce((count, interstitial) => {
24614        if (start <= interstitial.startOffset && end > interstitial.startOffset) {
24615          delete interstitial.error;
24616          return count + 1;
24617        }
24618        return count;
24619      }, 0);
24620    }
24621    return 0;
24622  }
24623  get duration() {
24624    const items = this.items;
24625    return items ? items[items.length - 1].end : 0;
24626  }
24627  get length() {
24628    return this.items ? this.items.length : 0;
24629  }
24630  getEvent(identifier) {
24631    return identifier ? this.eventMap[identifier] || null : null;
24632  }
24633  hasEvent(identifier) {
24634    return identifier in this.eventMap;
24635  }
24636  findItemIndex(item, time) {
24637    if (item.event) {
24638      // Find Event Item
24639      return this.findEventIndex(item.event.identifier);
24640    }
24641    // Find Primary Item
24642    let index = -1;
24643    if (item.nextEvent) {
24644      index = this.findEventIndex(item.nextEvent.identifier) - 1;
24645    } else if (item.previousEvent) {
24646      index = this.findEventIndex(item.previousEvent.identifier) + 1;
24647    }
24648    const items = this.items;
24649    if (items) {
24650      if (!items[index]) {
24651        if (time === undefined) {
24652          time = item.start;
24653        }
24654        index = this.findItemIndexAtTime(time);
24655      }
24656      // Only return index of a Primary Item
24657      while (index >= 0 && (_items$index = items[index]) != null && _items$index.event) {
24658        var _items$index;
24659        // If index found is an interstitial it is not a valid result as it should have been matched up top
24660        // decrement until result is negative (not found) or a primary segment
24661        index--;
24662      }
24663    }
24664    return index;
24665  }
24666  findItemIndexAtTime(timelinePos, timelineType) {
24667    const items = this.items;
24668    if (items) {
24669      for (let i = 0; i < items.length; i++) {
24670        let timeRange = items[i];
24671        if (timelineType && timelineType !== 'primary') {
24672          timeRange = timeRange[timelineType];
24673        }
24674        if (timelinePos === timeRange.start || timelinePos > timeRange.start && timelinePos < timeRange.end) {
24675          return i;
24676        }
24677      }
24678    }
24679    return -1;
24680  }
24681  findJumpRestrictedIndex(startIndex, endIndex) {
24682    const items = this.items;
24683    if (items) {
24684      for (let i = startIndex; i <= endIndex; i++) {
24685        if (!items[i]) {
24686          break;
24687        }
24688        const event = items[i].event;
24689        if (event != null && event.restrictions.jump && !event.appendInPlace) {
24690          return i;
24691        }
24692      }
24693    }
24694    return -1;
24695  }
24696  findEventIndex(identifier) {
24697    const items = this.items;
24698    if (items) {
24699      for (let i = items.length; i--;) {
24700        var _items$i$event;
24701        if (((_items$i$event = items[i].event) == null ? void 0 : _items$i$event.identifier) === identifier) {
24702          return i;
24703        }
24704      }
24705    }
24706    return -1;
24707  }
24708  findAssetIndex(event, timelinePos) {
24709    const assetList = event.assetList;
24710    const length = assetList.length;
24711    if (length > 1) {
24712      for (let i = 0; i < length; i++) {
24713        const asset = assetList[i];
24714        if (!asset.error) {
24715          const timelineStart = asset.timelineStart;
24716          if (timelinePos === timelineStart || timelinePos > timelineStart && (timelinePos < timelineStart + (asset.duration || 0) || i === length - 1)) {
24717            return i;
24718          }
24719        }
24720      }
24721    }
24722    return 0;
24723  }
24724  get assetIdAtEnd() {
24725    var _this$items;
24726    const interstitialAtEnd = (_this$items = this.items) == null || (_this$items = _this$items[this.length - 1]) == null ? void 0 : _this$items.event;
24727    if (interstitialAtEnd) {
24728      const assetList = interstitialAtEnd.assetList;
24729      const assetAtEnd = assetList[assetList.length - 1];
24730      if (assetAtEnd) {
24731        return assetAtEnd.identifier;
24732      }
24733    }
24734    return null;
24735  }
24736  parseInterstitialDateRanges(mediaSelection, enableAppendInPlace) {
24737    const details = mediaSelection.main.details;
24738    const {
24739      dateRanges
24740    } = details;
24741    const previousInterstitialEvents = this.events;
24742    const interstitialEvents = this.parseDateRanges(dateRanges, {
24743      url: details.url
24744    }, enableAppendInPlace);
24745    const ids = Object.keys(dateRanges);
24746    const removedInterstitials = previousInterstitialEvents ? previousInterstitialEvents.filter(event => !ids.includes(event.identifier)) : [];
24747    if (interstitialEvents.length) {
24748      // pre-rolls, post-rolls, and events with the same start time are played in playlist tag order
24749      // all other events are ordered by start time
24750      interstitialEvents.sort((a, b) => {
24751        const aPre = a.cue.pre;
24752        const aPost = a.cue.post;
24753        const bPre = b.cue.pre;
24754        const bPost = b.cue.post;
24755        if (aPre && !bPre) {
24756          return -1;
24757        }
24758        if (bPre && !aPre) {
24759          return 1;
24760        }
24761        if (aPost && !bPost) {
24762          return 1;
24763        }
24764        if (bPost && !aPost) {
24765          return -1;
24766        }
24767        if (!aPre && !bPre && !aPost && !bPost) {
24768          const startA = a.startTime;
24769          const startB = b.startTime;
24770          if (startA !== startB) {
24771            return startA - startB;
24772          }
24773        }
24774        return a.dateRange.tagOrder - b.dateRange.tagOrder;
24775      });
24776    }
24777    this.events = interstitialEvents;
24778
24779    // Clear removed DateRanges from buffered list (kills playback of active Interstitials)
24780    removedInterstitials.forEach(interstitial => {
24781      this.removeEvent(interstitial);
24782    });
24783    this.updateSchedule(mediaSelection, removedInterstitials);
24784  }
24785  updateSchedule(mediaSelection, removedInterstitials = [], forceUpdate = false) {
24786    const events = this.events || [];
24787    if (events.length || removedInterstitials.length || this.length < 2) {
24788      const currentItems = this.items;
24789      const updatedItems = this.parseSchedule(events, mediaSelection);
24790      const updated = forceUpdate || removedInterstitials.length || (currentItems == null ? void 0 : currentItems.length) !== updatedItems.length || updatedItems.some((item, i) => {
24791        return Math.abs(item.playout.start - currentItems[i].playout.start) > 0.005 || Math.abs(item.playout.end - currentItems[i].playout.end) > 0.005;
24792      });
24793      if (updated) {
24794        this.items = updatedItems;
24795        // call interstitials-controller onScheduleUpdated()
24796        this.onScheduleUpdate(removedInterstitials, currentItems);
24797      }
24798    }
24799  }
24800  parseDateRanges(dateRanges, baseData, enableAppendInPlace) {
24801    const interstitialEvents = [];
24802    const ids = Object.keys(dateRanges);
24803    for (let i = 0; i < ids.length; i++) {
24804      const id = ids[i];
24805      const dateRange = dateRanges[id];
24806      if (dateRange.isInterstitial) {
24807        let interstitial = this.eventMap[id];
24808        if (interstitial) {
24809          // Update InterstitialEvent already parsed and mapped
24810          // This retains already loaded duration and loaded asset list info
24811          interstitial.setDateRange(dateRange);
24812        } else {
24813          interstitial = new InterstitialEvent(dateRange, baseData);
24814          this.eventMap[id] = interstitial;
24815          if (enableAppendInPlace === false) {
24816            interstitial.appendInPlace = enableAppendInPlace;
24817          }
24818        }
24819        interstitialEvents.push(interstitial);
24820      }
24821    }
24822    return interstitialEvents;
24823  }
24824  parseSchedule(interstitialEvents, mediaSelection) {
24825    const schedule = [];
24826    const details = mediaSelection.main.details;
24827    const primaryDuration = details.live ? Infinity : details.edge;
24828    let playoutDuration = 0;
24829
24830    // Filter events that have errored from the schedule (Primary fallback)
24831    interstitialEvents = interstitialEvents.filter(event => !event.error && !(event.cue.once && event.hasPlayed));
24832    if (interstitialEvents.length) {
24833      // Update Schedule
24834      this.resolveOffsets(interstitialEvents, mediaSelection);
24835
24836      // Populate Schedule with Interstitial Event and Primary Segment Items
24837      let primaryPosition = 0;
24838      let integratedTime = 0;
24839      interstitialEvents.forEach((interstitial, i) => {
24840        const preroll = interstitial.cue.pre;
24841        const postroll = interstitial.cue.post;
24842        const previousEvent = interstitialEvents[i - 1] || null;
24843        const appendInPlace = interstitial.appendInPlace;
24844        const eventStart = postroll ? primaryDuration : interstitial.startOffset;
24845        const interstitialDuration = interstitial.duration;
24846        const timelineDuration = interstitial.timelineOccupancy === TimelineOccupancy.Range ? interstitialDuration : 0;
24847        const resumptionOffset = interstitial.resumptionOffset;
24848        const inSameStartTimeSequence = (previousEvent == null ? void 0 : previousEvent.startTime) === eventStart;
24849        const start = eventStart + interstitial.cumulativeDuration;
24850        let end = appendInPlace ? start + interstitialDuration : eventStart + resumptionOffset;
24851        if (preroll || !postroll && eventStart <= 0) {
24852          // preroll or in-progress midroll
24853          const integratedStart = integratedTime;
24854          integratedTime += timelineDuration;
24855          interstitial.timelineStart = start;
24856          const playoutStart = playoutDuration;
24857          playoutDuration += interstitialDuration;
24858          schedule.push({
24859            event: interstitial,
24860            start,
24861            end,
24862            playout: {
24863              start: playoutStart,
24864              end: playoutDuration
24865            },
24866            integrated: {
24867              start: integratedStart,
24868              end: integratedTime
24869            }
24870          });
24871        } else if (eventStart <= primaryDuration) {
24872          if (!inSameStartTimeSequence) {
24873            const segmentDuration = eventStart - primaryPosition;
24874            // Do not schedule a primary segment if interstitials are abutting by less than ABUTTING_THRESHOLD_SECONDS
24875            if (segmentDuration > ABUTTING_THRESHOLD_SECONDS) {
24876              // primary segment
24877              const timelineStart = primaryPosition;
24878              const _integratedStart = integratedTime;
24879              integratedTime += segmentDuration;
24880              const _playoutStart = playoutDuration;
24881              playoutDuration += segmentDuration;
24882              const primarySegment = {
24883                previousEvent: interstitialEvents[i - 1] || null,
24884                nextEvent: interstitial,
24885                start: timelineStart,
24886                end: timelineStart + segmentDuration,
24887                playout: {
24888                  start: _playoutStart,
24889                  end: playoutDuration
24890                },
24891                integrated: {
24892                  start: _integratedStart,
24893                  end: integratedTime
24894                }
24895              };
24896              schedule.push(primarySegment);
24897            } else if (segmentDuration > 0 && previousEvent) {
24898              // Add previous event `resumeTime` (based on duration or resumeOffset) so that it ends aligned with this one
24899              previousEvent.cumulativeDuration += segmentDuration;
24900              schedule[schedule.length - 1].end = eventStart;
24901            }
24902          }
24903          // midroll / postroll
24904          if (postroll) {
24905            end = start;
24906          }
24907          interstitial.timelineStart = start;
24908          const integratedStart = integratedTime;
24909          integratedTime += timelineDuration;
24910          const playoutStart = playoutDuration;
24911          playoutDuration += interstitialDuration;
24912          schedule.push({
24913            event: interstitial,
24914            start,
24915            end,
24916            playout: {
24917              start: playoutStart,
24918              end: playoutDuration
24919            },
24920            integrated: {
24921              start: integratedStart,
24922              end: integratedTime
24923            }
24924          });
24925        } else {
24926          // Interstitial starts after end of primary VOD - not included in schedule
24927          return;
24928        }
24929        const resumeTime = interstitial.resumeTime;
24930        if (postroll || resumeTime > primaryDuration) {
24931          primaryPosition = primaryDuration;
24932        } else {
24933          primaryPosition = resumeTime;
24934        }
24935      });
24936      if (primaryPosition < primaryDuration) {
24937        var _schedule;
24938        // last primary segment
24939        const timelineStart = primaryPosition;
24940        const integratedStart = integratedTime;
24941        const segmentDuration = primaryDuration - primaryPosition;
24942        integratedTime += segmentDuration;
24943        const playoutStart = playoutDuration;
24944        playoutDuration += segmentDuration;
24945        schedule.push({
24946          previousEvent: ((_schedule = schedule[schedule.length - 1]) == null ? void 0 : _schedule.event) || null,
24947          nextEvent: null,
24948          start: primaryPosition,
24949          end: timelineStart + segmentDuration,
24950          playout: {
24951            start: playoutStart,
24952            end: playoutDuration
24953          },
24954          integrated: {
24955            start: integratedStart,
24956            end: integratedTime
24957          }
24958        });
24959      }
24960      this.setDurations(primaryDuration, playoutDuration, integratedTime);
24961    } else {
24962      // no interstials - schedule is one primary segment
24963      const start = 0;
24964      schedule.push({
24965        previousEvent: null,
24966        nextEvent: null,
24967        start,
24968        end: primaryDuration,
24969        playout: {
24970          start,
24971          end: primaryDuration
24972        },
24973        integrated: {
24974          start,
24975          end: primaryDuration
24976        }
24977      });
24978      this.setDurations(primaryDuration, primaryDuration, primaryDuration);
24979    }
24980    return schedule;
24981  }
24982  setDurations(primary, playout, integrated) {
24983    this.durations = {
24984      primary,
24985      playout,
24986      integrated
24987    };
24988  }
24989  resolveOffsets(interstitialEvents, mediaSelection) {
24990    const details = mediaSelection.main.details;
24991    const primaryDuration = details.live ? Infinity : details.edge;
24992
24993    // First resolve cumulative resumption offsets for Interstitials that start at the same DateTime
24994    let cumulativeDuration = 0;
24995    let lastScheduledStart = -1;
24996    interstitialEvents.forEach((interstitial, i) => {
24997      const preroll = interstitial.cue.pre;
24998      const postroll = interstitial.cue.post;
24999      const eventStart = preroll ? 0 : postroll ? primaryDuration : interstitial.startTime;
25000      this.updateAssetDurations(interstitial);
25001
25002      // X-RESUME-OFFSET values of interstitials scheduled at the same time are cumulative
25003      const inSameStartTimeSequence = lastScheduledStart === eventStart;
25004      if (inSameStartTimeSequence) {
25005        interstitial.cumulativeDuration = cumulativeDuration;
25006      } else {
25007        cumulativeDuration = 0;
25008        lastScheduledStart = eventStart;
25009      }
25010      if (!postroll && interstitial.snapOptions.in) {
25011        // FIXME: Include audio playlist in snapping
25012        interstitial.resumeAnchor = findFragmentByPTS(null, details.fragments, interstitial.startOffset + interstitial.resumptionOffset, 0, 0) || undefined;
25013      }
25014      // Check if primary fragments align with resumption offset and disable appendInPlace if they do not
25015      if (interstitial.appendInPlace && !interstitial.appendInPlaceStarted) {
25016        const alignedSegmentStart = this.primaryCanResumeInPlaceAt(interstitial, mediaSelection);
25017        if (!alignedSegmentStart) {
25018          interstitial.appendInPlace = false;
25019        }
25020      }
25021      if (!interstitial.appendInPlace && i + 1 < interstitialEvents.length) {
25022        // abutting Interstitials must use the same MediaSource strategy, this applies to all whether or not they are back to back:
25023        const timeBetween = interstitialEvents[i + 1].startTime - interstitialEvents[i].resumeTime;
25024        if (timeBetween < ABUTTING_THRESHOLD_SECONDS) {
25025          interstitialEvents[i + 1].appendInPlace = false;
25026          if (interstitialEvents[i + 1].appendInPlace) {
25027            this.warn(`Could not change append strategy for abutting event ${interstitial}`);
25028          }
25029        }
25030      }
25031      // Update cumulativeDuration for next abutting interstitial with the same start date
25032      const resumeOffset = isFiniteNumber(interstitial.resumeOffset) ? interstitial.resumeOffset : interstitial.duration;
25033      cumulativeDuration += resumeOffset;
25034    });
25035  }
25036  primaryCanResumeInPlaceAt(interstitial, mediaSelection) {
25037    const resumeTime = interstitial.resumeTime;
25038    const resumesInPlaceAt = interstitial.startTime + interstitial.resumptionOffset;
25039    if (Math.abs(resumeTime - resumesInPlaceAt) > ALIGNED_END_THRESHOLD_SECONDS) {
25040      this.log(`"${interstitial.identifier}" resumption ${resumeTime} not aligned with estimated timeline end ${resumesInPlaceAt}`);
25041      return false;
25042    }
25043    const playlists = Object.keys(mediaSelection);
25044    return !playlists.some(playlistType => {
25045      const details = mediaSelection[playlistType].details;
25046      const playlistEnd = details.edge;
25047      if (resumeTime >= playlistEnd) {
25048        // Live playback - resumption segments are not yet available
25049        this.log(`"${interstitial.identifier}" resumption ${resumeTime} past ${playlistType} playlist end ${playlistEnd}`);
25050        // Assume alignment is possible (or reset can take place)
25051        return false;
25052      }
25053      const startFragment = findFragmentByPTS(null, details.fragments, resumeTime);
25054      if (!startFragment) {
25055        this.log(`"${interstitial.identifier}" resumption ${resumeTime} does not align with any fragments in ${playlistType} playlist (${details.fragStart}-${details.fragmentEnd})`);
25056        return true;
25057      }
25058      const allowance = playlistType === 'audio' ? 0.175 : 0;
25059      const alignedWithSegment = Math.abs(startFragment.start - resumeTime) < ALIGNED_END_THRESHOLD_SECONDS + allowance || Math.abs(startFragment.end - resumeTime) < ALIGNED_END_THRESHOLD_SECONDS + allowance;
25060      if (!alignedWithSegment) {
25061        this.log(`"${interstitial.identifier}" resumption ${resumeTime} not aligned with ${playlistType} fragment bounds (${startFragment.start}-${startFragment.end} sn: ${startFragment.sn} cc: ${startFragment.cc})`);
25062        return true;
25063      }
25064      return false;
25065    });
25066  }
25067  updateAssetDurations(interstitial) {
25068    if (!interstitial.assetListLoaded) {
25069      return;
25070    }
25071    const eventStart = interstitial.timelineStart;
25072    let sumDuration = 0;
25073    let hasUnknownDuration = false;
25074    let hasErrors = false;
25075    for (let i = 0; i < interstitial.assetList.length; i++) {
25076      const asset = interstitial.assetList[i];
25077      const timelineStart = eventStart + sumDuration;
25078      asset.startOffset = sumDuration;
25079      asset.timelineStart = timelineStart;
25080      hasUnknownDuration || (hasUnknownDuration = asset.duration === null);
25081      hasErrors || (hasErrors = !!asset.error);
25082      const duration = asset.error ? 0 : asset.duration || 0;
25083      sumDuration += duration;
25084    }
25085    // Use the sum of known durations when it is greater than the stated duration
25086    if (hasUnknownDuration && !hasErrors) {
25087      interstitial.duration = Math.max(sumDuration, interstitial.duration);
25088    } else {
25089      interstitial.duration = sumDuration;
25090    }
25091  }
25092  removeEvent(interstitial) {
25093    interstitial.reset();
25094    delete this.eventMap[interstitial.identifier];
25095  }
25096}
25097function segmentToString(segment) {
25098  return `[${segment.event ? '"' + segment.event.identifier + '"' : 'primary'}: ${segment.start.toFixed(2)}-${segment.end.toFixed(2)}]`;
25099}
25100
25101class AssetListLoader {
25102  constructor(hls) {
25103    this.hls = void 0;
25104    this.hls = hls;
25105  }
25106  destroy() {
25107    // @ts-ignore
25108    this.hls = null;
25109  }
25110  loadAssetList(interstitial, hlsStartOffset) {
25111    const assetListUrl = interstitial.assetListUrl;
25112    let url;
25113    try {
25114      url = getInterstitialUrl(assetListUrl, this.hls.sessionId, interstitial.baseUrl);
25115    } catch (error) {
25116      const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_ERROR, error, assetListUrl);
25117      this.hls.trigger(Events.ERROR, errorData);
25118      return;
25119    }
25120    if (hlsStartOffset && url.protocol !== 'data:') {
25121      url.searchParams.set('_HLS_start_offset', '' + hlsStartOffset);
25122    }
25123    const config = this.hls.config;
25124    const Loader = config.loader;
25125    const loader = new Loader(config);
25126    const context = {
25127      responseType: 'json',
25128      url: url.href
25129    };
25130    const loadPolicy = config.interstitialAssetListLoadPolicy.default;
25131    const loaderConfig = {
25132      loadPolicy,
25133      timeout: loadPolicy.maxLoadTimeMs,
25134      maxRetry: 0,
25135      retryDelay: 0,
25136      maxRetryDelay: 0
25137    };
25138    const callbacks = {
25139      onSuccess: (response, stats, context, networkDetails) => {
25140        const assetListResponse = response.data;
25141        const assets = assetListResponse == null ? void 0 : assetListResponse.ASSETS;
25142        if (!Array.isArray(assets)) {
25143          const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_PARSING_ERROR, new Error(`Invalid interstitial asset list`), context.url, stats, networkDetails);
25144          this.hls.trigger(Events.ERROR, errorData);
25145          return;
25146        }
25147        interstitial.assetListResponse = assetListResponse;
25148        this.hls.trigger(Events.ASSET_LIST_LOADED, {
25149          event: interstitial,
25150          assetListResponse,
25151          networkDetails
25152        });
25153      },
25154      onError: (error, context, networkDetails, stats) => {
25155        const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_ERROR, new Error(`Error loading X-ASSET-LIST: HTTP status ${error.code} ${error.text} (${context.url})`), context.url, stats, networkDetails);
25156        this.hls.trigger(Events.ERROR, errorData);
25157      },
25158      onTimeout: (stats, context, networkDetails) => {
25159        const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_TIMEOUT, new Error(`Timeout loading X-ASSET-LIST (${context.url})`), context.url, stats, networkDetails);
25160        this.hls.trigger(Events.ERROR, errorData);
25161      }
25162    };
25163    loader.load(context, loaderConfig, callbacks);
25164    this.hls.trigger(Events.ASSET_LIST_LOADING, {
25165      event: interstitial
25166    });
25167    return loader;
25168  }
25169  assignAssetListError(interstitial, details, error, url, stats, networkDetails) {
25170    interstitial.error = error;
25171    return {
25172      type: ErrorTypes.NETWORK_ERROR,
25173      details,
25174      fatal: false,
25175      interstitial,
25176      url,
25177      error,
25178      networkDetails,
25179      stats
25180    };
25181  }
25182}
25183
25184function playWithCatch(media) {
25185  var _media$play;
25186  media == null || (_media$play = media.play()) == null || _media$play.catch(() => {
25187    /* no-op */
25188  });
25189}
25190function timelineMessage(label, time) {
25191  return `[${label}] Advancing timeline position to ${time}`;
25192}
25193class InterstitialsController extends Logger {
vendor: 5,755 bytes, lines 25194-25347
25194  constructor(hls, HlsPlayerClass) {
25195    super('interstitials', hls.logger);
25196    this.HlsPlayerClass = void 0;
25197    this.hls = void 0;
25198    this.assetListLoader = void 0;
25199    // Last updated LevelDetails
25200    this.mediaSelection = null;
25201    this.altSelection = null;
25202    // Media and MediaSource/SourceBuffers
25203    this.media = null;
25204    this.detachedData = null;
25205    this.requiredTracks = null;
25206    // Public Interface for Interstitial playback state and control
25207    this.manager = null;
25208    // Interstitial Asset Players
25209    this.playerQueue = [];
25210    // Timeline position tracking
25211    this.bufferedPos = -1;
25212    this.timelinePos = -1;
25213    // Schedule
25214    this.schedule = void 0;
25215    // Schedule playback and buffering state
25216    this.playingItem = null;
25217    this.bufferingItem = null;
25218    this.waitingItem = null;
25219    this.endedItem = null;
25220    this.playingAsset = null;
25221    this.endedAsset = null;
25222    this.bufferingAsset = null;
25223    this.shouldPlay = false;
25224    this.onPlay = () => {
25225      this.shouldPlay = true;
25226    };
25227    this.onPause = () => {
25228      this.shouldPlay = false;
25229    };
25230    this.onSeeking = () => {
25231      const currentTime = this.currentTime;
25232      if (currentTime === undefined || this.playbackDisabled || !this.schedule) {
25233        return;
25234      }
25235      const diff = currentTime - this.timelinePos;
25236      const roundingError = Math.abs(diff) < 1 / 705600000; // one flick
25237      if (roundingError) {
25238        return;
25239      }
25240      const backwardSeek = diff <= -0.01;
25241      if (this.timelinePos === -1 && !this.effectivePlayingItem) {
25242        this.checkStart();
25243      }
25244      this.timelinePos = currentTime;
25245      this.bufferedPos = currentTime;
25246
25247      // Check if seeking out of an item
25248      const playingItem = this.playingItem;
25249      if (!playingItem) {
25250        this.checkBuffer();
25251        return;
25252      }
25253      if (backwardSeek) {
25254        const resetCount = this.schedule.resetErrorsInRange(currentTime, currentTime - diff);
25255        if (resetCount) {
25256          this.updateSchedule(true);
25257        }
25258      }
25259      this.checkBuffer();
25260      if (backwardSeek && currentTime < playingItem.start || currentTime >= playingItem.end) {
25261        var _this$media;
25262        const playingIndex = this.findItemIndex(playingItem);
25263        let scheduleIndex = this.schedule.findItemIndexAtTime(currentTime);
25264        if (scheduleIndex === -1) {
25265          scheduleIndex = playingIndex + (backwardSeek ? -1 : 1);
25266          this.log(`seeked ${backwardSeek ? 'back ' : ''}to position not covered by schedule ${currentTime} (resolving from ${playingIndex} to ${scheduleIndex})`);
25267        }
25268        if (!this.isInterstitial(playingItem) && (_this$media = this.media) != null && _this$media.paused) {
25269          this.shouldPlay = false;
25270        }
25271        if (!backwardSeek) {
25272          // check if an Interstitial between the current item and target item has an X-RESTRICT JUMP restriction
25273          if (scheduleIndex > playingIndex) {
25274            const jumpIndex = this.schedule.findJumpRestrictedIndex(playingIndex + 1, scheduleIndex);
25275            if (jumpIndex > playingIndex) {
25276              this.setSchedulePosition(jumpIndex);
25277              return;
25278            }
25279          }
25280        }
25281        this.setSchedulePosition(scheduleIndex);
25282        return;
25283      }
25284      // Check if seeking out of an asset (assumes same item following above check)
25285      const playingAsset = this.playingAsset;
25286      if (!playingAsset) {
25287        // restart Interstitial at end
25288        if (this.playingLastItem && this.isInterstitial(playingItem)) {
25289          const restartAsset = playingItem.event.assetList[0];
25290          // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25291          if (restartAsset) {
25292            this.endedItem = this.playingItem;
25293            this.playingItem = null;
25294            this.setScheduleToAssetAtTime(currentTime, restartAsset);
25295          }
25296        }
25297        return;
25298      }
25299      const start = playingAsset.timelineStart;
25300      const duration = playingAsset.duration || 0;
25301      if (backwardSeek && currentTime < start || currentTime >= start + duration) {
25302        var _playingItem$event;
25303        if ((_playingItem$event = playingItem.event) != null && _playingItem$event.appendInPlace) {
25304          // Return SourceBuffer(s) to primary player and flush
25305          this.clearAssetPlayers(playingItem.event, playingItem);
25306          this.flushFrontBuffer(currentTime);
25307        }
25308        this.setScheduleToAssetAtTime(currentTime, playingAsset);
25309      }
25310    };
25311    this.onTimeupdate = () => {
25312      const currentTime = this.currentTime;
25313      if (currentTime === undefined || this.playbackDisabled) {
25314        return;
25315      }
25316      if (this.timelinePos === -1 && !this.effectivePlayingItem) {
25317        this.checkStart();
25318      }
25319
25320      // Only allow timeupdate to advance primary position, seeking is used for jumping back
25321      // this prevents primaryPos from being reset to 0 after re-attach
25322      if (currentTime > this.timelinePos) {
25323        this.timelinePos = currentTime;
25324        if (currentTime > this.bufferedPos) {
25325          this.checkBuffer();
25326        }
25327      } else {
25328        return;
25329      }
25330
25331      // Check if playback has entered the next item
25332      const playingItem = this.playingItem;
25333      if (!playingItem || this.playingLastItem) {
25334        return;
25335      }
25336      if (currentTime >= playingItem.end) {
25337        this.timelinePos = playingItem.end;
25338        const playingIndex = this.findItemIndex(playingItem);
25339        this.setSchedulePosition(playingIndex + 1);
25340      }
25341      // Check if playback has entered the next asset
25342      const playingAsset = this.playingAsset;
25343      if (!playingAsset) {
25344        return;
25345      }
25346      const end = playingAsset.timelineStart + (playingAsset.duration || 0);
25347      if (currentTime >
vendor: 25,891 bytes, lines 25347-25970
25347= end) {
25348        this.setScheduleToAssetAtTime(currentTime, playingAsset);
25349      }
25350    };
25351    // Schedule update callback
25352    this.onScheduleUpdate = (removedInterstitials, previousItems) => {
25353      const schedule = this.schedule;
25354      if (!schedule) {
25355        return;
25356      }
25357      const playingItem = this.playingItem;
25358      const interstitialEvents = schedule.events || [];
25359      const scheduleItems = schedule.items || [];
25360      const durations = schedule.durations;
25361      const removedIds = removedInterstitials.map(interstitial => interstitial.identifier);
25362      const interstitialsUpdated = !!(interstitialEvents.length || removedIds.length);
25363      if (interstitialsUpdated || previousItems) {
25364        this.log(`INTERSTITIALS_UPDATED (${interstitialEvents.length}): ${interstitialEvents}
25365Schedule: ${scheduleItems.map(seg => segmentToString(seg))} pos: ${this.timelinePos}`);
25366      }
25367      if (removedIds.length) {
25368        this.log(`Removed events ${removedIds}`);
25369      }
25370
25371      // Update schedule item references
25372      // Do not replace Interstitial playingItem without a match - used for INTERSTITIAL_ASSET_ENDED and INTERSTITIAL_ENDED
25373      let updatedPlayingItem = null;
25374      let updatedBufferingItem = null;
25375      if (playingItem) {
25376        updatedPlayingItem = this.updateItem(playingItem, this.timelinePos);
25377        if (this.itemsMatch(playingItem, updatedPlayingItem)) {
25378          this.playingItem = updatedPlayingItem;
25379        } else {
25380          this.waitingItem = this.endedItem = null;
25381        }
25382      }
25383      // Clear waitingItem if it has been removed from the schedule
25384      this.waitingItem = this.updateItem(this.waitingItem);
25385      this.endedItem = this.updateItem(this.endedItem);
25386      // Do not replace Interstitial bufferingItem without a match - used for transfering media element or source
25387      const bufferingItem = this.bufferingItem;
25388      if (bufferingItem) {
25389        updatedBufferingItem = this.updateItem(bufferingItem, this.bufferedPos);
25390        if (this.itemsMatch(bufferingItem, updatedBufferingItem)) {
25391          this.bufferingItem = updatedBufferingItem;
25392        } else if (bufferingItem.event) {
25393          // Interstitial removed from schedule (Live -> VOD or other scenario where Start Date is outside the range of VOD Playlist)
25394          this.bufferingItem = this.playingItem;
25395          this.clearInterstitial(bufferingItem.event, null);
25396        }
25397      }
25398      removedInterstitials.forEach(interstitial => {
25399        interstitial.assetList.forEach(asset => {
25400          this.clearAssetPlayer(asset.identifier, null);
25401        });
25402      });
25403      this.playerQueue.forEach(player => {
25404        if (player.interstitial.appendInPlace) {
25405          const timelineStart = player.assetItem.timelineStart;
25406          const diff = player.timelineOffset - timelineStart;
25407          if (diff) {
25408            try {
25409              player.timelineOffset = timelineStart;
25410            } catch (e) {
25411              if (Math.abs(diff) > ALIGNED_END_THRESHOLD_SECONDS) {
25412                this.warn(`${e} ("${player.assetId}" ${player.timelineOffset}->${timelineStart})`);
25413              }
25414            }
25415          }
25416        }
25417      });
25418      if (interstitialsUpdated || previousItems) {
25419        this.hls.trigger(Events.INTERSTITIALS_UPDATED, {
25420          events: interstitialEvents.slice(0),
25421          schedule: scheduleItems.slice(0),
25422          durations,
25423          removedIds
25424        });
25425        if (this.isInterstitial(playingItem) && removedIds.includes(playingItem.event.identifier)) {
25426          this.warn(`Interstitial "${playingItem.event.identifier}" removed while playing`);
25427          this.primaryFallback(playingItem.event);
25428          return;
25429        }
25430        if (playingItem) {
25431          this.trimInPlace(updatedPlayingItem, playingItem);
25432        }
25433        if (bufferingItem && updatedBufferingItem !== updatedPlayingItem) {
25434          this.trimInPlace(updatedBufferingItem, bufferingItem);
25435        }
25436
25437        // Check if buffered to new Interstitial event boundary
25438        // (Live update publishes Interstitial with new segment)
25439        this.checkBuffer();
25440      }
25441    };
25442    this.hls = hls;
25443    this.HlsPlayerClass = HlsPlayerClass;
25444    this.assetListLoader = new AssetListLoader(hls);
25445    this.schedule = new InterstitialsSchedule(this.onScheduleUpdate, hls.logger);
25446    this.registerListeners();
25447  }
25448  registerListeners() {
25449    const hls = this.hls;
25450    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25451    if (hls) {
25452      hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
25453      hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
25454      hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
25455      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
25456      hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
25457      hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
25458      hls.on(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this);
25459      hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
25460      hls.on(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this);
25461      hls.on(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this);
25462      hls.on(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this);
25463      hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
25464      hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
25465      hls.on(Events.BUFFERED_TO_END, this.onBufferedToEnd, this);
25466      hls.on(Events.MEDIA_ENDED, this.onMediaEnded, this);
25467      hls.on(Events.ERROR, this.onError, this);
25468      hls.on(Events.DESTROYING, this.onDestroying, this);
25469    }
25470  }
25471  unregisterListeners() {
25472    const hls = this.hls;
25473    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25474    if (hls) {
25475      hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
25476      hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
25477      hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
25478      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
25479      hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
25480      hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
25481      hls.off(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this);
25482      hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
25483      hls.off(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this);
25484      hls.off(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this);
25485      hls.off(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this);
25486      hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
25487      hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
25488      hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
25489      hls.off(Events.BUFFERED_TO_END, this.onBufferedToEnd, this);
25490      hls.off(Events.MEDIA_ENDED, this.onMediaEnded, this);
25491      hls.off(Events.ERROR, this.onError, this);
25492      hls.off(Events.DESTROYING, this.onDestroying, this);
25493    }
25494  }
25495  startLoad() {
25496    // TODO: startLoad - check for waitingItem and retry by resetting schedule
25497    this.resumeBuffering();
25498  }
25499  stopLoad() {
25500    // TODO: stopLoad - stop all scheule.events[].assetListLoader?.abort() then delete the loaders
25501    this.pauseBuffering();
25502  }
25503  resumeBuffering() {
25504    var _this$getBufferingPla;
25505    (_this$getBufferingPla = this.getBufferingPlayer()) == null || _this$getBufferingPla.resumeBuffering();
25506  }
25507  pauseBuffering() {
25508    var _this$getBufferingPla2;
25509    (_this$getBufferingPla2 = this.getBufferingPlayer()) == null || _this$getBufferingPla2.pauseBuffering();
25510  }
25511  destroy() {
25512    this.unregisterListeners();
25513    this.stopLoad();
25514    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25515    if (this.assetListLoader) {
25516      this.assetListLoader.destroy();
25517    }
25518    this.emptyPlayerQueue();
25519    this.clearScheduleState();
25520    if (this.schedule) {
25521      this.schedule.destroy();
25522    }
25523    this.media = this.detachedData = this.mediaSelection = this.requiredTracks = this.altSelection = this.schedule = this.manager = null;
25524    // @ts-ignore
25525    this.hls = this.HlsPlayerClass = this.log = null;
25526    // @ts-ignore
25527    this.assetListLoader = null;
25528    // @ts-ignore
25529    this.onPlay = this.onPause = this.onSeeking = this.onTimeupdate = null;
25530    // @ts-ignore
25531    this.onScheduleUpdate = null;
25532  }
25533  onDestroying() {
25534    const media = this.primaryMedia || this.media;
25535    if (media) {
25536      this.removeMediaListeners(media);
25537    }
25538  }
25539  removeMediaListeners(media) {
25540    removeEventListener(media, 'play', this.onPlay);
25541    removeEventListener(media, 'pause', this.onPause);
25542    removeEventListener(media, 'seeking', this.onSeeking);
25543    removeEventListener(media, 'timeupdate', this.onTimeupdate);
25544  }
25545  onMediaAttaching(event, data) {
25546    const media = this.media = data.media;
25547    addEventListener(media, 'seeking', this.onSeeking);
25548    addEventListener(media, 'timeupdate', this.onTimeupdate);
25549    addEventListener(media, 'play', this.onPlay);
25550    addEventListener(media, 'pause', this.onPause);
25551  }
25552  onMediaAttached(event, data) {
25553    const playingItem = this.effectivePlayingItem;
25554    const detachedMedia = this.detachedData;
25555    this.detachedData = null;
25556    if (playingItem === null) {
25557      this.checkStart();
25558    } else if (!detachedMedia) {
25559      // Resume schedule after detached externally
25560      this.clearScheduleState();
25561      const playingIndex = this.findItemIndex(playingItem);
25562      this.setSchedulePosition(playingIndex);
25563    }
25564  }
25565  clearScheduleState() {
25566    this.log(`clear schedule state`);
25567    this.playingItem = this.bufferingItem = this.waitingItem = this.endedItem = this.playingAsset = this.endedAsset = this.bufferingAsset = null;
25568  }
25569  onMediaDetaching(event, data) {
25570    const transferringMedia = !!data.transferMedia;
25571    const media = this.media;
25572    this.media = null;
25573    if (transferringMedia) {
25574      return;
25575    }
25576    if (media) {
25577      this.removeMediaListeners(media);
25578    }
25579    // If detachMedia is called while in an Interstitial, detach the asset player as well and reset the schedule position
25580    if (this.detachedData) {
25581      const player = this.getBufferingPlayer();
25582      if (player) {
25583        this.log(`Removing schedule state for detachedData and ${player}`);
25584        this.playingAsset = this.endedAsset = this.bufferingAsset = this.bufferingItem = this.waitingItem = this.detachedData = null;
25585        player.detachMedia();
25586      }
25587      this.shouldPlay = false;
25588    }
25589  }
25590  get interstitialsManager() {
25591    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25592    if (!this.hls) {
25593      return null;
25594    }
25595    if (this.manager) {
25596      return this.manager;
25597    }
25598    const c = this;
25599    const effectiveBufferingItem = () => c.bufferingItem || c.waitingItem;
25600    const getAssetPlayer = asset => asset ? c.getAssetPlayer(asset.identifier) : asset;
25601    const getMappedTime = (item, timelineType, asset, controllerField, assetPlayerField) => {
25602      if (item) {
25603        let time = item[timelineType].start;
25604        const interstitial = item.event;
25605        if (interstitial) {
25606          if (timelineType === 'playout' || interstitial.timelineOccupancy !== TimelineOccupancy.Point) {
25607            const assetPlayer = getAssetPlayer(asset);
25608            if ((assetPlayer == null ? void 0 : assetPlayer.interstitial) === interstitial) {
25609              time += assetPlayer.assetItem.startOffset + assetPlayer[assetPlayerField];
25610            }
25611          }
25612        } else {
25613          const value = controllerField === 'bufferedPos' ? getBufferedEnd() : c[controllerField];
25614          time += value - item.start;
25615        }
25616        return time;
25617      }
25618      return 0;
25619    };
25620    const findMappedTime = (primaryTime, timelineType) => {
25621      var _c$schedule;
25622      if (primaryTime !== 0 && timelineType !== 'primary' && (_c$schedule = c.schedule) != null && _c$schedule.length) {
25623        var _c$schedule$items;
25624        const index = c.schedule.findItemIndexAtTime(primaryTime);
25625        const item = (_c$schedule$items = c.schedule.items) == null ? void 0 : _c$schedule$items[index];
25626        if (item) {
25627          const diff = item[timelineType].start - item.start;
25628          return primaryTime + diff;
25629        }
25630      }
25631      return primaryTime;
25632    };
25633    const getBufferedEnd = () => {
25634      const value = c.bufferedPos;
25635      if (value === Number.MAX_VALUE) {
25636        return getMappedDuration('primary');
25637      }
25638      return Math.max(value, 0);
25639    };
25640    const getMappedDuration = timelineType => {
25641      var _c$primaryDetails, _c$schedule2;
25642      if ((_c$primaryDetails = c.primaryDetails) != null && _c$primaryDetails.live) {
25643        // return end of last event item or playlist
25644        return c.primaryDetails.edge;
25645      }
25646      return ((_c$schedule2 = c.schedule) == null ? void 0 : _c$schedule2.durations[timelineType]) || 0;
25647    };
25648    const seekTo = (time, timelineType) => {
25649      var _item$event, _c$schedule$items2;
25650      const item = c.effectivePlayingItem;
25651      if (item != null && (_item$event = item.event) != null && _item$event.restrictions.skip || !c.schedule) {
25652        return;
25653      }
25654      c.log(`seek to ${time} "${timelineType}"`);
25655      const playingItem = c.effectivePlayingItem;
25656      const targetIndex = c.schedule.findItemIndexAtTime(time, timelineType);
25657      const targetItem = (_c$schedule$items2 = c.schedule.items) == null ? void 0 : _c$schedule$items2[targetIndex];
25658      const bufferingPlayer = c.getBufferingPlayer();
25659      const bufferingInterstitial = bufferingPlayer == null ? void 0 : bufferingPlayer.interstitial;
25660      const appendInPlace = bufferingInterstitial == null ? void 0 : bufferingInterstitial.appendInPlace;
25661      const seekInItem = playingItem && c.itemsMatch(playingItem, targetItem);
25662      if (playingItem && (appendInPlace || seekInItem)) {
25663        // seek in asset player or primary media (appendInPlace)
25664        const assetPlayer = getAssetPlayer(c.playingAsset);
25665        const media = (assetPlayer == null ? void 0 : assetPlayer.media) || c.primaryMedia;
25666        if (media) {
25667          const currentTime = timelineType === 'primary' ? media.currentTime : getMappedTime(playingItem, timelineType, c.playingAsset, 'timelinePos', 'currentTime');
25668          const diff = time - currentTime;
25669          const seekToTime = (appendInPlace ? currentTime : media.currentTime) + diff;
25670          if (seekToTime >= 0 && (!assetPlayer || appendInPlace || seekToTime <= assetPlayer.duration)) {
25671            media.currentTime = seekToTime;
25672            return;
25673          }
25674        }
25675      }
25676      // seek out of item or asset
25677      if (targetItem) {
25678        let seekToTime = time;
25679        if (timelineType !== 'primary') {
25680          const primarySegmentStart = targetItem[timelineType].start;
25681          const diff = time - primarySegmentStart;
25682          seekToTime = targetItem.start + diff;
25683        }
25684        const targetIsPrimary = !c.isInterstitial(targetItem);
25685        if ((!c.isInterstitial(playingItem) || playingItem.event.appendInPlace) && (targetIsPrimary || targetItem.event.appendInPlace)) {
25686          const media = c.media || (appendInPlace ? bufferingPlayer == null ? void 0 : bufferingPlayer.media : null);
25687          if (media) {
25688            media.currentTime = seekToTime;
25689          }
25690        } else if (playingItem) {
25691          // check if an Interstitial between the current item and target item has an X-RESTRICT JUMP restriction
25692          const playingIndex = c.findItemIndex(playingItem);
25693          if (targetIndex > playingIndex) {
25694            const jumpIndex = c.schedule.findJumpRestrictedIndex(playingIndex + 1, targetIndex);
25695            if (jumpIndex > playingIndex) {
25696              c.setSchedulePosition(jumpIndex);
25697              return;
25698            }
25699          }
25700          let assetIndex = 0;
25701          if (targetIsPrimary) {
25702            c.timelinePos = seekToTime;
25703            c.checkBuffer();
25704          } else {
25705            const assetList = targetItem.event.assetList;
25706            const eventTime = time - (targetItem[timelineType] || targetItem).start;
25707            for (let i = assetList.length; i--;) {
25708              const asset = assetList[i];
25709              if (asset.duration && eventTime >= asset.startOffset && eventTime < asset.startOffset + asset.duration) {
25710                assetIndex = i;
25711                break;
25712              }
25713            }
25714          }
25715          c.setSchedulePosition(targetIndex, assetIndex);
25716        }
25717      }
25718    };
25719    const getActiveInterstitial = () => {
25720      const playingItem = c.effectivePlayingItem;
25721      if (c.isInterstitial(playingItem)) {
25722        return playingItem;
25723      }
25724      const bufferingItem = effectiveBufferingItem();
25725      if (c.isInterstitial(bufferingItem)) {
25726        return bufferingItem;
25727      }
25728      return null;
25729    };
25730    const interstitialPlayer = {
25731      get bufferedEnd() {
25732        const interstitialItem = effectiveBufferingItem();
25733        const bufferingItem = c.bufferingItem;
25734        if (bufferingItem && bufferingItem === interstitialItem) {
25735          var _c$bufferingAsset;
25736          return getMappedTime(bufferingItem, 'playout', c.bufferingAsset, 'bufferedPos', 'bufferedEnd') - bufferingItem.playout.start || ((_c$bufferingAsset = c.bufferingAsset) == null ? void 0 : _c$bufferingAsset.startOffset) || 0;
25737        }
25738        return 0;
25739      },
25740      get currentTime() {
25741        const interstitialItem = getActiveInterstitial();
25742        const playingItem = c.effectivePlayingItem;
25743        if (playingItem && playingItem === interstitialItem) {
25744          return getMappedTime(playingItem, 'playout', c.effectivePlayingAsset, 'timelinePos', 'currentTime') - playingItem.playout.start;
25745        }
25746        return 0;
25747      },
25748      set currentTime(time) {
25749        const interstitialItem = getActiveInterstitial();
25750        const playingItem = c.effectivePlayingItem;
25751        if (playingItem && playingItem === interstitialItem) {
25752          seekTo(time + playingItem.playout.start, 'playout');
25753        }
25754      },
25755      get duration() {
25756        const interstitialItem = getActiveInterstitial();
25757        if (interstitialItem) {
25758          return interstitialItem.playout.end - interstitialItem.playout.start;
25759        }
25760        return 0;
25761      },
25762      get assetPlayers() {
25763        var _getActiveInterstitia;
25764        const assetList = (_getActiveInterstitia = getActiveInterstitial()) == null ? void 0 : _getActiveInterstitia.event.assetList;
25765        if (assetList) {
25766          return assetList.map(asset => c.getAssetPlayer(asset.identifier));
25767        }
25768        return [];
25769      },
25770      get playingIndex() {
25771        var _getActiveInterstitia2;
25772        const interstitial = (_getActiveInterstitia2 = getActiveInterstitial()) == null ? void 0 : _getActiveInterstitia2.event;
25773        if (interstitial && c.effectivePlayingAsset) {
25774          return interstitial.findAssetIndex(c.effectivePlayingAsset);
25775        }
25776        return -1;
25777      },
25778      get scheduleItem() {
25779        return getActiveInterstitial();
25780      }
25781    };
25782    return this.manager = {
25783      get events() {
25784        var _c$schedule3;
25785        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25786        return ((_c$schedule3 = c.schedule) == null || (_c$schedule3 = _c$schedule3.events) == null ? void 0 : _c$schedule3.slice(0)) || [];
25787      },
25788      get schedule() {
25789        var _c$schedule4;
25790        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
25791        return ((_c$schedule4 = c.schedule) == null || (_c$schedule4 = _c$schedule4.items) == null ? void 0 : _c$schedule4.slice(0)) || [];
25792      },
25793      get interstitialPlayer() {
25794        if (getActiveInterstitial()) {
25795          return interstitialPlayer;
25796        }
25797        return null;
25798      },
25799      get playerQueue() {
25800        return c.playerQueue.slice(0);
25801      },
25802      get bufferingAsset() {
25803        return c.bufferingAsset;
25804      },
25805      get bufferingItem() {
25806        return effectiveBufferingItem();
25807      },
25808      get bufferingIndex() {
25809        const item = effectiveBufferingItem();
25810        return c.findItemIndex(item);
25811      },
25812      get playingAsset() {
25813        return c.effectivePlayingAsset;
25814      },
25815      get playingItem() {
25816        return c.effectivePlayingItem;
25817      },
25818      get playingIndex() {
25819        const item = c.effectivePlayingItem;
25820        return c.findItemIndex(item);
25821      },
25822      primary: {
25823        get bufferedEnd() {
25824          return getBufferedEnd();
25825        },
25826        get currentTime() {
25827          const timelinePos = c.timelinePos;
25828          return timelinePos > 0 ? timelinePos : 0;
25829        },
25830        set currentTime(time) {
25831          seekTo(time, 'primary');
25832        },
25833        get duration() {
25834          return getMappedDuration('primary');
25835        },
25836        get seekableStart() {
25837          var _c$primaryDetails2;
25838          return ((_c$primaryDetails2 = c.primaryDetails) == null ? void 0 : _c$primaryDetails2.fragmentStart) || 0;
25839        }
25840      },
25841      integrated: {
25842        get bufferedEnd() {
25843          return getMappedTime(effectiveBufferingItem(), 'integrated', c.bufferingAsset, 'bufferedPos', 'bufferedEnd');
25844        },
25845        get currentTime() {
25846          return getMappedTime(c.effectivePlayingItem, 'integrated', c.effectivePlayingAsset, 'timelinePos', 'currentTime');
25847        },
25848        set currentTime(time) {
25849          seekTo(time, 'integrated');
25850        },
25851        get duration() {
25852          return getMappedDuration('integrated');
25853        },
25854        get seekableStart() {
25855          var _c$primaryDetails3;
25856          return findMappedTime(((_c$primaryDetails3 = c.primaryDetails) == null ? void 0 : _c$primaryDetails3.fragmentStart) || 0, 'integrated');
25857        }
25858      },
25859      skip: () => {
25860        const item = c.effectivePlayingItem;
25861        const event = item == null ? void 0 : item.event;
25862        if (event && !event.restrictions.skip) {
25863          const index = c.findItemIndex(item);
25864          if (event.appendInPlace) {
25865            const time = item.playout.start + item.event.duration;
25866            seekTo(time + 0.001, 'playout');
25867          } else {
25868            c.advanceAfterAssetEnded(event, index, Infinity);
25869          }
25870        }
25871      }
25872    };
25873  }
25874
25875  // Schedule getters
25876  get effectivePlayingItem() {
25877    return this.waitingItem || this.playingItem || this.endedItem;
25878  }
25879  get effectivePlayingAsset() {
25880    return this.playingAsset || this.endedAsset;
25881  }
25882  get playingLastItem() {
25883    var _this$schedule;
25884    const playingItem = this.playingItem;
25885    const items = (_this$schedule = this.schedule) == null ? void 0 : _this$schedule.items;
25886    if (!this.playbackStarted || !playingItem || !items) {
25887      return false;
25888    }
25889    return this.findItemIndex(playingItem) === items.length - 1;
25890  }
25891  get playbackStarted() {
25892    return this.effectivePlayingItem !== null;
25893  }
25894
25895  // Media getters and event callbacks
25896  get currentTime() {
25897    var _this$bufferingItem, _media;
25898    if (this.mediaSelection === null) {
25899      // Do not advance before schedule is known
25900      return undefined;
25901    }
25902    // Ignore currentTime when detached for Interstitial playback with source reset
25903    const queuedForPlayback = this.waitingItem || this.playingItem;
25904    if (this.isInterstitial(queuedForPlayback) && !queuedForPlayback.event.appendInPlace) {
25905      return undefined;
25906    }
25907    let media = this.media;
25908    if (!media && (_this$bufferingItem = this.bufferingItem) != null && (_this$bufferingItem = _this$bufferingItem.event) != null && _this$bufferingItem.appendInPlace) {
25909      // Observe detached media currentTime when appending in place
25910      media = this.primaryMedia;
25911    }
25912    const currentTime = (_media = media) == null ? void 0 : _media.currentTime;
25913    if (currentTime === undefined || !isFiniteNumber(currentTime)) {
25914      return undefined;
25915    }
25916    return currentTime;
25917  }
25918  get primaryMedia() {
25919    var _this$detachedData;
25920    return this.media || ((_this$detachedData = this.detachedData) == null ? void 0 : _this$detachedData.media) || null;
25921  }
25922  isInterstitial(item) {
25923    return !!(item != null && item.event);
25924  }
25925  retreiveMediaSource(assetId, toSegment) {
25926    const player = this.getAssetPlayer(assetId);
25927    if (player) {
25928      this.transferMediaFromPlayer(player, toSegment);
25929    }
25930  }
25931  transferMediaFromPlayer(player, toSegment) {
25932    const appendInPlace = player.interstitial.appendInPlace;
25933    const playerMedia = player.media;
25934    if (appendInPlace && playerMedia === this.primaryMedia) {
25935      this.bufferingAsset = null;
25936      if (!toSegment || this.isInterstitial(toSegment) && !toSegment.event.appendInPlace) {
25937        // MediaSource cannot be transfered back to an Interstitial that requires a source reset
25938        // no-op when toSegment is undefined
25939        if (toSegment && playerMedia) {
25940          this.detachedData = {
25941            media: playerMedia
25942          };
25943          return;
25944        }
25945      }
25946      const attachMediaSourceData = player.transferMedia();
25947      this.log(`transfer MediaSource from ${player} ${stringify(attachMediaSourceData)}`);
25948      this.detachedData = attachMediaSourceData;
25949    } else if (toSegment && playerMedia) {
25950      this.shouldPlay || (this.shouldPlay = !playerMedia.paused);
25951    }
25952  }
25953  transferMediaTo(player, media) {
25954    var _this$detachedData2, _attachMediaSourceDat;
25955    if (player.media === media) {
25956      return;
25957    }
25958    let attachMediaSourceData = null;
25959    const primaryPlayer = this.hls;
25960    const isAssetPlayer = player !== primaryPlayer;
25961    const appendInPlace = isAssetPlayer && player.interstitial.appendInPlace;
25962    const detachedMediaSource = (_this$detachedData2 = this.detachedData) == null ? void 0 : _this$detachedData2.mediaSource;
25963    let logFromSource;
25964    if (primaryPlayer.media) {
25965      if (appendInPlace) {
25966        attachMediaSourceData = primaryPlayer.transferMedia();
25967        this.detachedData = attachMediaSourceData;
25968      }
25969      logFromSource = `Primary`;
25970    } else if (detachedMediaSource) {
vendor: 6,499 bytes, lines 25971-26118
25971      const bufferingPlayer = this.getBufferingPlayer();
25972      if (bufferingPlayer) {
25973        attachMediaSourceData = bufferingPlayer.transferMedia();
25974        logFromSource = `${bufferingPlayer}`;
25975      } else {
25976        logFromSource = `detached MediaSource`;
25977      }
25978    } else {
25979      logFromSource = `detached media`;
25980    }
25981    if (!attachMediaSourceData) {
25982      if (detachedMediaSource) {
25983        attachMediaSourceData = this.detachedData;
25984        this.log(`using detachedData: MediaSource ${stringify(attachMediaSourceData)}`);
25985      } else if (!this.detachedData || primaryPlayer.media === media) {
25986        // Keep interstitial media transition consistent
25987        const playerQueue = this.playerQueue;
25988        if (playerQueue.length > 1) {
25989          playerQueue.forEach(queuedPlayer => {
25990            if (isAssetPlayer && queuedPlayer.interstitial.appendInPlace !== appendInPlace) {
25991              const interstitial = queuedPlayer.interstitial;
25992              this.clearInterstitial(queuedPlayer.interstitial, null);
25993              interstitial.appendInPlace = false; // setter may be a no-op;
25994              // `appendInPlace` getter may still return `true` after insterstitial streaming has begun in that mode.
25995              if (interstitial.appendInPlace) {
25996                this.warn(`Could not change append strategy for queued assets ${interstitial}`);
25997              }
25998            }
25999          });
26000        }
26001        this.hls.detachMedia();
26002        this.detachedData = {
26003          media
26004        };
26005      }
26006    }
26007    const transferring = attachMediaSourceData && 'mediaSource' in attachMediaSourceData && ((_attachMediaSourceDat = attachMediaSourceData.mediaSource) == null ? void 0 : _attachMediaSourceDat.readyState) !== 'closed';
26008    const dataToAttach = transferring && attachMediaSourceData ? attachMediaSourceData : media;
26009    this.log(`${transferring ? 'transfering MediaSource' : 'attaching media'} to ${isAssetPlayer ? player : 'Primary'} from ${logFromSource} (media.currentTime: ${media.currentTime})`);
26010    const schedule = this.schedule;
26011    if (dataToAttach === attachMediaSourceData && schedule) {
26012      const isAssetAtEndOfSchedule = isAssetPlayer && player.assetId === schedule.assetIdAtEnd;
26013      // Prevent asset players from marking EoS on transferred MediaSource
26014      dataToAttach.overrides = {
26015        duration: schedule.duration,
26016        endOfStream: !isAssetPlayer || isAssetAtEndOfSchedule,
26017        cueRemoval: !isAssetPlayer
26018      };
26019    }
26020    player.attachMedia(dataToAttach);
26021  }
26022  onInterstitialCueEnter() {
26023    this.onTimeupdate();
26024  }
26025  // Scheduling methods
26026  checkStart() {
26027    const schedule = this.schedule;
26028    const interstitialEvents = schedule == null ? void 0 : schedule.events;
26029    if (!interstitialEvents || this.playbackDisabled || !this.media) {
26030      return;
26031    }
26032    // Check buffered to pre-roll
26033    if (this.bufferedPos === -1) {
26034      this.bufferedPos = 0;
26035    }
26036    // Start stepping through schedule when playback begins for the first time and we have a pre-roll
26037    const timelinePos = this.timelinePos;
26038    const effectivePlayingItem = this.effectivePlayingItem;
26039    if (timelinePos === -1) {
26040      const startPosition = this.hls.startPosition;
26041      this.timelinePos = startPosition;
26042      if (interstitialEvents.length === 0) {
26043        this.setSchedulePosition(0);
26044      } else if (interstitialEvents[0].cue.pre) {
26045        this.log(timelineMessage('checkStart (preroll)', startPosition));
26046        const index = schedule.findEventIndex(interstitialEvents[0].identifier);
26047        this.setSchedulePosition(index);
26048      } else if (startPosition >= 0 || !this.primaryLive) {
26049        this.log(timelineMessage('checkStart', startPosition));
26050        const start = this.timelinePos = startPosition > 0 ? startPosition : 0;
26051        const index = schedule.findItemIndexAtTime(start);
26052        this.setSchedulePosition(index);
26053      } else if (this.hls.liveSyncPosition === 0) {
26054        this.setSchedulePosition(0);
26055      } else {
26056        this.log('[checkStart] waiting for live start');
26057      }
26058    } else if (effectivePlayingItem && !this.playingItem) {
26059      this.log(timelineMessage('checkStart (playing item)', effectivePlayingItem.start));
26060      const index = schedule.findItemIndex(effectivePlayingItem);
26061      this.setSchedulePosition(index);
26062    }
26063  }
26064  advanceAssetBuffering(item, assetItem) {
26065    const interstitial = item.event;
26066    const assetListIndex = interstitial.findAssetIndex(assetItem);
26067    const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex);
26068    if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) {
26069      this.bufferedToEvent(item, nextAssetIndex);
26070    } else if (this.schedule) {
26071      var _this$schedule$items;
26072      const nextItem = (_this$schedule$items = this.schedule.items) == null ? void 0 : _this$schedule$items[this.findItemIndex(item) + 1];
26073      if (nextItem) {
26074        this.bufferedToItem(nextItem);
26075      }
26076    }
26077  }
26078  advanceAfterAssetEnded(interstitial, index, assetListIndex) {
26079    const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex);
26080    if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) {
26081      // Advance to next asset list item
26082      if (interstitial.appendInPlace) {
26083        const assetItem = interstitial.assetList[nextAssetIndex];
26084        if (assetItem) {
26085          this.advanceInPlace(assetItem.timelineStart);
26086        }
26087      }
26088      this.setSchedulePosition(index, nextAssetIndex);
26089    } else if (this.schedule) {
26090      // Advance to next schedule segment
26091      // check if we've reached the end of the program
26092      const scheduleItems = this.schedule.items;
26093      if (scheduleItems) {
26094        const nextIndex = index + 1;
26095        const scheduleLength = scheduleItems.length;
26096        if (nextIndex >= scheduleLength) {
26097          this.setSchedulePosition(-1);
26098          return;
26099        }
26100        const resumptionTime = interstitial.resumeTime;
26101        if (this.timelinePos < resumptionTime) {
26102          this.log(timelineMessage('advanceAfterAssetEnded', resumptionTime));
26103          this.timelinePos = resumptionTime;
26104          if (interstitial.appendInPlace) {
26105            this.advanceInPlace(resumptionTime);
26106          }
26107          this.checkBuffer(this.bufferedPos < resumptionTime);
26108        }
26109        this.setSchedulePosition(nextIndex);
26110      }
26111    }
26112  }
26113  setScheduleToAssetAtTime(time, playingAsset) {
26114    const schedule = this.schedule;
26115    if (!schedule) {
26116      return;
26117    }
26118    const parentIdentifier = playingAsset.parentIdentifier;
vendor: 248,144 bytes, lines 26119-33372
26119    const interstitial = schedule.getEvent(parentIdentifier);
26120    if (interstitial) {
26121      const itemIndex = schedule.findEventIndex(parentIdentifier);
26122      const assetListIndex = schedule.findAssetIndex(interstitial, time);
26123      this.advanceAfterAssetEnded(interstitial, itemIndex, assetListIndex - 1);
26124    }
26125  }
26126  setSchedulePosition(index, assetListIndex) {
26127    var _this$schedule2;
26128    const scheduleItems = (_this$schedule2 = this.schedule) == null ? void 0 : _this$schedule2.items;
26129    if (!scheduleItems || this.playbackDisabled) {
26130      return;
26131    }
26132    const scheduledItem = index >= 0 ? scheduleItems[index] : null;
26133    this.log(`setSchedulePosition ${index}, ${assetListIndex} (${scheduledItem ? segmentToString(scheduledItem) : scheduledItem}) pos: ${this.timelinePos}`);
26134    // Cleanup current item / asset
26135    const currentItem = this.waitingItem || this.playingItem;
26136    const playingLastItem = this.playingLastItem;
26137    if (this.isInterstitial(currentItem)) {
26138      const interstitial = currentItem.event;
26139      const playingAsset = this.playingAsset;
26140      const assetId = playingAsset == null ? void 0 : playingAsset.identifier;
26141      const player = assetId ? this.getAssetPlayer(assetId) : null;
26142      if (player && assetId && (!this.eventItemsMatch(currentItem, scheduledItem) || assetListIndex !== undefined && assetId !== interstitial.assetList[assetListIndex].identifier)) {
26143        var _this$detachedData3;
26144        const playingAssetListIndex = interstitial.findAssetIndex(playingAsset);
26145        this.log(`INTERSTITIAL_ASSET_ENDED ${playingAssetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`);
26146        this.endedAsset = playingAsset;
26147        this.playingAsset = null;
26148        this.hls.trigger(Events.INTERSTITIAL_ASSET_ENDED, {
26149          asset: playingAsset,
26150          assetListIndex: playingAssetListIndex,
26151          event: interstitial,
26152          schedule: scheduleItems.slice(0),
26153          scheduleIndex: index,
26154          player
26155        });
26156        if (currentItem !== this.playingItem) {
26157          // Schedule change occured on INTERSTITIAL_ASSET_ENDED
26158          if (this.itemsMatch(currentItem, this.playingItem) &&
26159          // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
26160          !this.playingAsset // INTERSTITIAL_ASSET_ENDED side-effect
26161          ) {
26162            this.advanceAfterAssetEnded(interstitial, this.findItemIndex(this.playingItem), playingAssetListIndex);
26163          }
26164          // Navigation occured on INTERSTITIAL_ASSET_ENDED
26165          return;
26166        }
26167        this.retreiveMediaSource(assetId, scheduledItem);
26168        if (player.media && !((_this$detachedData3 = this.detachedData) != null && _this$detachedData3.mediaSource)) {
26169          player.detachMedia();
26170        }
26171      }
26172      if (!this.eventItemsMatch(currentItem, scheduledItem)) {
26173        this.endedItem = currentItem;
26174        this.playingItem = null;
26175        this.log(`INTERSTITIAL_ENDED ${interstitial} ${segmentToString(currentItem)}`);
26176        interstitial.hasPlayed = true;
26177        this.hls.trigger(Events.INTERSTITIAL_ENDED, {
26178          event: interstitial,
26179          schedule: scheduleItems.slice(0),
26180          scheduleIndex: index
26181        });
26182        // Exiting an Interstitial
26183        if (interstitial.cue.once) {
26184          var _this$schedule3;
26185          // Remove interstitial with CUE attribute value of ONCE after it has played
26186          this.updateSchedule();
26187          const updatedScheduleItems = (_this$schedule3 = this.schedule) == null ? void 0 : _this$schedule3.items;
26188          if (scheduledItem && updatedScheduleItems) {
26189            const updatedIndex = this.findItemIndex(scheduledItem);
26190            this.advanceSchedule(updatedIndex, updatedScheduleItems, assetListIndex, currentItem, playingLastItem);
26191          }
26192          return;
26193        }
26194      }
26195    }
26196    this.advanceSchedule(index, scheduleItems, assetListIndex, currentItem, playingLastItem);
26197  }
26198  advanceSchedule(index, scheduleItems, assetListIndex, currentItem, playedLastItem) {
26199    const schedule = this.schedule;
26200    if (!schedule) {
26201      return;
26202    }
26203    const scheduledItem = scheduleItems[index] || null;
26204    const media = this.primaryMedia;
26205    // Cleanup out of range Interstitials
26206    const playerQueue = this.playerQueue;
26207    if (playerQueue.length) {
26208      playerQueue.forEach(player => {
26209        const interstitial = player.interstitial;
26210        const queuedIndex = schedule.findEventIndex(interstitial.identifier);
26211        if (queuedIndex < index || queuedIndex > index + 1) {
26212          this.clearInterstitial(interstitial, scheduledItem);
26213        }
26214      });
26215    }
26216    // Setup scheduled item
26217    if (this.isInterstitial(scheduledItem)) {
26218      this.timelinePos = Math.min(Math.max(this.timelinePos, scheduledItem.start), scheduledItem.end);
26219      // Handle Interstitial
26220      const interstitial = scheduledItem.event;
26221      // find asset index
26222      if (assetListIndex === undefined) {
26223        assetListIndex = schedule.findAssetIndex(interstitial, this.timelinePos);
26224        const assetIndexCandidate = getNextAssetIndex(interstitial, assetListIndex - 1);
26225        if (interstitial.isAssetPastPlayoutLimit(assetIndexCandidate) || interstitial.appendInPlace && this.timelinePos === scheduledItem.end) {
26226          this.advanceAfterAssetEnded(interstitial, index, assetListIndex);
26227          return;
26228        }
26229        assetListIndex = assetIndexCandidate;
26230      }
26231      // Ensure Interstitial is enqueued
26232      const waitingItem = this.waitingItem;
26233      if (!this.assetsBuffered(scheduledItem, media)) {
26234        this.setBufferingItem(scheduledItem);
26235      }
26236      let player = this.preloadAssets(interstitial, assetListIndex);
26237      if (!this.eventItemsMatch(scheduledItem, waitingItem || currentItem)) {
26238        this.waitingItem = scheduledItem;
26239        this.log(`INTERSTITIAL_STARTED ${segmentToString(scheduledItem)} ${interstitial.appendInPlace ? 'append in place' : ''}`);
26240        this.hls.trigger(Events.INTERSTITIAL_STARTED, {
26241          event: interstitial,
26242          schedule: scheduleItems.slice(0),
26243          scheduleIndex: index
26244        });
26245      }
26246      if (!interstitial.assetListLoaded) {
26247        // Waiting at end of primary content segment
26248        // Expect setSchedulePosition to be called again once ASSET-LIST is loaded
26249        this.log(`Waiting for ASSET-LIST to complete loading ${interstitial}`);
26250        return;
26251      }
26252      if (interstitial.assetListLoader) {
26253        interstitial.assetListLoader.destroy();
26254        interstitial.assetListLoader = undefined;
26255      }
26256      if (!media) {
26257        this.log(`Waiting for attachMedia to start Interstitial ${interstitial}`);
26258        return;
26259      }
26260      // Update schedule and asset list position now that it can start
26261      this.waitingItem = this.endedItem = null;
26262      this.playingItem = scheduledItem;
26263
26264      // If asset-list is empty or missing asset index, advance to next item
26265      const assetItem = interstitial.assetList[assetListIndex];
26266      if (!assetItem) {
26267        this.advanceAfterAssetEnded(interstitial, index, assetListIndex || 0);
26268        return;
26269      }
26270
26271      // Start Interstitial Playback
26272      if (!player) {
26273        player = this.getAssetPlayer(assetItem.identifier);
26274      }
26275      if (player === null || player.destroyed) {
26276        const assetListLength = interstitial.assetList.length;
26277        this.warn(`asset ${assetListIndex + 1}/${assetListLength} player destroyed ${interstitial}`);
26278        player = this.createAssetPlayer(interstitial, assetItem, assetListIndex);
26279        player.loadSource();
26280      }
26281      if (!this.eventItemsMatch(scheduledItem, this.bufferingItem)) {
26282        if (interstitial.appendInPlace && this.isAssetBuffered(assetItem)) {
26283          return;
26284        }
26285      }
26286      this.startAssetPlayer(player, assetListIndex, scheduleItems, index, media);
26287      if (this.shouldPlay) {
26288        playWithCatch(player.media);
26289      }
26290    } else if (scheduledItem) {
26291      this.resumePrimary(scheduledItem, index, currentItem);
26292      if (this.shouldPlay) {
26293        playWithCatch(this.hls.media);
26294      }
26295    } else if (playedLastItem && this.isInterstitial(currentItem)) {
26296      // Maintain playingItem state at end of schedule (setSchedulePosition(-1) called to end program)
26297      // this allows onSeeking handler to update schedule position
26298      this.endedItem = null;
26299      this.playingItem = currentItem;
26300      if (!currentItem.event.appendInPlace) {
26301        // Media must be re-attached to resume primary schedule if not sharing source
26302        this.attachPrimary(schedule.durations.primary, null);
26303      }
26304    }
26305  }
26306  get playbackDisabled() {
26307    return this.hls.config.enableInterstitialPlayback === false;
26308  }
26309  get primaryDetails() {
26310    var _this$mediaSelection;
26311    return (_this$mediaSelection = this.mediaSelection) == null ? void 0 : _this$mediaSelection.main.details;
26312  }
26313  get primaryLive() {
26314    var _this$primaryDetails;
26315    return !!((_this$primaryDetails = this.primaryDetails) != null && _this$primaryDetails.live);
26316  }
26317  resumePrimary(scheduledItem, index, fromItem) {
26318    var _this$detachedData4, _this$schedule4;
26319    this.playingItem = scheduledItem;
26320    this.playingAsset = this.endedAsset = null;
26321    this.waitingItem = this.endedItem = null;
26322    this.bufferedToItem(scheduledItem);
26323    this.log(`resuming ${segmentToString(scheduledItem)}`);
26324    if (!((_this$detachedData4 = this.detachedData) != null && _this$detachedData4.mediaSource)) {
26325      let timelinePos = this.timelinePos;
26326      if (timelinePos < scheduledItem.start || timelinePos >= scheduledItem.end) {
26327        timelinePos = this.getPrimaryResumption(scheduledItem, index);
26328        this.log(timelineMessage('resumePrimary', timelinePos));
26329        this.timelinePos = timelinePos;
26330      }
26331      this.attachPrimary(timelinePos, scheduledItem);
26332    }
26333    if (!fromItem) {
26334      return;
26335    }
26336    const scheduleItems = (_this$schedule4 = this.schedule) == null ? void 0 : _this$schedule4.items;
26337    if (!scheduleItems) {
26338      return;
26339    }
26340    this.log(`INTERSTITIALS_PRIMARY_RESUMED ${segmentToString(scheduledItem)}`);
26341    this.hls.trigger(Events.INTERSTITIALS_PRIMARY_RESUMED, {
26342      schedule: scheduleItems.slice(0),
26343      scheduleIndex: index
26344    });
26345    this.checkBuffer();
26346  }
26347  getPrimaryResumption(scheduledItem, index) {
26348    const itemStart = scheduledItem.start;
26349    if (this.primaryLive) {
26350      const details = this.primaryDetails;
26351      if (index === 0) {
26352        return this.hls.startPosition;
26353      } else if (details && (itemStart < details.fragmentStart || itemStart > details.edge)) {
26354        return this.hls.liveSyncPosition || -1;
26355      }
26356    }
26357    return itemStart;
26358  }
26359  isAssetBuffered(asset) {
26360    const player = this.getAssetPlayer(asset.identifier);
26361    if (player != null && player.hls) {
26362      return player.hls.bufferedToEnd;
26363    }
26364    const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, this.timelinePos, 0);
26365    return bufferInfo.end + 1 >= asset.timelineStart + (asset.duration || 0);
26366  }
26367  attachPrimary(timelinePos, item, skipSeekToStartPosition) {
26368    if (item) {
26369      this.setBufferingItem(item);
26370    } else {
26371      this.bufferingItem = this.playingItem;
26372    }
26373    this.bufferingAsset = null;
26374    const media = this.primaryMedia;
26375    if (!media) {
26376      return;
26377    }
26378    const hls = this.hls;
26379    if (hls.media) {
26380      this.checkBuffer();
26381    } else {
26382      this.transferMediaTo(hls, media);
26383      if (skipSeekToStartPosition) {
26384        this.startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition);
26385      }
26386    }
26387    if (!skipSeekToStartPosition) {
26388      // Set primary position to resume time
26389      this.log(timelineMessage('attachPrimary', timelinePos));
26390      this.timelinePos = timelinePos;
26391      this.startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition);
26392    }
26393  }
26394  startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition) {
26395    var _hls$mainForwardBuffe;
26396    const hls = this.hls;
26397    if (!hls.loadingEnabled || !hls.media || Math.abs((((_hls$mainForwardBuffe = hls.mainForwardBufferInfo) == null ? void 0 : _hls$mainForwardBuffe.start) || hls.media.currentTime) - timelinePos) > 0.5) {
26398      hls.startLoad(timelinePos, skipSeekToStartPosition);
26399    } else if (!hls.bufferingEnabled) {
26400      hls.resumeBuffering();
26401    }
26402  }
26403
26404  // HLS.js event callbacks
26405  onManifestLoading() {
26406    var _this$schedule5;
26407    this.stopLoad();
26408    (_this$schedule5 = this.schedule) == null || _this$schedule5.reset();
26409    this.emptyPlayerQueue();
26410    this.clearScheduleState();
26411    this.shouldPlay = false;
26412    this.bufferedPos = this.timelinePos = -1;
26413    this.mediaSelection = this.altSelection = this.manager = this.requiredTracks = null;
26414    // BUFFER_CODECS listener added here for buffer-controller to handle it first where it adds tracks
26415    this.hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this);
26416    this.hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this);
26417  }
26418  onLevelUpdated(event, data) {
26419    if (data.level === -1 || !this.schedule) {
26420      // level was removed
26421      return;
26422    }
26423    const main = this.hls.levels[data.level];
26424    if (!main.details) {
26425      return;
26426    }
26427    const currentSelection = _objectSpread2(_objectSpread2({}, this.mediaSelection || this.altSelection), {}, {
26428      main
26429    });
26430    this.mediaSelection = currentSelection;
26431    this.schedule.parseInterstitialDateRanges(currentSelection, this.hls.config.interstitialAppendInPlace);
26432    if (!this.effectivePlayingItem && this.schedule.items) {
26433      this.checkStart();
26434    }
26435  }
26436  onAudioTrackUpdated(event, data) {
26437    const audio = this.hls.audioTracks[data.id];
26438    const previousSelection = this.mediaSelection;
26439    if (!previousSelection) {
26440      this.altSelection = _objectSpread2(_objectSpread2({}, this.altSelection), {}, {
26441        audio
26442      });
26443      return;
26444    }
26445    const currentSelection = _objectSpread2(_objectSpread2({}, previousSelection), {}, {
26446      audio
26447    });
26448    this.mediaSelection = currentSelection;
26449  }
26450  onSubtitleTrackUpdated(event, data) {
26451    const subtitles = this.hls.subtitleTracks[data.id];
26452    const previousSelection = this.mediaSelection;
26453    if (!previousSelection) {
26454      this.altSelection = _objectSpread2(_objectSpread2({}, this.altSelection), {}, {
26455        subtitles
26456      });
26457      return;
26458    }
26459    const currentSelection = _objectSpread2(_objectSpread2({}, previousSelection), {}, {
26460      subtitles
26461    });
26462    this.mediaSelection = currentSelection;
26463  }
26464  onAudioTrackSwitching(event, data) {
26465    const audioOption = getBasicSelectionOption(data);
26466    this.playerQueue.forEach(({
26467      hls
26468    }) => hls && (hls.setAudioOption(data) || hls.setAudioOption(audioOption)));
26469  }
26470  onSubtitleTrackSwitch(event, data) {
26471    const subtitleOption = getBasicSelectionOption(data);
26472    this.playerQueue.forEach(({
26473      hls
26474    }) => hls && (hls.setSubtitleOption(data) || data.id !== -1 && hls.setSubtitleOption(subtitleOption)));
26475  }
26476  onBufferCodecs(event, data) {
26477    const requiredTracks = data.tracks;
26478    if (requiredTracks) {
26479      this.requiredTracks = requiredTracks;
26480    }
26481  }
26482  onBufferAppended(event, data) {
26483    this.checkBuffer();
26484  }
26485  onBufferFlushed(event, data) {
26486    const playingItem = this.playingItem;
26487    if (playingItem && !this.itemsMatch(playingItem, this.bufferingItem) && !this.isInterstitial(playingItem)) {
26488      const timelinePos = this.timelinePos;
26489      this.bufferedPos = timelinePos;
26490      this.checkBuffer();
26491    }
26492  }
26493  onBufferedToEnd(event) {
26494    if (!this.schedule) {
26495      return;
26496    }
26497    // Buffered to post-roll
26498    const interstitialEvents = this.schedule.events;
26499    if (this.bufferedPos < Number.MAX_VALUE && interstitialEvents) {
26500      for (let i = 0; i < interstitialEvents.length; i++) {
26501        const interstitial = interstitialEvents[i];
26502        if (interstitial.cue.post) {
26503          var _this$schedule$items2;
26504          const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
26505          const item = (_this$schedule$items2 = this.schedule.items) == null ? void 0 : _this$schedule$items2[scheduleIndex];
26506          if (this.isInterstitial(item) && this.eventItemsMatch(item, this.bufferingItem)) {
26507            this.bufferedToItem(item, 0);
26508          }
26509          break;
26510        }
26511      }
26512      this.bufferedPos = Number.MAX_VALUE;
26513    }
26514  }
26515  onMediaEnded(event) {
26516    const playingItem = this.playingItem;
26517    if (!this.playingLastItem && playingItem) {
26518      const playingIndex = this.findItemIndex(playingItem);
26519      this.setSchedulePosition(playingIndex + 1);
26520    } else {
26521      this.shouldPlay = false;
26522    }
26523  }
26524  updateItem(previousItem, time) {
26525    var _this$schedule6;
26526    // find item in this.schedule.items;
26527    const items = (_this$schedule6 = this.schedule) == null ? void 0 : _this$schedule6.items;
26528    if (previousItem && items) {
26529      const index = this.findItemIndex(previousItem, time);
26530      return items[index] || null;
26531    }
26532    return null;
26533  }
26534  trimInPlace(updatedItem, itemBeforeUpdate) {
26535    if (this.isInterstitial(updatedItem) && updatedItem.event.appendInPlace && itemBeforeUpdate.end - updatedItem.end > 0.25) {
26536      updatedItem.event.assetList.forEach((asset, index) => {
26537        if (updatedItem.event.isAssetPastPlayoutLimit(index)) {
26538          this.clearAssetPlayer(asset.identifier, null);
26539        }
26540      });
26541      const flushStart = updatedItem.end + 0.25;
26542      const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, flushStart, 0);
26543      if (bufferInfo.end > flushStart || (bufferInfo.nextStart || 0) > flushStart) {
26544        this.log(`trim buffered interstitial ${segmentToString(updatedItem)} (was ${segmentToString(itemBeforeUpdate)})`);
26545        const skipSeekToStartPosition = true;
26546        this.attachPrimary(flushStart, null, skipSeekToStartPosition);
26547        this.flushFrontBuffer(flushStart);
26548      }
26549    }
26550  }
26551  itemsMatch(a, b) {
26552    return !!b && (a === b || a.event && b.event && this.eventItemsMatch(a, b) || !a.event && !b.event && this.findItemIndex(a) === this.findItemIndex(b));
26553  }
26554  eventItemsMatch(a, b) {
26555    var _b$event;
26556    return !!b && (a === b || a.event.identifier === ((_b$event = b.event) == null ? void 0 : _b$event.identifier));
26557  }
26558  findItemIndex(item, time) {
26559    return item && this.schedule ? this.schedule.findItemIndex(item, time) : -1;
26560  }
26561  updateSchedule(forceUpdate = false) {
26562    var _this$schedule7;
26563    const mediaSelection = this.mediaSelection;
26564    if (!mediaSelection) {
26565      return;
26566    }
26567    (_this$schedule7 = this.schedule) == null || _this$schedule7.updateSchedule(mediaSelection, [], forceUpdate);
26568  }
26569
26570  // Schedule buffer control
26571  checkBuffer(starved) {
26572    var _this$schedule8;
26573    const items = (_this$schedule8 = this.schedule) == null ? void 0 : _this$schedule8.items;
26574    if (!items) {
26575      return;
26576    }
26577    // Find when combined forward buffer change reaches next schedule segment
26578    const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, this.timelinePos, 0);
26579    if (starved) {
26580      this.bufferedPos = this.timelinePos;
26581    }
26582    starved || (starved = bufferInfo.len < 1);
26583    this.updateBufferedPos(bufferInfo.end, items, starved);
26584  }
26585  updateBufferedPos(bufferEnd, items, bufferIsEmpty) {
26586    const schedule = this.schedule;
26587    const bufferingItem = this.bufferingItem;
26588    if (this.bufferedPos > bufferEnd || !schedule) {
26589      return;
26590    }
26591    if (items.length === 1 && this.itemsMatch(items[0], bufferingItem)) {
26592      this.bufferedPos = bufferEnd;
26593      return;
26594    }
26595    const playingItem = this.playingItem;
26596    const playingIndex = this.findItemIndex(playingItem);
26597    let bufferEndIndex = schedule.findItemIndexAtTime(bufferEnd);
26598    if (this.bufferedPos < bufferEnd) {
26599      var _nextItemToBuffer$eve;
26600      const bufferingIndex = this.findItemIndex(bufferingItem);
26601      const nextToBufferIndex = Math.min(bufferingIndex + 1, items.length - 1);
26602      const nextItemToBuffer = items[nextToBufferIndex];
26603      if (bufferEndIndex === -1 && bufferingItem && bufferEnd >= bufferingItem.end || (_nextItemToBuffer$eve = nextItemToBuffer.event) != null && _nextItemToBuffer$eve.appendInPlace && bufferEnd + 0.01 >= nextItemToBuffer.start) {
26604        bufferEndIndex = nextToBufferIndex;
26605      }
26606      if (this.isInterstitial(bufferingItem)) {
26607        const interstitial = bufferingItem.event;
26608        if (nextToBufferIndex - playingIndex > 1 && interstitial.appendInPlace === false) {
26609          // do not advance buffering item past Interstitial that requires source reset
26610          return;
26611        }
26612        if (interstitial.assetList.length === 0 && interstitial.assetListLoader) {
26613          // do not advance buffering item past Interstitial loading asset-list
26614          return;
26615        }
26616      }
26617      this.bufferedPos = bufferEnd;
26618      if (bufferEndIndex > bufferingIndex && bufferEndIndex > playingIndex) {
26619        this.bufferedToItem(nextItemToBuffer);
26620      } else {
26621        // allow more time than distance from edge for assets to load
26622        const details = this.primaryDetails;
26623        if (this.primaryLive && details && bufferEnd > details.edge - details.targetduration && nextItemToBuffer.start < details.edge + this.hls.config.interstitialLiveLookAhead && this.isInterstitial(nextItemToBuffer)) {
26624          this.preloadAssets(nextItemToBuffer.event, 0);
26625        }
26626      }
26627    } else if (bufferIsEmpty && playingItem && !this.itemsMatch(playingItem, bufferingItem)) {
26628      if (bufferEndIndex === playingIndex) {
26629        this.bufferedToItem(playingItem);
26630      } else if (bufferEndIndex === playingIndex + 1) {
26631        this.bufferedToItem(items[bufferEndIndex]);
26632      }
26633    }
26634  }
26635  assetsBuffered(item, media) {
26636    const assetList = item.event.assetList;
26637    if (assetList.length === 0) {
26638      return false;
26639    }
26640    return !item.event.assetList.some(asset => {
26641      const player = this.getAssetPlayer(asset.identifier);
26642      return !(player != null && player.bufferedInPlaceToEnd(media));
26643    });
26644  }
26645  setBufferingItem(item) {
26646    const bufferingLast = this.bufferingItem;
26647    const schedule = this.schedule;
26648    if (!this.itemsMatch(item, bufferingLast) && schedule) {
26649      const {
26650        items,
26651        events
26652      } = schedule;
26653      if (!items || !events) {
26654        return bufferingLast;
26655      }
26656      const isInterstitial = this.isInterstitial(item);
26657      const bufferingPlayer = this.getBufferingPlayer();
26658      this.bufferingItem = item;
26659      this.bufferedPos = Math.max(item.start, Math.min(item.end, this.timelinePos));
26660      const timeRemaining = bufferingPlayer ? bufferingPlayer.remaining : bufferingLast ? bufferingLast.end - this.timelinePos : 0;
26661      this.log(`INTERSTITIALS_BUFFERED_TO_BOUNDARY ${segmentToString(item)}` + (bufferingLast ? ` (${timeRemaining.toFixed(2)} remaining)` : ''));
26662      if (!this.playbackDisabled) {
26663        if (isInterstitial) {
26664          const bufferIndex = schedule.findAssetIndex(item.event, this.bufferedPos);
26665          // primary fragment loading will exit early in base-stream-controller while `bufferingItem` is set to an Interstitial block
26666          item.event.assetList.forEach((asset, i) => {
26667            const player = this.getAssetPlayer(asset.identifier);
26668            if (player) {
26669              if (i === bufferIndex) {
26670                player.loadSource();
26671              }
26672              player.resumeBuffering();
26673            }
26674          });
26675        } else {
26676          this.hls.resumeBuffering();
26677          this.playerQueue.forEach(player => player.pauseBuffering());
26678        }
26679      }
26680      this.hls.trigger(Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY, {
26681        events: events.slice(0),
26682        schedule: items.slice(0),
26683        bufferingIndex: this.findItemIndex(item),
26684        playingIndex: this.findItemIndex(this.playingItem)
26685      });
26686    } else if (this.bufferingItem !== item) {
26687      this.bufferingItem = item;
26688    }
26689    return bufferingLast;
26690  }
26691  bufferedToItem(item, assetListIndex = 0) {
26692    const bufferingLast = this.setBufferingItem(item);
26693    if (this.playbackDisabled) {
26694      return;
26695    }
26696    if (this.isInterstitial(item)) {
26697      // Ensure asset list is loaded
26698      this.bufferedToEvent(item, assetListIndex);
26699    } else if (bufferingLast !== null) {
26700      // If primary player is detached, it is also stopped, restart loading at primary position
26701      this.bufferingAsset = null;
26702      const detachedData = this.detachedData;
26703      if (detachedData) {
26704        if (detachedData.mediaSource) {
26705          const skipSeekToStartPosition = true;
26706          this.attachPrimary(item.start, item, skipSeekToStartPosition);
26707        } else {
26708          this.preloadPrimary(item);
26709        }
26710      } else {
26711        // If not detached seek to resumption point
26712        this.preloadPrimary(item);
26713      }
26714    }
26715  }
26716  preloadPrimary(item) {
26717    const index = this.findItemIndex(item);
26718    const timelinePos = this.getPrimaryResumption(item, index);
26719    this.startLoadingPrimaryAt(timelinePos);
26720  }
26721  bufferedToEvent(item, assetListIndex) {
26722    const interstitial = item.event;
26723    const neverLoaded = interstitial.assetList.length === 0 && !interstitial.assetListLoader;
26724    const playOnce = interstitial.cue.once;
26725    if (neverLoaded || !playOnce) {
26726      // Buffered to Interstitial boundary
26727      const player = this.preloadAssets(interstitial, assetListIndex);
26728      if (player != null && player.interstitial.appendInPlace) {
26729        const media = this.primaryMedia;
26730        if (media) {
26731          this.bufferAssetPlayer(player, media);
26732        }
26733      }
26734    }
26735  }
26736  preloadAssets(interstitial, assetListIndex) {
26737    const uri = interstitial.assetUrl;
26738    const assetListLength = interstitial.assetList.length;
26739    const neverLoaded = assetListLength === 0 && !interstitial.assetListLoader;
26740    const playOnce = interstitial.cue.once;
26741    if (neverLoaded) {
26742      const timelineStart = interstitial.timelineStart;
26743      if (interstitial.appendInPlace) {
26744        var _playingItem$nextEven;
26745        const playingItem = this.playingItem;
26746        if (!this.isInterstitial(playingItem) && (playingItem == null || (_playingItem$nextEven = playingItem.nextEvent) == null ? void 0 : _playingItem$nextEven.identifier) === interstitial.identifier) {
26747          this.flushFrontBuffer(timelineStart + 0.25);
26748        }
26749      }
26750      let hlsStartOffset;
26751      let liveStartPosition = 0;
26752      if (!this.playingItem && this.primaryLive) {
26753        liveStartPosition = this.hls.startPosition;
26754        if (liveStartPosition === -1) {
26755          liveStartPosition = this.hls.liveSyncPosition || 0;
26756        }
26757      }
26758      if (liveStartPosition && !(interstitial.cue.pre || interstitial.cue.post)) {
26759        const startOffset = liveStartPosition - timelineStart;
26760        if (startOffset > 0) {
26761          hlsStartOffset = Math.round(startOffset * 1000) / 1000;
26762        }
26763      }
26764      this.log(`Load interstitial asset ${assetListIndex + 1}/${uri ? 1 : assetListLength} ${interstitial}${hlsStartOffset ? ` live-start: ${liveStartPosition} start-offset: ${hlsStartOffset}` : ''}`);
26765      if (uri) {
26766        return this.createAsset(interstitial, 0, 0, timelineStart, interstitial.duration, uri);
26767      }
26768      const assetListLoader = this.assetListLoader.loadAssetList(interstitial, hlsStartOffset);
26769      if (assetListLoader) {
26770        interstitial.assetListLoader = assetListLoader;
26771      }
26772    } else if (!playOnce && assetListLength) {
26773      // Re-buffered to Interstitial boundary, re-create asset player(s)
26774      for (let i = assetListIndex; i < assetListLength; i++) {
26775        const _asset = interstitial.assetList[i];
26776        const playerIndex = this.getAssetPlayerQueueIndex(_asset.identifier);
26777        if ((playerIndex === -1 || this.playerQueue[playerIndex].destroyed) && !_asset.error) {
26778          this.createAssetPlayer(interstitial, _asset, i);
26779        }
26780      }
26781      const asset = interstitial.assetList[assetListIndex];
26782      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
26783      if (asset) {
26784        const player = this.getAssetPlayer(asset.identifier);
26785        if (player) {
26786          player.loadSource();
26787        }
26788        return player;
26789      }
26790    }
26791    return null;
26792  }
26793  flushFrontBuffer(startOffset) {
26794    // Force queued flushing of all buffers
26795    const requiredTracks = this.requiredTracks;
26796    if (!requiredTracks) {
26797      return;
26798    }
26799    this.log(`Removing front buffer starting at ${startOffset}`);
26800    const sourceBufferNames = Object.keys(requiredTracks);
26801    sourceBufferNames.forEach(type => {
26802      this.hls.trigger(Events.BUFFER_FLUSHING, {
26803        startOffset,
26804        endOffset: Infinity,
26805        type
26806      });
26807    });
26808  }
26809
26810  // Interstitial Asset Player control
26811  getAssetPlayerQueueIndex(assetId) {
26812    const playerQueue = this.playerQueue;
26813    for (let i = 0; i < playerQueue.length; i++) {
26814      if (assetId === playerQueue[i].assetId) {
26815        return i;
26816      }
26817    }
26818    return -1;
26819  }
26820  getAssetPlayer(assetId) {
26821    const index = this.getAssetPlayerQueueIndex(assetId);
26822    return this.playerQueue[index] || null;
26823  }
26824  getBufferingPlayer() {
26825    const {
26826      playerQueue,
26827      primaryMedia
26828    } = this;
26829    if (primaryMedia) {
26830      for (let i = 0; i < playerQueue.length; i++) {
26831        if (playerQueue[i].media === primaryMedia) {
26832          return playerQueue[i];
26833        }
26834      }
26835    }
26836    return null;
26837  }
26838  createAsset(interstitial, assetListIndex, startOffset, timelineStart, duration, uri) {
26839    const assetItem = {
26840      parentIdentifier: interstitial.identifier,
26841      identifier: generateAssetIdentifier(interstitial, uri, assetListIndex),
26842      duration,
26843      startOffset,
26844      timelineStart,
26845      uri
26846    };
26847    return this.createAssetPlayer(interstitial, assetItem, assetListIndex);
26848  }
26849  createAssetPlayer(interstitial, assetItem, assetListIndex) {
26850    const primary = this.hls;
26851    const userConfig = primary.userConfig;
26852    let videoPreference = userConfig.videoPreference;
26853    const currentLevel = primary.loadLevelObj || primary.levels[primary.currentLevel];
26854    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
26855    if (videoPreference || currentLevel) {
26856      videoPreference = _extends({}, videoPreference);
26857      if (currentLevel.videoCodec) {
26858        videoPreference.videoCodec = currentLevel.videoCodec;
26859      }
26860      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
26861      if (currentLevel.videoRange) {
26862        videoPreference.allowedVideoRanges = [currentLevel.videoRange];
26863      }
26864    }
26865    const selectedAudio = primary.audioTracks[primary.audioTrack];
26866    const selectedSubtitle = primary.subtitleTracks[primary.subtitleTrack];
26867    let startPosition = 0;
26868    if (this.primaryLive || interstitial.appendInPlace) {
26869      const timePastStart = this.timelinePos - assetItem.timelineStart;
26870      if (timePastStart > 1) {
26871        const duration = assetItem.duration;
26872        if (duration && timePastStart < duration) {
26873          startPosition = timePastStart;
26874        }
26875      }
26876    }
26877    const assetId = assetItem.identifier;
26878    const playerConfig = _objectSpread2(_objectSpread2({}, userConfig), {}, {
26879      maxMaxBufferLength: Math.min(180, primary.config.maxMaxBufferLength),
26880      autoStartLoad: true,
26881      startFragPrefetch: true,
26882      primarySessionId: primary.sessionId,
26883      assetPlayerId: assetId,
26884      abrEwmaDefaultEstimate: primary.bandwidthEstimate,
26885      interstitialsController: undefined,
26886      startPosition,
26887      liveDurationInfinity: false,
26888      testBandwidth: false,
26889      videoPreference,
26890      audioPreference: selectedAudio || userConfig.audioPreference,
26891      subtitlePreference: selectedSubtitle || userConfig.subtitlePreference
26892    });
26893    // TODO: limit maxMaxBufferLength in asset players to prevent QEE
26894    if (interstitial.appendInPlace) {
26895      interstitial.appendInPlaceStarted = true;
26896      if (assetItem.timelineStart) {
26897        playerConfig.timelineOffset = assetItem.timelineStart;
26898      }
26899    }
26900    const cmcd = playerConfig.cmcd;
26901    if (cmcd != null && cmcd.sessionId && cmcd.contentId) {
26902      playerConfig.cmcd = _extends({}, cmcd, {
26903        contentId: hash(assetItem.uri)
26904      });
26905    }
26906    if (this.getAssetPlayer(assetId)) {
26907      this.warn(`Duplicate date range identifier ${interstitial} and asset ${assetId}`);
26908    }
26909    const player = new HlsAssetPlayer(this.HlsPlayerClass, playerConfig, interstitial, assetItem);
26910    this.playerQueue.push(player);
26911    interstitial.assetList[assetListIndex] = assetItem;
26912    // Listen for LevelDetails and PTS change to update duration
26913    let initialDuration = true;
26914    const updateAssetPlayerDetails = details => {
26915      if (details.live) {
26916        var _this$schedule9;
26917        const error = new Error(`Interstitials MUST be VOD assets ${interstitial}`);
26918        const errorData = {
26919          fatal: true,
26920          type: ErrorTypes.OTHER_ERROR,
26921          details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR,
26922          error
26923        };
26924        const scheduleIndex = ((_this$schedule9 = this.schedule) == null ? void 0 : _this$schedule9.findEventIndex(interstitial.identifier)) || -1;
26925        this.handleAssetItemError(errorData, interstitial, scheduleIndex, assetListIndex, error.message);
26926        return;
26927      }
26928      // Get time at end of last fragment
26929      const duration = details.edge - details.fragmentStart;
26930      const currentAssetDuration = assetItem.duration;
26931      if (initialDuration || currentAssetDuration === null || duration > currentAssetDuration) {
26932        initialDuration = false;
26933        this.log(`Interstitial asset "${assetId}" duration change ${currentAssetDuration} > ${duration}`);
26934        assetItem.duration = duration;
26935        // Update schedule with new event and asset duration
26936        this.updateSchedule();
26937      }
26938    };
26939    player.on(Events.LEVEL_UPDATED, (event, {
26940      details
26941    }) => updateAssetPlayerDetails(details));
26942    player.on(Events.LEVEL_PTS_UPDATED, (event, {
26943      details
26944    }) => updateAssetPlayerDetails(details));
26945    player.on(Events.EVENT_CUE_ENTER, () => this.onInterstitialCueEnter());
26946    const onBufferCodecs = (event, data) => {
26947      const inQueuPlayer = this.getAssetPlayer(assetId);
26948      if (inQueuPlayer && data.tracks) {
26949        inQueuPlayer.off(Events.BUFFER_CODECS, onBufferCodecs);
26950        inQueuPlayer.tracks = data.tracks;
26951        const media = this.primaryMedia;
26952        if (this.bufferingAsset === inQueuPlayer.assetItem && media && !inQueuPlayer.media) {
26953          this.bufferAssetPlayer(inQueuPlayer, media);
26954        }
26955      }
26956    };
26957    player.on(Events.BUFFER_CODECS, onBufferCodecs);
26958    const bufferedToEnd = () => {
26959      var _this$schedule$items3;
26960      const inQueuPlayer = this.getAssetPlayer(assetId);
26961      this.log(`buffered to end of asset ${inQueuPlayer}`);
26962      if (!inQueuPlayer || !this.schedule) {
26963        return;
26964      }
26965      // Preload at end of asset
26966      const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
26967      const item = (_this$schedule$items3 = this.schedule.items) == null ? void 0 : _this$schedule$items3[scheduleIndex];
26968      if (this.isInterstitial(item)) {
26969        this.advanceAssetBuffering(item, assetItem);
26970      }
26971    };
26972    player.on(Events.BUFFERED_TO_END, bufferedToEnd);
26973    const endedWithAssetIndex = assetIndex => {
26974      return () => {
26975        const inQueuPlayer = this.getAssetPlayer(assetId);
26976        if (!inQueuPlayer || !this.schedule) {
26977          return;
26978        }
26979        this.shouldPlay = true;
26980        const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
26981        this.advanceAfterAssetEnded(interstitial, scheduleIndex, assetIndex);
26982      };
26983    };
26984    player.once(Events.MEDIA_ENDED, endedWithAssetIndex(assetListIndex));
26985    player.once(Events.PLAYOUT_LIMIT_REACHED, endedWithAssetIndex(Infinity));
26986    player.on(Events.ERROR, (event, data) => {
26987      if (!this.schedule) {
26988        return;
26989      }
26990      const inQueuPlayer = this.getAssetPlayer(assetId);
26991      if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) {
26992        if (inQueuPlayer != null && inQueuPlayer.appendInPlace) {
26993          this.handleInPlaceStall(interstitial);
26994          return;
26995        }
26996        this.onTimeupdate();
26997        this.checkBuffer(true);
26998        return;
26999      }
27000      this.handleAssetItemError(data, interstitial, this.schedule.findEventIndex(interstitial.identifier), assetListIndex, `Asset player error ${data.error} ${interstitial}`);
27001    });
27002    player.on(Events.DESTROYING, () => {
27003      const inQueuPlayer = this.getAssetPlayer(assetId);
27004      if (!inQueuPlayer || !this.schedule) {
27005        return;
27006      }
27007      const error = new Error(`Asset player destroyed unexpectedly ${assetId}`);
27008      const errorData = {
27009        fatal: true,
27010        type: ErrorTypes.OTHER_ERROR,
27011        details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR,
27012        error
27013      };
27014      this.handleAssetItemError(errorData, interstitial, this.schedule.findEventIndex(interstitial.identifier), assetListIndex, error.message);
27015    });
27016    this.log(`INTERSTITIAL_ASSET_PLAYER_CREATED ${eventAssetToString(assetItem)}`);
27017    this.hls.trigger(Events.INTERSTITIAL_ASSET_PLAYER_CREATED, {
27018      asset: assetItem,
27019      assetListIndex,
27020      event: interstitial,
27021      player
27022    });
27023    return player;
27024  }
27025  clearInterstitial(interstitial, toSegment) {
27026    this.clearAssetPlayers(interstitial, toSegment);
27027    // Remove asset list and resolved duration
27028    interstitial.reset();
27029  }
27030  clearAssetPlayers(interstitial, toSegment) {
27031    interstitial.assetList.forEach(asset => {
27032      this.clearAssetPlayer(asset.identifier, toSegment);
27033    });
27034  }
27035  resetAssetPlayer(assetId) {
27036    // Reset asset player so that it's timeline can be adjusted without reloading the MVP
27037    const playerIndex = this.getAssetPlayerQueueIndex(assetId);
27038    if (playerIndex !== -1) {
27039      this.log(`reset asset player "${assetId}" after error`);
27040      const player = this.playerQueue[playerIndex];
27041      this.transferMediaFromPlayer(player, null);
27042      player.resetDetails();
27043    }
27044  }
27045  clearAssetPlayer(assetId, toSegment) {
27046    const playerIndex = this.getAssetPlayerQueueIndex(assetId);
27047    if (playerIndex !== -1) {
27048      const player = this.playerQueue[playerIndex];
27049      this.log(`clear ${player} toSegment: ${toSegment ? segmentToString(toSegment) : toSegment}`);
27050      this.transferMediaFromPlayer(player, toSegment);
27051      this.playerQueue.splice(playerIndex, 1);
27052      player.destroy();
27053    }
27054  }
27055  emptyPlayerQueue() {
27056    let player;
27057    while (player = this.playerQueue.pop()) {
27058      player.destroy();
27059    }
27060    this.playerQueue = [];
27061  }
27062  startAssetPlayer(player, assetListIndex, scheduleItems, scheduleIndex, media) {
27063    const {
27064      interstitial,
27065      assetItem,
27066      assetId
27067    } = player;
27068    const assetListLength = interstitial.assetList.length;
27069    const playingAsset = this.playingAsset;
27070    this.endedAsset = null;
27071    this.playingAsset = assetItem;
27072    if (!playingAsset || playingAsset.identifier !== assetId) {
27073      if (playingAsset) {
27074        // Exiting another Interstitial asset
27075        this.clearAssetPlayer(playingAsset.identifier, scheduleItems[scheduleIndex]);
27076        delete playingAsset.error;
27077      }
27078      this.log(`INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${eventAssetToString(assetItem)}`);
27079      this.hls.trigger(Events.INTERSTITIAL_ASSET_STARTED, {
27080        asset: assetItem,
27081        assetListIndex,
27082        event: interstitial,
27083        schedule: scheduleItems.slice(0),
27084        scheduleIndex,
27085        player
27086      });
27087    }
27088
27089    // detach media and attach to interstitial player if it does not have another element attached
27090    this.bufferAssetPlayer(player, media);
27091  }
27092  bufferAssetPlayer(player, media) {
27093    var _this$schedule$items4, _this$detachedData5;
27094    if (!this.schedule) {
27095      return;
27096    }
27097    const {
27098      interstitial,
27099      assetItem
27100    } = player;
27101    const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier);
27102    const item = (_this$schedule$items4 = this.schedule.items) == null ? void 0 : _this$schedule$items4[scheduleIndex];
27103    if (!item) {
27104      return;
27105    }
27106    player.loadSource();
27107    this.setBufferingItem(item);
27108    this.bufferingAsset = assetItem;
27109    const bufferingPlayer = this.getBufferingPlayer();
27110    if (bufferingPlayer === player) {
27111      return;
27112    }
27113    const appendInPlaceNext = interstitial.appendInPlace;
27114    if (appendInPlaceNext && (bufferingPlayer == null ? void 0 : bufferingPlayer.interstitial.appendInPlace) === false) {
27115      // Media is detached and not available to append in place
27116      return;
27117    }
27118    const activeTracks = (bufferingPlayer == null ? void 0 : bufferingPlayer.tracks) || ((_this$detachedData5 = this.detachedData) == null ? void 0 : _this$detachedData5.tracks) || this.requiredTracks;
27119    if (appendInPlaceNext && assetItem !== this.playingAsset) {
27120      // Do not buffer another item if tracks are unknown or incompatible
27121      if (!player.tracks) {
27122        this.log(`Waiting for track info before buffering ${player}`);
27123        return;
27124      }
27125      if (activeTracks && !isCompatibleTrackChange(activeTracks, player.tracks)) {
27126        const error = new Error(`Asset ${eventAssetToString(assetItem)} SourceBuffer tracks ('${Object.keys(player.tracks)}') are not compatible with primary content tracks ('${Object.keys(activeTracks)}')`);
27127        const errorData = {
27128          fatal: true,
27129          type: ErrorTypes.OTHER_ERROR,
27130          details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR,
27131          error
27132        };
27133        const assetListIndex = interstitial.findAssetIndex(assetItem);
27134        this.handleAssetItemError(errorData, interstitial, scheduleIndex, assetListIndex, error.message);
27135        return;
27136      }
27137    }
27138    this.transferMediaTo(player, media);
27139  }
27140  handleInPlaceStall(interstitial) {
27141    const schedule = this.schedule;
27142    const media = this.primaryMedia;
27143    if (!schedule || !media) {
27144      return;
27145    }
27146    const currentTime = media.currentTime;
27147    const foundAssetIndex = schedule.findAssetIndex(interstitial, currentTime);
27148    const stallingAsset = interstitial.assetList[foundAssetIndex];
27149    if (stallingAsset) {
27150      const player = this.getAssetPlayer(stallingAsset.identifier);
27151      if (player) {
27152        const assetCurrentTime = player.currentTime || currentTime - stallingAsset.timelineStart;
27153        const distanceFromEnd = player.duration - assetCurrentTime;
27154        this.warn(`Stalled at ${assetCurrentTime} of ${assetCurrentTime + distanceFromEnd} in ${player} ${interstitial} (media.currentTime: ${currentTime})`);
27155        if (assetCurrentTime && (distanceFromEnd / media.playbackRate < 0.5 || player.bufferedInPlaceToEnd(media)) && player.hls) {
27156          const scheduleIndex = schedule.findEventIndex(interstitial.identifier);
27157          this.advanceAfterAssetEnded(interstitial, scheduleIndex, foundAssetIndex);
27158        }
27159      }
27160    }
27161  }
27162  advanceInPlace(time) {
27163    const media = this.primaryMedia;
27164    if (media && media.currentTime < time) {
27165      media.currentTime = time;
27166    }
27167  }
27168  handleAssetItemError(data, interstitial, scheduleIndex, assetListIndex, errorMessage) {
27169    if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) {
27170      return;
27171    }
27172    const assetItem = interstitial.assetList[assetListIndex] || null;
27173    this.warn(`INTERSTITIAL_ASSET_ERROR ${assetItem ? eventAssetToString(assetItem) : assetItem} ${data.error}`);
27174    if (!this.schedule) {
27175      return;
27176    }
27177    const assetId = (assetItem == null ? void 0 : assetItem.identifier) || '';
27178    const playerIndex = this.getAssetPlayerQueueIndex(assetId);
27179    const player = this.playerQueue[playerIndex] || null;
27180    const items = this.schedule.items;
27181    const interstitialAssetError = _extends({}, data, {
27182      fatal: false,
27183      errorAction: createDoNothingErrorAction(true),
27184      asset: assetItem,
27185      assetListIndex,
27186      event: interstitial,
27187      schedule: items,
27188      scheduleIndex,
27189      player
27190    });
27191    this.hls.trigger(Events.INTERSTITIAL_ASSET_ERROR, interstitialAssetError);
27192    if (!data.fatal) {
27193      return;
27194    }
27195    const playingAsset = this.playingAsset;
27196    const bufferingAsset = this.bufferingAsset;
27197    const error = new Error(errorMessage);
27198    if (assetItem) {
27199      this.clearAssetPlayer(assetId, null);
27200      assetItem.error = error;
27201    }
27202
27203    // If all assets in interstitial fail, mark the interstitial with an error
27204    if (!interstitial.assetList.some(asset => !asset.error)) {
27205      interstitial.error = error;
27206    } else {
27207      // Reset level details and reload/parse media playlists to align with updated schedule
27208      for (let i = assetListIndex; i < interstitial.assetList.length; i++) {
27209        this.resetAssetPlayer(interstitial.assetList[i].identifier);
27210      }
27211    }
27212    this.updateSchedule(true);
27213    if (interstitial.error) {
27214      this.primaryFallback(interstitial);
27215    } else if (playingAsset && playingAsset.identifier === assetId) {
27216      this.advanceAfterAssetEnded(interstitial, scheduleIndex, assetListIndex);
27217    } else if (bufferingAsset && bufferingAsset.identifier === assetId && this.isInterstitial(this.bufferingItem)) {
27218      this.advanceAssetBuffering(this.bufferingItem, bufferingAsset);
27219    }
27220  }
27221  primaryFallback(interstitial) {
27222    // Fallback to Primary by on current or future events by updating schedule to skip errored interstitials/assets
27223    const flushStart = interstitial.timelineStart;
27224    const playingItem = this.effectivePlayingItem;
27225    let timelinePos = this.timelinePos;
27226    // Update schedule now that interstitial/assets are flagged with `error` for fallback
27227    if (playingItem) {
27228      this.log(`Fallback to primary from event "${interstitial.identifier}" start: ${flushStart} pos: ${timelinePos} playing: ${segmentToString(playingItem)} error: ${interstitial.error}`);
27229      if (timelinePos === -1) {
27230        timelinePos = this.hls.startPosition;
27231      }
27232      const newPlayingItem = this.updateItem(playingItem, timelinePos);
27233      if (this.itemsMatch(playingItem, newPlayingItem)) {
27234        this.clearInterstitial(interstitial, null);
27235      }
27236      if (interstitial.appendInPlace) {
27237        this.attachPrimary(flushStart, null);
27238        this.flushFrontBuffer(flushStart);
27239      }
27240    } else if (timelinePos === -1) {
27241      this.checkStart();
27242      return;
27243    }
27244    if (!this.schedule) {
27245      return;
27246    }
27247    const scheduleIndex = this.schedule.findItemIndexAtTime(timelinePos);
27248    this.setSchedulePosition(scheduleIndex);
27249  }
27250
27251  // Asset List loading
27252  onAssetListLoaded(event, data) {
27253    var _this$schedule0, _this$bufferingItem2;
27254    const interstitial = data.event;
27255    const interstitialId = interstitial.identifier;
27256    const assets = data.assetListResponse.ASSETS;
27257    if (!((_this$schedule0 = this.schedule) != null && _this$schedule0.hasEvent(interstitialId))) {
27258      // Interstitial with id was removed
27259      return;
27260    }
27261    const eventStart = interstitial.timelineStart;
27262    const previousDuration = interstitial.duration;
27263    let sumDuration = 0;
27264    assets.forEach((asset, assetListIndex) => {
27265      const duration = parseFloat(asset.DURATION);
27266      this.createAsset(interstitial, assetListIndex, sumDuration, eventStart + sumDuration, duration, asset.URI);
27267      sumDuration += duration;
27268    });
27269    interstitial.duration = sumDuration;
27270    this.log(`Loaded asset-list with duration: ${sumDuration} (was: ${previousDuration}) ${interstitial}`);
27271    const waitingItem = this.waitingItem;
27272    const waitingForItem = (waitingItem == null ? void 0 : waitingItem.event.identifier) === interstitialId;
27273
27274    // Update schedule now that asset.DURATION(s) are parsed
27275    this.updateSchedule();
27276    const bufferingEvent = (_this$bufferingItem2 = this.bufferingItem) == null ? void 0 : _this$bufferingItem2.event;
27277
27278    // If buffer reached Interstitial, start buffering first asset
27279    if (waitingForItem) {
27280      var _this$schedule$items5;
27281      // Advance schedule when waiting for asset list data to play
27282      const scheduleIndex = this.schedule.findEventIndex(interstitialId);
27283      const item = (_this$schedule$items5 = this.schedule.items) == null ? void 0 : _this$schedule$items5[scheduleIndex];
27284      if (item) {
27285        if (!this.playingItem && this.timelinePos > item.end) {
27286          // Abandon if new duration is reduced enough to land playback in primary start
27287          const index = this.schedule.findItemIndexAtTime(this.timelinePos);
27288          if (index !== scheduleIndex) {
27289            interstitial.error = new Error(`Interstitial ${assets.length ? 'no longer within playback range' : 'asset-list is empty'} ${this.timelinePos} ${interstitial}`);
27290            this.log(interstitial.error.message);
27291            this.updateSchedule(true);
27292            this.primaryFallback(interstitial);
27293            return;
27294          }
27295        }
27296        this.setBufferingItem(item);
27297      }
27298      this.setSchedulePosition(scheduleIndex);
27299    } else if ((bufferingEvent == null ? void 0 : bufferingEvent.identifier) === interstitialId) {
27300      const assetItem = interstitial.assetList[0];
27301      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
27302      if (assetItem) {
27303        const player = this.getAssetPlayer(assetItem.identifier);
27304        if (bufferingEvent.appendInPlace) {
27305          // If buffering (but not playback) has reached this item transfer media-source
27306          const media = this.primaryMedia;
27307          if (player && media) {
27308            this.bufferAssetPlayer(player, media);
27309          }
27310        } else if (player) {
27311          player.loadSource();
27312        }
27313      }
27314    }
27315  }
27316  onError(event, data) {
27317    if (!this.schedule) {
27318      return;
27319    }
27320    switch (data.details) {
27321      case ErrorDetails.ASSET_LIST_PARSING_ERROR:
27322      case ErrorDetails.ASSET_LIST_LOAD_ERROR:
27323      case ErrorDetails.ASSET_LIST_LOAD_TIMEOUT:
27324        {
27325          const interstitial = data.interstitial;
27326          if (interstitial) {
27327            this.updateSchedule(true);
27328            this.primaryFallback(interstitial);
27329          }
27330          break;
27331        }
27332      case ErrorDetails.BUFFER_STALLED_ERROR:
27333        {
27334          const stallingItem = this.endedItem || this.waitingItem || this.playingItem;
27335          if (this.isInterstitial(stallingItem) && stallingItem.event.appendInPlace) {
27336            this.handleInPlaceStall(stallingItem.event);
27337            return;
27338          }
27339          this.log(`Primary player stall @${this.timelinePos} bufferedPos: ${this.bufferedPos}`);
27340          this.onTimeupdate();
27341          this.checkBuffer(true);
27342          break;
27343        }
27344    }
27345  }
27346}
27347
27348const TICK_INTERVAL$2 = 500; // how often to tick in ms
27349
27350class SubtitleStreamController extends BaseStreamController {
27351  constructor(hls, fragmentTracker, keyLoader) {
27352    super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
27353    this.currentTrackId = -1;
27354    this.tracksBuffered = [];
27355    this.mainDetails = null;
27356    this.registerListeners();
27357  }
27358  onHandlerDestroying() {
27359    this.unregisterListeners();
27360    super.onHandlerDestroying();
27361    this.mainDetails = null;
27362  }
27363  registerListeners() {
27364    super.registerListeners();
27365    const {
27366      hls
27367    } = this;
27368    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27369    hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
27370    hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
27371    hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
27372    hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
27373    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
27374  }
27375  unregisterListeners() {
27376    super.unregisterListeners();
27377    const {
27378      hls
27379    } = this;
27380    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
27381    hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
27382    hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
27383    hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
27384    hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this);
27385    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
27386  }
27387  startLoad(startPosition, skipSeekToStartPosition) {
27388    this.stopLoad();
27389    this.state = State.IDLE;
27390    this.setInterval(TICK_INTERVAL$2);
27391    this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset;
27392    this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
27393    this.tick();
27394  }
27395  onManifestLoading() {
27396    super.onManifestLoading();
27397    this.mainDetails = null;
27398  }
27399  onMediaDetaching(event, data) {
27400    this.tracksBuffered = [];
27401    super.onMediaDetaching(event, data);
27402  }
27403  onLevelLoaded(event, data) {
27404    this.mainDetails = data.details;
27405  }
27406  onSubtitleFragProcessed(event, data) {
27407    const {
27408      frag,
27409      success
27410    } = data;
27411    if (!this.fragContextChanged(frag)) {
27412      if (isMediaFragment(frag)) {
27413        this.fragPrevious = frag;
27414      }
27415      this.state = State.IDLE;
27416    }
27417    if (!success) {
27418      return;
27419    }
27420    const buffered = this.tracksBuffered[this.currentTrackId];
27421    if (!buffered) {
27422      return;
27423    }
27424
27425    // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo
27426    // so we can re-use the logic used to detect how much has been buffered
27427    let timeRange;
27428    const fragStart = frag.start;
27429    for (let i = 0; i < buffered.length; i++) {
27430      if (fragStart >= buffered[i].start && fragStart <= buffered[i].end) {
27431        timeRange = buffered[i];
27432        break;
27433      }
27434    }
27435    const fragEnd = frag.start + frag.duration;
27436    if (timeRange) {
27437      timeRange.end = fragEnd;
27438    } else {
27439      timeRange = {
27440        start: fragStart,
27441        end: fragEnd
27442      };
27443      buffered.push(timeRange);
27444    }
27445    this.fragmentTracker.fragBuffered(frag);
27446    this.fragBufferedComplete(frag, null);
27447    if (this.media) {
27448      this.tick();
27449    }
27450  }
27451  onBufferFlushing(event, data) {
27452    const {
27453      startOffset,
27454      endOffset
27455    } = data;
27456    if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) {
27457      const endOffsetSubtitles = endOffset - 1;
27458      if (endOffsetSubtitles <= 0) {
27459        return;
27460      }
27461      data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles);
27462      this.tracksBuffered.forEach(buffered => {
27463        for (let i = 0; i < buffered.length;) {
27464          if (buffered[i].end <= endOffsetSubtitles) {
27465            buffered.shift();
27466            continue;
27467          } else if (buffered[i].start < endOffsetSubtitles) {
27468            buffered[i].start = endOffsetSubtitles;
27469          } else {
27470            break;
27471          }
27472          i++;
27473        }
27474      });
27475      this.fragmentTracker.removeFragmentsInRange(startOffset, endOffsetSubtitles, PlaylistLevelType.SUBTITLE);
27476    }
27477  }
27478
27479  // If something goes wrong, proceed to next frag, if we were processing one.
27480  onError(event, data) {
27481    const frag = data.frag;
27482    if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.SUBTITLE) {
27483      if (data.details === ErrorDetails.FRAG_GAP) {
27484        this.fragmentTracker.fragBuffered(frag, true);
27485      }
27486      if (this.fragCurrent) {
27487        this.fragCurrent.abortRequests();
27488      }
27489      if (this.state !== State.STOPPED) {
27490        this.state = State.IDLE;
27491      }
27492    }
27493  }
27494
27495  // Got all new subtitle levels.
27496  onSubtitleTracksUpdated(event, {
27497    subtitleTracks
27498  }) {
27499    if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) {
27500      this.levels = subtitleTracks.map(mediaPlaylist => new Level(mediaPlaylist));
27501      return;
27502    }
27503    this.tracksBuffered = [];
27504    this.levels = subtitleTracks.map(mediaPlaylist => {
27505      const level = new Level(mediaPlaylist);
27506      this.tracksBuffered[level.id] = [];
27507      return level;
27508    });
27509    this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE);
27510    this.fragPrevious = null;
27511    this.mediaBuffer = null;
27512  }
27513  onSubtitleTrackSwitch(event, data) {
27514    var _this$levels;
27515    this.currentTrackId = data.id;
27516    if (!((_this$levels = this.levels) != null && _this$levels.length) || this.currentTrackId === -1) {
27517      this.clearInterval();
27518      return;
27519    }
27520
27521    // Check if track has the necessary details to load fragments
27522    const currentTrack = this.levels[this.currentTrackId];
27523    if (currentTrack != null && currentTrack.details) {
27524      this.mediaBuffer = this.mediaBufferTimeRanges;
27525    } else {
27526      this.mediaBuffer = null;
27527    }
27528    if (currentTrack && this.state !== State.STOPPED) {
27529      this.setInterval(TICK_INTERVAL$2);
27530    }
27531  }
27532
27533  // Got a new set of subtitle fragments.
27534  onSubtitleTrackLoaded(event, data) {
27535    var _track$details;
27536    const {
27537      currentTrackId,
27538      levels
27539    } = this;
27540    const {
27541      details: newDetails,
27542      id: trackId
27543    } = data;
27544    if (!levels) {
27545      this.warn(`Subtitle tracks were reset while loading level ${trackId}`);
27546      return;
27547    }
27548    const track = levels[trackId];
27549    if (trackId >= levels.length || !track) {
27550      return;
27551    }
27552    this.log(`Subtitle track ${trackId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''},duration:${newDetails.totalduration}`);
27553    this.mediaBuffer = this.mediaBufferTimeRanges;
27554    let sliding = 0;
27555    if (newDetails.live || (_track$details = track.details) != null && _track$details.live) {
27556      if (newDetails.deltaUpdateFailed) {
27557        return;
27558      }
27559      const mainDetails = this.mainDetails;
27560      if (!mainDetails) {
27561        this.startFragRequested = false;
27562        return;
27563      }
27564      const mainSlidingStartFragment = mainDetails.fragments[0];
27565      if (!track.details) {
27566        if (newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) {
27567          alignMediaPlaylistByPDT(newDetails, mainDetails);
27568          sliding = newDetails.fragmentStart;
27569        } else if (mainSlidingStartFragment) {
27570          // line up live playlist with main so that fragments in range are loaded
27571          sliding = mainSlidingStartFragment.start;
27572          addSliding(newDetails, sliding);
27573        }
27574      } else {
27575        var _this$levelLastLoaded;
27576        sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
27577        if (sliding === 0 && mainSlidingStartFragment) {
27578          // realign with main when there is no overlap with last refresh
27579          sliding = mainSlidingStartFragment.start;
27580          addSliding(newDetails, sliding);
27581        }
27582      }
27583      // compute start position if we are aligned with the main playlist
27584      if (mainDetails && !this.startFragRequested) {
27585        this.setStartPosition(mainDetails, sliding);
27586      }
27587    }
27588    track.details = newDetails;
27589    this.levelLastLoaded = track;
27590    if (trackId !== currentTrackId) {
27591      return;
27592    }
27593    this.hls.trigger(Events.SUBTITLE_TRACK_UPDATED, {
27594      details: newDetails,
27595      id: trackId,
27596      groupId: data.groupId
27597    });
27598
27599    // trigger handler right now
27600    this.tick();
27601
27602    // If playlist is misaligned because of bad PDT or drift, delete details to resync with main on reload
27603    if (newDetails.live && !this.fragCurrent && this.media && this.state === State.IDLE) {
27604      const foundFrag = findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0);
27605      if (!foundFrag) {
27606        this.warn('Subtitle playlist not aligned with playback');
27607        track.details = undefined;
27608      }
27609    }
27610  }
27611  _handleFragmentLoadComplete(fragLoadedData) {
27612    const {
27613      frag,
27614      payload
27615    } = fragLoadedData;
27616    const decryptData = frag.decryptdata;
27617    const hls = this.hls;
27618    if (this.fragContextChanged(frag)) {
27619      return;
27620    }
27621    // check to see if the payload needs to be decrypted
27622    if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
27623      const startTime = performance.now();
27624      // decrypt the subtitles
27625      this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
27626        hls.trigger(Events.ERROR, {
27627          type: ErrorTypes.MEDIA_ERROR,
27628          details: ErrorDetails.FRAG_DECRYPT_ERROR,
27629          fatal: false,
27630          error: err,
27631          reason: err.message,
27632          frag
27633        });
27634        throw err;
27635      }).then(decryptedData => {
27636        const endTime = performance.now();
27637        hls.trigger(Events.FRAG_DECRYPTED, {
27638          frag,
27639          payload: decryptedData,
27640          stats: {
27641            tstart: startTime,
27642            tdecrypt: endTime
27643          }
27644        });
27645      }).catch(err => {
27646        this.warn(`${err.name}: ${err.message}`);
27647        this.state = State.IDLE;
27648      });
27649    }
27650  }
27651  doTick() {
27652    if (!this.media) {
27653      this.state = State.IDLE;
27654      return;
27655    }
27656    if (this.state === State.IDLE) {
27657      const {
27658        currentTrackId,
27659        levels
27660      } = this;
27661      const track = levels == null ? void 0 : levels[currentTrackId];
27662      if (!track || !levels.length || !track.details) {
27663        return;
27664      }
27665      if (this.waitForLive(track)) {
27666        return;
27667      }
27668      const {
27669        config
27670      } = this;
27671      const currentTime = this.getLoadPosition();
27672      const bufferedInfo = BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId] || [], currentTime, config.maxBufferHole);
27673      const {
27674        end: targetBufferTime,
27675        len: bufferLen
27676      } = bufferedInfo;
27677      const trackDetails = track.details;
27678      const maxBufLen = this.hls.maxBufferLength + trackDetails.levelTargetDuration;
27679      if (bufferLen > maxBufLen) {
27680        return;
27681      }
27682      const fragments = trackDetails.fragments;
27683      const fragLen = fragments.length;
27684      const end = trackDetails.edge;
27685      let foundFrag = null;
27686      const fragPrevious = this.fragPrevious;
27687      if (targetBufferTime < end) {
27688        const tolerance = config.maxFragLookUpTolerance;
27689        const lookupTolerance = targetBufferTime > end - tolerance ? 0 : tolerance;
27690        foundFrag = findFragmentByPTS(fragPrevious, fragments, Math.max(fragments[0].start, targetBufferTime), lookupTolerance);
27691        if (!foundFrag && fragPrevious && fragPrevious.start < fragments[0].start) {
27692          foundFrag = fragments[0];
27693        }
27694      } else {
27695        foundFrag = fragments[fragLen - 1];
27696      }
27697      foundFrag = this.filterReplacedPrimary(foundFrag, track.details);
27698      if (!foundFrag) {
27699        return;
27700      }
27701      // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment
27702      const curSNIdx = foundFrag.sn - trackDetails.startSN;
27703      const prevFrag = fragments[curSNIdx - 1];
27704      if (prevFrag && prevFrag.cc === foundFrag.cc && this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED) {
27705        foundFrag = prevFrag;
27706      }
27707      if (this.fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED) {
27708        // only load if fragment is not loaded
27709        const fragToLoad = this.mapToInitFragWhenRequired(foundFrag);
27710        if (fragToLoad) {
27711          this.loadFragment(fragToLoad, track, targetBufferTime);
27712        }
27713      }
27714    }
27715  }
27716  loadFragment(frag, level, targetBufferTime) {
27717    if (!isMediaFragment(frag)) {
27718      this._loadInitSegment(frag, level);
27719    } else {
27720      super.loadFragment(frag, level, targetBufferTime);
27721    }
27722  }
27723  get mediaBufferTimeRanges() {
27724    return new BufferableInstance(this.tracksBuffered[this.currentTrackId] || []);
27725  }
27726}
27727class BufferableInstance {
27728  constructor(timeranges) {
27729    this.buffered = void 0;
27730    const getRange = (name, index, length) => {
27731      index = index >>> 0;
27732      if (index > length - 1) {
27733        throw new DOMException(`Failed to execute '${name}' on 'TimeRanges': The index provided (${index}) is greater than the maximum bound (${length})`);
27734      }
27735      return timeranges[index][name];
27736    };
27737    this.buffered = {
27738      get length() {
27739        return timeranges.length;
27740      },
27741      end(index) {
27742        return getRange('end', index, timeranges.length);
27743      },
27744      start(index) {
27745        return getRange('start', index, timeranges.length);
27746      }
27747    };
27748  }
27749}
27750
27751/**
27752 *
27753 * This code was ported from the dash.js project at:
27754 *   https://github.com/Dash-Industry-Forum/dash.js/blob/development/externals/cea608-parser.js
27755 *   https://github.com/Dash-Industry-Forum/dash.js/commit/8269b26a761e0853bb21d78780ed945144ecdd4d#diff-71bc295a2d6b6b7093a1d3290d53a4b2
27756 *
27757 * The original copyright appears below:
27758 *
27759 * The copyright in this software is being made available under the BSD License,
27760 * included below. This software may be subject to other third party and contributor
27761 * rights, including patent rights, and no such rights are granted under this license.
27762 *
27763 * Copyright (c) 2015-2016, DASH Industry Forum.
27764 * All rights reserved.
27765 *
27766 * Redistribution and use in source and binary forms, with or without modification,
27767 * are permitted provided that the following conditions are met:
27768 *  1. Redistributions of source code must retain the above copyright notice, this
27769 *  list of conditions and the following disclaimer.
27770 *  * Redistributions in binary form must reproduce the above copyright notice,
27771 *  this list of conditions and the following disclaimer in the documentation and/or
27772 *  other materials provided with the distribution.
27773 *  2. Neither the name of Dash Industry Forum nor the names of its
27774 *  contributors may be used to endorse or promote products derived from this software
27775 *  without specific prior written permission.
27776 *
27777 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
27778 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
27779 *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27780 *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27781 *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27782 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27783 *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27784 *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27785 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27786 *  POSSIBILITY OF SUCH DAMAGE.
27787 */
27788/**
27789 *  Exceptions from regular ASCII. CodePoints are mapped to UTF-16 codes
27790 */
27791
27792const specialCea608CharsCodes = {
27793  0x2a: 0xe1,
27794  // lowercase a, acute accent
27795  0x5c: 0xe9,
27796  // lowercase e, acute accent
27797  0x5e: 0xed,
27798  // lowercase i, acute accent
27799  0x5f: 0xf3,
27800  // lowercase o, acute accent
27801  0x60: 0xfa,
27802  // lowercase u, acute accent
27803  0x7b: 0xe7,
27804  // lowercase c with cedilla
27805  0x7c: 0xf7,
27806  // division symbol
27807  0x7d: 0xd1,
27808  // uppercase N tilde
27809  0x7e: 0xf1,
27810  // lowercase n tilde
27811  0x7f: 0x2588,
27812  // Full block
27813  // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
27814  // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F
27815  // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES
27816  0x80: 0xae,
27817  // Registered symbol (R)
27818  0x81: 0xb0,
27819  // degree sign
27820  0x82: 0xbd,
27821  // 1/2 symbol
27822  0x83: 0xbf,
27823  // Inverted (open) question mark
27824  0x84: 0x2122,
27825  // Trademark symbol (TM)
27826  0x85: 0xa2,
27827  // Cents symbol
27828  0x86: 0xa3,
27829  // Pounds sterling
27830  0x87: 0x266a,
27831  // Music 8'th note
27832  0x88: 0xe0,
27833  // lowercase a, grave accent
27834  0x89: 0x20,
27835  // transparent space (regular)
27836  0x8a: 0xe8,
27837  // lowercase e, grave accent
27838  0x8b: 0xe2,
27839  // lowercase a, circumflex accent
27840  0x8c: 0xea,
27841  // lowercase e, circumflex accent
27842  0x8d: 0xee,
27843  // lowercase i, circumflex accent
27844  0x8e: 0xf4,
27845  // lowercase o, circumflex accent
27846  0x8f: 0xfb,
27847  // lowercase u, circumflex accent
27848  // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
27849  // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F
27850  0x90: 0xc1,
27851  // capital letter A with acute
27852  0x91: 0xc9,
27853  // capital letter E with acute
27854  0x92: 0xd3,
27855  // capital letter O with acute
27856  0x93: 0xda,
27857  // capital letter U with acute
27858  0x94: 0xdc,
27859  // capital letter U with diaresis
27860  0x95: 0xfc,
27861  // lowercase letter U with diaeresis
27862  0x96: 0x2018,
27863  // opening single quote
27864  0x97: 0xa1,
27865  // inverted exclamation mark
27866  0x98: 0x2a,
27867  // asterisk
27868  0x99: 0x2019,
27869  // closing single quote
27870  0x9a: 0x2501,
27871  // box drawings heavy horizontal
27872  0x9b: 0xa9,
27873  // copyright sign
27874  0x9c: 0x2120,
27875  // Service mark
27876  0x9d: 0x2022,
27877  // (round) bullet
27878  0x9e: 0x201c,
27879  // Left double quotation mark
27880  0x9f: 0x201d,
27881  // Right double quotation mark
27882  0xa0: 0xc0,
27883  // uppercase A, grave accent
27884  0xa1: 0xc2,
27885  // uppercase A, circumflex
27886  0xa2: 0xc7,
27887  // uppercase C with cedilla
27888  0xa3: 0xc8,
27889  // uppercase E, grave accent
27890  0xa4: 0xca,
27891  // uppercase E, circumflex
27892  0xa5: 0xcb,
27893  // capital letter E with diaresis
27894  0xa6: 0xeb,
27895  // lowercase letter e with diaresis
27896  0xa7: 0xce,
27897  // uppercase I, circumflex
27898  0xa8: 0xcf,
27899  // uppercase I, with diaresis
27900  0xa9: 0xef,
27901  // lowercase i, with diaresis
27902  0xaa: 0xd4,
27903  // uppercase O, circumflex
27904  0xab: 0xd9,
27905  // uppercase U, grave accent
27906  0xac: 0xf9,
27907  // lowercase u, grave accent
27908  0xad: 0xdb,
27909  // uppercase U, circumflex
27910  0xae: 0xab,
27911  // left-pointing double angle quotation mark
27912  0xaf: 0xbb,
27913  // right-pointing double angle quotation mark
27914  // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
27915  // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F
27916  0xb0: 0xc3,
27917  // Uppercase A, tilde
27918  0xb1: 0xe3,
27919  // Lowercase a, tilde
27920  0xb2: 0xcd,
27921  // Uppercase I, acute accent
27922  0xb3: 0xcc,
27923  // Uppercase I, grave accent
27924  0xb4: 0xec,
27925  // Lowercase i, grave accent
27926  0xb5: 0xd2,
27927  // Uppercase O, grave accent
27928  0xb6: 0xf2,
27929  // Lowercase o, grave accent
27930  0xb7: 0xd5,
27931  // Uppercase O, tilde
27932  0xb8: 0xf5,
27933  // Lowercase o, tilde
27934  0xb9: 0x7b,
27935  // Open curly brace
27936  0xba: 0x7d,
27937  // Closing curly brace
27938  0xbb: 0x5c,
27939  // Backslash
27940  0xbc: 0x5e,
27941  // Caret
27942  0xbd: 0x5f,
27943  // Underscore
27944  0xbe: 0x7c,
27945  // Pipe (vertical line)
27946  0xbf: 0x223c,
27947  // Tilde operator
27948  0xc0: 0xc4,
27949  // Uppercase A, umlaut
27950  0xc1: 0xe4,
27951  // Lowercase A, umlaut
27952  0xc2: 0xd6,
27953  // Uppercase O, umlaut
27954  0xc3: 0xf6,
27955  // Lowercase o, umlaut
27956  0xc4: 0xdf,
27957  // Esszett (sharp S)
27958  0xc5: 0xa5,
27959  // Yen symbol
27960  0xc6: 0xa4,
27961  // Generic currency sign
27962  0xc7: 0x2503,
27963  // Box drawings heavy vertical
27964  0xc8: 0xc5,
27965  // Uppercase A, ring
27966  0xc9: 0xe5,
27967  // Lowercase A, ring
27968  0xca: 0xd8,
27969  // Uppercase O, stroke
27970  0xcb: 0xf8,
27971  // Lowercase o, strok
27972  0xcc: 0x250f,
27973  // Box drawings heavy down and right
27974  0xcd: 0x2513,
27975  // Box drawings heavy down and left
27976  0xce: 0x2517,
27977  // Box drawings heavy up and right
27978  0xcf: 0x251b // Box drawings heavy up and left
27979};
27980
27981/**
27982 * Utils
27983 */
27984const getCharForByte = byte => String.fromCharCode(specialCea608CharsCodes[byte] || byte);
27985const NR_ROWS = 15;
27986const NR_COLS = 100;
27987// Tables to look up row from PAC data
27988const rowsLowCh1 = {
27989  0x11: 1,
27990  0x12: 3,
27991  0x15: 5,
27992  0x16: 7,
27993  0x17: 9,
27994  0x10: 11,
27995  0x13: 12,
27996  0x14: 14
27997};
27998const rowsHighCh1 = {
27999  0x11: 2,
28000  0x12: 4,
28001  0x15: 6,
28002  0x16: 8,
28003  0x17: 10,
28004  0x13: 13,
28005  0x14: 15
28006};
28007const rowsLowCh2 = {
28008  0x19: 1,
28009  0x1a: 3,
28010  0x1d: 5,
28011  0x1e: 7,
28012  0x1f: 9,
28013  0x18: 11,
28014  0x1b: 12,
28015  0x1c: 14
28016};
28017const rowsHighCh2 = {
28018  0x19: 2,
28019  0x1a: 4,
28020  0x1d: 6,
28021  0x1e: 8,
28022  0x1f: 10,
28023  0x1b: 13,
28024  0x1c: 15
28025};
28026const backgroundColors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'black', 'transparent'];
28027class CaptionsLogger {
28028  constructor() {
28029    this.time = null;
28030    this.verboseLevel = 0;
28031  }
28032  log(severity, msg) {
28033    if (this.verboseLevel >= severity) {
28034      const m = typeof msg === 'function' ? msg() : msg;
28035      logger.log(`${this.time} [${severity}] ${m}`);
28036    }
28037  }
28038}
28039const numArrayToHexArray = function numArrayToHexArray(numArray) {
28040  const hexArray = [];
28041  for (let j = 0; j < numArray.length; j++) {
28042    hexArray.push(numArray[j].toString(16));
28043  }
28044  return hexArray;
28045};
28046class PenState {
28047  constructor() {
28048    this.foreground = 'white';
28049    this.underline = false;
28050    this.italics = false;
28051    this.background = 'black';
28052    this.flash = false;
28053  }
28054  reset() {
28055    this.foreground = 'white';
28056    this.underline = false;
28057    this.italics = false;
28058    this.background = 'black';
28059    this.flash = false;
28060  }
28061  setStyles(styles) {
28062    const attribs = ['foreground', 'underline', 'italics', 'background', 'flash'];
28063    for (let i = 0; i < attribs.length; i++) {
28064      const style = attribs[i];
28065      if (styles.hasOwnProperty(style)) {
28066        this[style] = styles[style];
28067      }
28068    }
28069  }
28070  isDefault() {
28071    return this.foreground === 'white' && !this.underline && !this.italics && this.background === 'black' && !this.flash;
28072  }
28073  equals(other) {
28074    return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash;
28075  }
28076  copy(newPenState) {
28077    this.foreground = newPenState.foreground;
28078    this.underline = newPenState.underline;
28079    this.italics = newPenState.italics;
28080    this.background = newPenState.background;
28081    this.flash = newPenState.flash;
28082  }
28083  toString() {
28084    return 'color=' + this.foreground + ', underline=' + this.underline + ', italics=' + this.italics + ', background=' + this.background + ', flash=' + this.flash;
28085  }
28086}
28087
28088/**
28089 * Unicode character with styling and background.
28090 * @constructor
28091 */
28092class StyledUnicodeChar {
28093  constructor() {
28094    this.uchar = ' ';
28095    this.penState = new PenState();
28096  }
28097  reset() {
28098    this.uchar = ' ';
28099    this.penState.reset();
28100  }
28101  setChar(uchar, newPenState) {
28102    this.uchar = uchar;
28103    this.penState.copy(newPenState);
28104  }
28105  setPenState(newPenState) {
28106    this.penState.copy(newPenState);
28107  }
28108  equals(other) {
28109    return this.uchar === other.uchar && this.penState.equals(other.penState);
28110  }
28111  copy(newChar) {
28112    this.uchar = newChar.uchar;
28113    this.penState.copy(newChar.penState);
28114  }
28115  isEmpty() {
28116    return this.uchar === ' ' && this.penState.isDefault();
28117  }
28118}
28119
28120/**
28121 * CEA-608 row consisting of NR_COLS instances of StyledUnicodeChar.
28122 * @constructor
28123 */
28124class Row {
28125  constructor(logger) {
28126    this.chars = [];
28127    this.pos = 0;
28128    this.currPenState = new PenState();
28129    this.cueStartTime = null;
28130    this.logger = void 0;
28131    for (let i = 0; i < NR_COLS; i++) {
28132      this.chars.push(new StyledUnicodeChar());
28133    }
28134    this.logger = logger;
28135  }
28136  equals(other) {
28137    for (let i = 0; i < NR_COLS; i++) {
28138      if (!this.chars[i].equals(other.chars[i])) {
28139        return false;
28140      }
28141    }
28142    return true;
28143  }
28144  copy(other) {
28145    for (let i = 0; i < NR_COLS; i++) {
28146      this.chars[i].copy(other.chars[i]);
28147    }
28148  }
28149  isEmpty() {
28150    let empty = true;
28151    for (let i = 0; i < NR_COLS; i++) {
28152      if (!this.chars[i].isEmpty()) {
28153        empty = false;
28154        break;
28155      }
28156    }
28157    return empty;
28158  }
28159
28160  /**
28161   *  Set the cursor to a valid column.
28162   */
28163  setCursor(absPos) {
28164    if (this.pos !== absPos) {
28165      this.pos = absPos;
28166    }
28167    if (this.pos < 0) {
28168      this.logger.log(3, 'Negative cursor position ' + this.pos);
28169      this.pos = 0;
28170    } else if (this.pos > NR_COLS) {
28171      this.logger.log(3, 'Too large cursor position ' + this.pos);
28172      this.pos = NR_COLS;
28173    }
28174  }
28175
28176  /**
28177   * Move the cursor relative to current position.
28178   */
28179  moveCursor(relPos) {
28180    const newPos = this.pos + relPos;
28181    if (relPos > 1) {
28182      for (let i = this.pos + 1; i < newPos + 1; i++) {
28183        this.chars[i].setPenState(this.currPenState);
28184      }
28185    }
28186    this.setCursor(newPos);
28187  }
28188
28189  /**
28190   * Backspace, move one step back and clear character.
28191   */
28192  backSpace() {
28193    this.moveCursor(-1);
28194    this.chars[this.pos].setChar(' ', this.currPenState);
28195  }
28196  insertChar(byte) {
28197    if (byte >= 0x90) {
28198      // Extended char
28199      this.backSpace();
28200    }
28201    const char = getCharForByte(byte);
28202    if (this.pos >= NR_COLS) {
28203      this.logger.log(0, () => 'Cannot insert ' + byte.toString(16) + ' (' + char + ') at position ' + this.pos + '. Skipping it!');
28204      return;
28205    }
28206    this.chars[this.pos].setChar(char, this.currPenState);
28207    this.moveCursor(1);
28208  }
28209  clearFromPos(startPos) {
28210    let i;
28211    for (i = startPos; i < NR_COLS; i++) {
28212      this.chars[i].reset();
28213    }
28214  }
28215  clear() {
28216    this.clearFromPos(0);
28217    this.pos = 0;
28218    this.currPenState.reset();
28219  }
28220  clearToEndOfRow() {
28221    this.clearFromPos(this.pos);
28222  }
28223  getTextString() {
28224    const chars = [];
28225    let empty = true;
28226    for (let i = 0; i < NR_COLS; i++) {
28227      const char = this.chars[i].uchar;
28228      if (char !== ' ') {
28229        empty = false;
28230      }
28231      chars.push(char);
28232    }
28233    if (empty) {
28234      return '';
28235    } else {
28236      return chars.join('');
28237    }
28238  }
28239  setPenStyles(styles) {
28240    this.currPenState.setStyles(styles);
28241    const currChar = this.chars[this.pos];
28242    currChar.setPenState(this.currPenState);
28243  }
28244}
28245
28246/**
28247 * Keep a CEA-608 screen of 32x15 styled characters
28248 * @constructor
28249 */
28250class CaptionScreen {
28251  constructor(logger) {
28252    this.rows = [];
28253    this.currRow = NR_ROWS - 1;
28254    this.nrRollUpRows = null;
28255    this.lastOutputScreen = null;
28256    this.logger = void 0;
28257    for (let i = 0; i < NR_ROWS; i++) {
28258      this.rows.push(new Row(logger));
28259    }
28260    this.logger = logger;
28261  }
28262  reset() {
28263    for (let i = 0; i < NR_ROWS; i++) {
28264      this.rows[i].clear();
28265    }
28266    this.currRow = NR_ROWS - 1;
28267  }
28268  equals(other) {
28269    let equal = true;
28270    for (let i = 0; i < NR_ROWS; i++) {
28271      if (!this.rows[i].equals(other.rows[i])) {
28272        equal = false;
28273        break;
28274      }
28275    }
28276    return equal;
28277  }
28278  copy(other) {
28279    for (let i = 0; i < NR_ROWS; i++) {
28280      this.rows[i].copy(other.rows[i]);
28281    }
28282  }
28283  isEmpty() {
28284    let empty = true;
28285    for (let i = 0; i < NR_ROWS; i++) {
28286      if (!this.rows[i].isEmpty()) {
28287        empty = false;
28288        break;
28289      }
28290    }
28291    return empty;
28292  }
28293  backSpace() {
28294    const row = this.rows[this.currRow];
28295    row.backSpace();
28296  }
28297  clearToEndOfRow() {
28298    const row = this.rows[this.currRow];
28299    row.clearToEndOfRow();
28300  }
28301
28302  /**
28303   * Insert a character (without styling) in the current row.
28304   */
28305  insertChar(char) {
28306    const row = this.rows[this.currRow];
28307    row.insertChar(char);
28308  }
28309  setPen(styles) {
28310    const row = this.rows[this.currRow];
28311    row.setPenStyles(styles);
28312  }
28313  moveCursor(relPos) {
28314    const row = this.rows[this.currRow];
28315    row.moveCursor(relPos);
28316  }
28317  setCursor(absPos) {
28318    this.logger.log(2, 'setCursor: ' + absPos);
28319    const row = this.rows[this.currRow];
28320    row.setCursor(absPos);
28321  }
28322  setPAC(pacData) {
28323    this.logger.log(2, () => 'pacData = ' + stringify(pacData));
28324    let newRow = pacData.row - 1;
28325    if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) {
28326      newRow = this.nrRollUpRows - 1;
28327    }
28328
28329    // Make sure this only affects Roll-up Captions by checking this.nrRollUpRows
28330    if (this.nrRollUpRows && this.currRow !== newRow) {
28331      // clear all rows first
28332      for (let i = 0; i < NR_ROWS; i++) {
28333        this.rows[i].clear();
28334      }
28335
28336      // Copy this.nrRollUpRows rows from lastOutputScreen and place it in the newRow location
28337      // topRowIndex - the start of rows to copy (inclusive index)
28338      const topRowIndex = this.currRow + 1 - this.nrRollUpRows;
28339      // We only copy if the last position was already shown.
28340      // We use the cueStartTime value to check this.
28341      const lastOutputScreen = this.lastOutputScreen;
28342      if (lastOutputScreen) {
28343        const prevLineTime = lastOutputScreen.rows[topRowIndex].cueStartTime;
28344        const time = this.logger.time;
28345        if (prevLineTime !== null && time !== null && prevLineTime < time) {
28346          for (let i = 0; i < this.nrRollUpRows; i++) {
28347            this.rows[newRow - this.nrRollUpRows + i + 1].copy(lastOutputScreen.rows[topRowIndex + i]);
28348          }
28349        }
28350      }
28351    }
28352    this.currRow = newRow;
28353    const row = this.rows[this.currRow];
28354    if (pacData.indent !== null) {
28355      const indent = pacData.indent;
28356      const prevPos = Math.max(indent - 1, 0);
28357      row.setCursor(pacData.indent);
28358      pacData.color = row.chars[prevPos].penState.foreground;
28359    }
28360    const styles = {
28361      foreground: pacData.color,
28362      underline: pacData.underline,
28363      italics: pacData.italics,
28364      background: 'black',
28365      flash: false
28366    };
28367    this.setPen(styles);
28368  }
28369
28370  /**
28371   * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility).
28372   */
28373  setBkgData(bkgData) {
28374    this.logger.log(2, () => 'bkgData = ' + stringify(bkgData));
28375    this.backSpace();
28376    this.setPen(bkgData);
28377    this.insertChar(0x20); // Space
28378  }
28379  setRollUpRows(nrRows) {
28380    this.nrRollUpRows = nrRows;
28381  }
28382  rollUp() {
28383    if (this.nrRollUpRows === null) {
28384      this.logger.log(3, 'roll_up but nrRollUpRows not set yet');
28385      return; // Not properly setup
28386    }
28387    this.logger.log(1, () => this.getDisplayText());
28388    const topRowIndex = this.currRow + 1 - this.nrRollUpRows;
28389    const topRow = this.rows.splice(topRowIndex, 1)[0];
28390    topRow.clear();
28391    this.rows.splice(this.currRow, 0, topRow);
28392    this.logger.log(2, 'Rolling up');
28393    // this.logger.log(VerboseLevel.TEXT, this.get_display_text())
28394  }
28395
28396  /**
28397   * Get all non-empty rows with as unicode text.
28398   */
28399  getDisplayText(asOneRow) {
28400    asOneRow = asOneRow || false;
28401    const displayText = [];
28402    let text = '';
28403    let rowNr = -1;
28404    for (let i = 0; i < NR_ROWS; i++) {
28405      const rowText = this.rows[i].getTextString();
28406      if (rowText) {
28407        rowNr = i + 1;
28408        if (asOneRow) {
28409          displayText.push('Row ' + rowNr + ": '" + rowText + "'");
28410        } else {
28411          displayText.push(rowText.trim());
28412        }
28413      }
28414    }
28415    if (displayText.length > 0) {
28416      if (asOneRow) {
28417        text = '[' + displayText.join(' | ') + ']';
28418      } else {
28419        text = displayText.join('\n');
28420      }
28421    }
28422    return text;
28423  }
28424  getTextAndFormat() {
28425    return this.rows;
28426  }
28427}
28428
28429// var modes = ['MODE_ROLL-UP', 'MODE_POP-ON', 'MODE_PAINT-ON', 'MODE_TEXT'];
28430
28431class Cea608Channel {
28432  constructor(channelNumber, outputFilter, logger) {
28433    this.chNr = void 0;
28434    this.outputFilter = void 0;
28435    this.mode = void 0;
28436    this.verbose = void 0;
28437    this.displayedMemory = void 0;
28438    this.nonDisplayedMemory = void 0;
28439    this.lastOutputScreen = void 0;
28440    this.currRollUpRow = void 0;
28441    this.writeScreen = void 0;
28442    this.cueStartTime = void 0;
28443    this.logger = void 0;
28444    this.chNr = channelNumber;
28445    this.outputFilter = outputFilter;
28446    this.mode = null;
28447    this.verbose = 0;
28448    this.displayedMemory = new CaptionScreen(logger);
28449    this.nonDisplayedMemory = new CaptionScreen(logger);
28450    this.lastOutputScreen = new CaptionScreen(logger);
28451    this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
28452    this.writeScreen = this.displayedMemory;
28453    this.mode = null;
28454    this.cueStartTime = null; // Keeps track of where a cue started.
28455    this.logger = logger;
28456  }
28457  reset() {
28458    this.mode = null;
28459    this.displayedMemory.reset();
28460    this.nonDisplayedMemory.reset();
28461    this.lastOutputScreen.reset();
28462    this.outputFilter.reset();
28463    this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1];
28464    this.writeScreen = this.displayedMemory;
28465    this.mode = null;
28466    this.cueStartTime = null;
28467  }
28468  getHandler() {
28469    return this.outputFilter;
28470  }
28471  setHandler(newHandler) {
28472    this.outputFilter = newHandler;
28473  }
28474  setPAC(pacData) {
28475    this.writeScreen.setPAC(pacData);
28476  }
28477  setBkgData(bkgData) {
28478    this.writeScreen.setBkgData(bkgData);
28479  }
28480  setMode(newMode) {
28481    if (newMode === this.mode) {
28482      return;
28483    }
28484    this.mode = newMode;
28485    this.logger.log(2, () => 'MODE=' + newMode);
28486    if (this.mode === 'MODE_POP-ON') {
28487      this.writeScreen = this.nonDisplayedMemory;
28488    } else {
28489      this.writeScreen = this.displayedMemory;
28490      this.writeScreen.reset();
28491    }
28492    if (this.mode !== 'MODE_ROLL-UP') {
28493      this.displayedMemory.nrRollUpRows = null;
28494      this.nonDisplayedMemory.nrRollUpRows = null;
28495    }
28496    this.mode = newMode;
28497  }
28498  insertChars(chars) {
28499    for (let i = 0; i < chars.length; i++) {
28500      this.writeScreen.insertChar(chars[i]);
28501    }
28502    const screen = this.writeScreen === this.displayedMemory ? 'DISP' : 'NON_DISP';
28503    this.logger.log(2, () => screen + ': ' + this.writeScreen.getDisplayText(true));
28504    if (this.mode === 'MODE_PAINT-ON' || this.mode === 'MODE_ROLL-UP') {
28505      this.logger.log(1, () => 'DISPLAYED: ' + this.displayedMemory.getDisplayText(true));
28506      this.outputDataUpdate();
28507    }
28508  }
28509  ccRCL() {
28510    // Resume Caption Loading (switch mode to Pop On)
28511    this.logger.log(2, 'RCL - Resume Caption Loading');
28512    this.setMode('MODE_POP-ON');
28513  }
28514  ccBS() {
28515    // BackSpace
28516    this.logger.log(2, 'BS - BackSpace');
28517    if (this.mode === 'MODE_TEXT') {
28518      return;
28519    }
28520    this.writeScreen.backSpace();
28521    if (this.writeScreen === this.displayedMemory) {
28522      this.outputDataUpdate();
28523    }
28524  }
28525  ccAOF() {
28526    // Reserved (formerly Alarm Off)
28527  }
28528  ccAON() {
28529    // Reserved (formerly Alarm On)
28530  }
28531  ccDER() {
28532    // Delete to End of Row
28533    this.logger.log(2, 'DER- Delete to End of Row');
28534    this.writeScreen.clearToEndOfRow();
28535    this.outputDataUpdate();
28536  }
28537  ccRU(nrRows) {
28538    // Roll-Up Captions-2,3,or 4 Rows
28539    this.logger.log(2, 'RU(' + nrRows + ') - Roll Up');
28540    this.writeScreen = this.displayedMemory;
28541    this.setMode('MODE_ROLL-UP');
28542    this.writeScreen.setRollUpRows(nrRows);
28543  }
28544  ccFON() {
28545    // Flash On
28546    this.logger.log(2, 'FON - Flash On');
28547    this.writeScreen.setPen({
28548      flash: true
28549    });
28550  }
28551  ccRDC() {
28552    // Resume Direct Captioning (switch mode to PaintOn)
28553    this.logger.log(2, 'RDC - Resume Direct Captioning');
28554    this.setMode('MODE_PAINT-ON');
28555  }
28556  ccTR() {
28557    // Text Restart in text mode (not supported, however)
28558    this.logger.log(2, 'TR');
28559    this.setMode('MODE_TEXT');
28560  }
28561  ccRTD() {
28562    // Resume Text Display in Text mode (not supported, however)
28563    this.logger.log(2, 'RTD');
28564    this.setMode('MODE_TEXT');
28565  }
28566  ccEDM() {
28567    // Erase Displayed Memory
28568    this.logger.log(2, 'EDM - Erase Displayed Memory');
28569    this.displayedMemory.reset();
28570    this.outputDataUpdate(true);
28571  }
28572  ccCR() {
28573    // Carriage Return
28574    this.logger.log(2, 'CR - Carriage Return');
28575    this.writeScreen.rollUp();
28576    this.outputDataUpdate(true);
28577  }
28578  ccENM() {
28579    // Erase Non-Displayed Memory
28580    this.logger.log(2, 'ENM - Erase Non-displayed Memory');
28581    this.nonDisplayedMemory.reset();
28582  }
28583  ccEOC() {
28584    // End of Caption (Flip Memories)
28585    this.logger.log(2, 'EOC - End Of Caption');
28586    if (this.mode === 'MODE_POP-ON') {
28587      const tmp = this.displayedMemory;
28588      this.displayedMemory = this.nonDisplayedMemory;
28589      this.nonDisplayedMemory = tmp;
28590      this.writeScreen = this.nonDisplayedMemory;
28591      this.logger.log(1, () => 'DISP: ' + this.displayedMemory.getDisplayText());
28592    }
28593    this.outputDataUpdate(true);
28594  }
28595  ccTO(nrCols) {
28596    // Tab Offset 1,2, or 3 columns
28597    this.logger.log(2, 'TO(' + nrCols + ') - Tab Offset');
28598    this.writeScreen.moveCursor(nrCols);
28599  }
28600  ccMIDROW(secondByte) {
28601    // Parse MIDROW command
28602    const styles = {
28603      flash: false
28604    };
28605    styles.underline = secondByte % 2 === 1;
28606    styles.italics = secondByte >= 0x2e;
28607    if (!styles.italics) {
28608      const colorIndex = Math.floor(secondByte / 2) - 0x10;
28609      const colors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta'];
28610      styles.foreground = colors[colorIndex];
28611    } else {
28612      styles.foreground = 'white';
28613    }
28614    this.logger.log(2, 'MIDROW: ' + stringify(styles));
28615    this.writeScreen.setPen(styles);
28616  }
28617  outputDataUpdate(dispatch = false) {
28618    const time = this.logger.time;
28619    if (time === null) {
28620      return;
28621    }
28622    if (this.outputFilter) {
28623      if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) {
28624        // Start of a new cue
28625        this.cueStartTime = time;
28626      } else {
28627        if (!this.displayedMemory.equals(this.lastOutputScreen)) {
28628          this.outputFilter.newCue(this.cueStartTime, time, this.lastOutputScreen);
28629          if (dispatch && this.outputFilter.dispatchCue) {
28630            this.outputFilter.dispatchCue();
28631          }
28632          this.cueStartTime = this.displayedMemory.isEmpty() ? null : time;
28633        }
28634      }
28635      this.lastOutputScreen.copy(this.displayedMemory);
28636    }
28637  }
28638  cueSplitAtTime(t) {
28639    if (this.outputFilter) {
28640      if (!this.displayedMemory.isEmpty()) {
28641        if (this.outputFilter.newCue) {
28642          this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory);
28643        }
28644        this.cueStartTime = t;
28645      }
28646    }
28647  }
28648}
28649
28650// Will be 1 or 2 when parsing captions
28651
28652class Cea608Parser {
28653  constructor(field, out1, out2) {
28654    this.channels = void 0;
28655    this.currentChannel = 0;
28656    this.cmdHistory = createCmdHistory();
28657    this.logger = void 0;
28658    const logger = this.logger = new CaptionsLogger();
28659    this.channels = [null, new Cea608Channel(field, out1, logger), new Cea608Channel(field + 1, out2, logger)];
28660  }
28661  getHandler(channel) {
28662    return this.channels[channel].getHandler();
28663  }
28664  setHandler(channel, newHandler) {
28665    this.channels[channel].setHandler(newHandler);
28666  }
28667
28668  /**
28669   * Add data for time t in forms of list of bytes (unsigned ints). The bytes are treated as pairs.
28670   */
28671  addData(time, byteList) {
28672    this.logger.time = time;
28673    for (let i = 0; i < byteList.length; i += 2) {
28674      const a = byteList[i] & 0x7f;
28675      const b = byteList[i + 1] & 0x7f;
28676      let cmdFound = false;
28677      let charsFound = null;
28678      if (a === 0 && b === 0) {
28679        continue;
28680      } else {
28681        this.logger.log(3, () => '[' + numArrayToHexArray([byteList[i], byteList[i + 1]]) + '] -> (' + numArrayToHexArray([a, b]) + ')');
28682      }
28683      const cmdHistory = this.cmdHistory;
28684      const isControlCode = a >= 0x10 && a <= 0x1f;
28685      if (isControlCode) {
28686        // Skip redundant control codes
28687        if (hasCmdRepeated(a, b, cmdHistory)) {
28688          setLastCmd(null, null, cmdHistory);
28689          this.logger.log(3, () => 'Repeated command (' + numArrayToHexArray([a, b]) + ') is dropped');
28690          continue;
28691        }
28692        setLastCmd(a, b, this.cmdHistory);
28693        cmdFound = this.parseCmd(a, b);
28694        if (!cmdFound) {
28695          cmdFound = this.parseMidrow(a, b);
28696        }
28697        if (!cmdFound) {
28698          cmdFound = this.parsePAC(a, b);
28699        }
28700        if (!cmdFound) {
28701          cmdFound = this.parseBackgroundAttributes(a, b);
28702        }
28703      } else {
28704        setLastCmd(null, null, cmdHistory);
28705      }
28706      if (!cmdFound) {
28707        charsFound = this.parseChars(a, b);
28708        if (charsFound) {
28709          const currChNr = this.currentChannel;
28710          if (currChNr && currChNr > 0) {
28711            const channel = this.channels[currChNr];
28712            channel.insertChars(charsFound);
28713          } else {
28714            this.logger.log(2, 'No channel found yet. TEXT-MODE?');
28715          }
28716        }
28717      }
28718      if (!cmdFound && !charsFound) {
28719        this.logger.log(2, () => "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + ' orig: ' + numArrayToHexArray([byteList[i], byteList[i + 1]]));
28720      }
28721    }
28722  }
28723
28724  /**
28725   * Parse Command.
28726   * @returns True if a command was found
28727   */
28728  parseCmd(a, b) {
28729    const cond1 = (a === 0x14 || a === 0x1c || a === 0x15 || a === 0x1d) && b >= 0x20 && b <= 0x2f;
28730    const cond2 = (a === 0x17 || a === 0x1f) && b >= 0x21 && b <= 0x23;
28731    if (!(cond1 || cond2)) {
28732      return false;
28733    }
28734    const chNr = a === 0x14 || a === 0x15 || a === 0x17 ? 1 : 2;
28735    const channel = this.channels[chNr];
28736    if (a === 0x14 || a === 0x15 || a === 0x1c || a === 0x1d) {
28737      if (b === 0x20) {
28738        channel.ccRCL();
28739      } else if (b === 0x21) {
28740        channel.ccBS();
28741      } else if (b === 0x22) {
28742        channel.ccAOF();
28743      } else if (b === 0x23) {
28744        channel.ccAON();
28745      } else if (b === 0x24) {
28746        channel.ccDER();
28747      } else if (b === 0x25) {
28748        channel.ccRU(2);
28749      } else if (b === 0x26) {
28750        channel.ccRU(3);
28751      } else if (b === 0x27) {
28752        channel.ccRU(4);
28753      } else if (b === 0x28) {
28754        channel.ccFON();
28755      } else if (b === 0x29) {
28756        channel.ccRDC();
28757      } else if (b === 0x2a) {
28758        channel.ccTR();
28759      } else if (b === 0x2b) {
28760        channel.ccRTD();
28761      } else if (b === 0x2c) {
28762        channel.ccEDM();
28763      } else if (b === 0x2d) {
28764        channel.ccCR();
28765      } else if (b === 0x2e) {
28766        channel.ccENM();
28767      } else if (b === 0x2f) {
28768        channel.ccEOC();
28769      }
28770    } else {
28771      // a == 0x17 || a == 0x1F
28772      channel.ccTO(b - 0x20);
28773    }
28774    this.currentChannel = chNr;
28775    return true;
28776  }
28777
28778  /**
28779   * Parse midrow styling command
28780   */
28781  parseMidrow(a, b) {
28782    let chNr = 0;
28783    if ((a === 0x11 || a === 0x19) && b >= 0x20 && b <= 0x2f) {
28784      if (a === 0x11) {
28785        chNr = 1;
28786      } else {
28787        chNr = 2;
28788      }
28789      if (chNr !== this.currentChannel) {
28790        this.logger.log(0, 'Mismatch channel in midrow parsing');
28791        return false;
28792      }
28793      const channel = this.channels[chNr];
28794      if (!channel) {
28795        return false;
28796      }
28797      channel.ccMIDROW(b);
28798      this.logger.log(3, () => 'MIDROW (' + numArrayToHexArray([a, b]) + ')');
28799      return true;
28800    }
28801    return false;
28802  }
28803
28804  /**
28805   * Parse Preable Access Codes (Table 53).
28806   * @returns {Boolean} Tells if PAC found
28807   */
28808  parsePAC(a, b) {
28809    let row;
28810    const case1 = (a >= 0x11 && a <= 0x17 || a >= 0x19 && a <= 0x1f) && b >= 0x40 && b <= 0x7f;
28811    const case2 = (a === 0x10 || a === 0x18) && b >= 0x40 && b <= 0x5f;
28812    if (!(case1 || case2)) {
28813      return false;
28814    }
28815    const chNr = a <= 0x17 ? 1 : 2;
28816    if (b >= 0x40 && b <= 0x5f) {
28817      row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a];
28818    } else {
28819      // 0x60 <= b <= 0x7F
28820      row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a];
28821    }
28822    const channel = this.channels[chNr];
28823    if (!channel) {
28824      return false;
28825    }
28826    channel.setPAC(this.interpretPAC(row, b));
28827    this.currentChannel = chNr;
28828    return true;
28829  }
28830
28831  /**
28832   * Interpret the second byte of the pac, and return the information.
28833   * @returns pacData with style parameters
28834   */
28835  interpretPAC(row, byte) {
28836    let pacIndex;
28837    const pacData = {
28838      color: null,
28839      italics: false,
28840      indent: null,
28841      underline: false,
28842      row: row
28843    };
28844    if (byte > 0x5f) {
28845      pacIndex = byte - 0x60;
28846    } else {
28847      pacIndex = byte - 0x40;
28848    }
28849    pacData.underline = (pacIndex & 1) === 1;
28850    if (pacIndex <= 0xd) {
28851      pacData.color = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'white'][Math.floor(pacIndex / 2)];
28852    } else if (pacIndex <= 0xf) {
28853      pacData.italics = true;
28854      pacData.color = 'white';
28855    } else {
28856      pacData.indent = Math.floor((pacIndex - 0x10) / 2) * 4;
28857    }
28858    return pacData; // Note that row has zero offset. The spec uses 1.
28859  }
28860
28861  /**
28862   * Parse characters.
28863   * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise.
28864   */
28865  parseChars(a, b) {
28866    let channelNr;
28867    let charCodes = null;
28868    let charCode1 = null;
28869    if (a >= 0x19) {
28870      channelNr = 2;
28871      charCode1 = a - 8;
28872    } else {
28873      channelNr = 1;
28874      charCode1 = a;
28875    }
28876    if (charCode1 >= 0x11 && charCode1 <= 0x13) {
28877      // Special character
28878      let oneCode;
28879      if (charCode1 === 0x11) {
28880        oneCode = b + 0x50;
28881      } else if (charCode1 === 0x12) {
28882        oneCode = b + 0x70;
28883      } else {
28884        oneCode = b + 0x90;
28885      }
28886      this.logger.log(2, () => "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr);
28887      charCodes = [oneCode];
28888    } else if (a >= 0x20 && a <= 0x7f) {
28889      charCodes = b === 0 ? [a] : [a, b];
28890    }
28891    if (charCodes) {
28892      this.logger.log(3, () => 'Char codes =  ' + numArrayToHexArray(charCodes).join(','));
28893    }
28894    return charCodes;
28895  }
28896
28897  /**
28898   * Parse extended background attributes as well as new foreground color black.
28899   * @returns True if background attributes are found
28900   */
28901  parseBackgroundAttributes(a, b) {
28902    const case1 = (a === 0x10 || a === 0x18) && b >= 0x20 && b <= 0x2f;
28903    const case2 = (a === 0x17 || a === 0x1f) && b >= 0x2d && b <= 0x2f;
28904    if (!(case1 || case2)) {
28905      return false;
28906    }
28907    let index;
28908    const bkgData = {};
28909    if (a === 0x10 || a === 0x18) {
28910      index = Math.floor((b - 0x20) / 2);
28911      bkgData.background = backgroundColors[index];
28912      if (b % 2 === 1) {
28913        bkgData.background = bkgData.background + '_semi';
28914      }
28915    } else if (b === 0x2d) {
28916      bkgData.background = 'transparent';
28917    } else {
28918      bkgData.foreground = 'black';
28919      if (b === 0x2f) {
28920        bkgData.underline = true;
28921      }
28922    }
28923    const chNr = a <= 0x17 ? 1 : 2;
28924    const channel = this.channels[chNr];
28925    channel.setBkgData(bkgData);
28926    return true;
28927  }
28928
28929  /**
28930   * Reset state of parser and its channels.
28931   */
28932  reset() {
28933    for (let i = 0; i < Object.keys(this.channels).length; i++) {
28934      const channel = this.channels[i];
28935      if (channel) {
28936        channel.reset();
28937      }
28938    }
28939    setLastCmd(null, null, this.cmdHistory);
28940  }
28941
28942  /**
28943   * Trigger the generation of a cue, and the start of a new one if displayScreens are not empty.
28944   */
28945  cueSplitAtTime(t) {
28946    for (let i = 0; i < this.channels.length; i++) {
28947      const channel = this.channels[i];
28948      if (channel) {
28949        channel.cueSplitAtTime(t);
28950      }
28951    }
28952  }
28953}
28954function setLastCmd(a, b, cmdHistory) {
28955  cmdHistory.a = a;
28956  cmdHistory.b = b;
28957}
28958function hasCmdRepeated(a, b, cmdHistory) {
28959  return cmdHistory.a === a && cmdHistory.b === b;
28960}
28961function createCmdHistory() {
28962  return {
28963    a: null,
28964    b: null
28965  };
28966}
28967
28968/**
28969 * Copyright 2013 vtt.js Contributors
28970 *
28971 * Licensed under the Apache License, Version 2.0 (the 'License');
28972 * you may not use this file except in compliance with the License.
28973 * You may obtain a copy of the License at
28974 *
28975 *   http://www.apache.org/licenses/LICENSE-2.0
28976 *
28977 * Unless required by applicable law or agreed to in writing, software
28978 * distributed under the License is distributed on an 'AS IS' BASIS,
28979 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28980 * See the License for the specific language governing permissions and
28981 * limitations under the License.
28982 */
28983
28984var VTTCue = (function () {
28985  if (optionalSelf != null && optionalSelf.VTTCue) {
28986    return self.VTTCue;
28987  }
28988  const AllowedDirections = ['', 'lr', 'rl'];
28989  const AllowedAlignments = ['start', 'middle', 'end', 'left', 'right'];
28990  function isAllowedValue(allowed, value) {
28991    if (typeof value !== 'string') {
28992      return false;
28993    }
28994    // necessary for assuring the generic conforms to the Array interface
28995    if (!Array.isArray(allowed)) {
28996      return false;
28997    }
28998    // reset the type so that the next narrowing works well
28999    const lcValue = value.toLowerCase();
29000    // use the allow list to narrow the type to a specific subset of strings
29001    if (~allowed.indexOf(lcValue)) {
29002      return lcValue;
29003    }
29004    return false;
29005  }
29006  function findDirectionSetting(value) {
29007    return isAllowedValue(AllowedDirections, value);
29008  }
29009  function findAlignSetting(value) {
29010    return isAllowedValue(AllowedAlignments, value);
29011  }
29012  function extend(obj, ...rest) {
29013    let i = 1;
29014    for (; i < arguments.length; i++) {
29015      const cobj = arguments[i];
29016      for (const p in cobj) {
29017        obj[p] = cobj[p];
29018      }
29019    }
29020    return obj;
29021  }
29022  function VTTCue(startTime, endTime, text) {
29023    const cue = this;
29024    const baseObj = {
29025      enumerable: true
29026    };
29027    /**
29028     * Shim implementation specific properties. These properties are not in
29029     * the spec.
29030     */
29031
29032    // Lets us know when the VTTCue's data has changed in such a way that we need
29033    // to recompute its display state. This lets us compute its display state
29034    // lazily.
29035    cue.hasBeenReset = false;
29036
29037    /**
29038     * VTTCue and TextTrackCue properties
29039     * http://dev.w3.org/html5/webvtt/#vttcue-interface
29040     */
29041
29042    let _id = '';
29043    let _pauseOnExit = false;
29044    let _startTime = startTime;
29045    let _endTime = endTime;
29046    let _text = text;
29047    let _region = null;
29048    let _vertical = '';
29049    let _snapToLines = true;
29050    let _line = 'auto';
29051    let _lineAlign = 'start';
29052    let _position = 50;
29053    let _positionAlign = 'middle';
29054    let _size = 50;
29055    let _align = 'middle';
29056    Object.defineProperty(cue, 'id', extend({}, baseObj, {
29057      get: function () {
29058        return _id;
29059      },
29060      set: function (value) {
29061        _id = '' + value;
29062      }
29063    }));
29064    Object.defineProperty(cue, 'pauseOnExit', extend({}, baseObj, {
29065      get: function () {
29066        return _pauseOnExit;
29067      },
29068      set: function (value) {
29069        _pauseOnExit = !!value;
29070      }
29071    }));
29072    Object.defineProperty(cue, 'startTime', extend({}, baseObj, {
29073      get: function () {
29074        return _startTime;
29075      },
29076      set: function (value) {
29077        if (typeof value !== 'number') {
29078          throw new TypeError('Start time must be set to a number.');
29079        }
29080        _startTime = value;
29081        this.hasBeenReset = true;
29082      }
29083    }));
29084    Object.defineProperty(cue, 'endTime', extend({}, baseObj, {
29085      get: function () {
29086        return _endTime;
29087      },
29088      set: function (value) {
29089        if (typeof value !== 'number') {
29090          throw new TypeError('End time must be set to a number.');
29091        }
29092        _endTime = value;
29093        this.hasBeenReset = true;
29094      }
29095    }));
29096    Object.defineProperty(cue, 'text', extend({}, baseObj, {
29097      get: function () {
29098        return _text;
29099      },
29100      set: function (value) {
29101        _text = '' + value;
29102        this.hasBeenReset = true;
29103      }
29104    }));
29105
29106    // todo: implement VTTRegion polyfill?
29107    Object.defineProperty(cue, 'region', extend({}, baseObj, {
29108      get: function () {
29109        return _region;
29110      },
29111      set: function (value) {
29112        _region = value;
29113        this.hasBeenReset = true;
29114      }
29115    }));
29116    Object.defineProperty(cue, 'vertical', extend({}, baseObj, {
29117      get: function () {
29118        return _vertical;
29119      },
29120      set: function (value) {
29121        const setting = findDirectionSetting(value);
29122        // Have to check for false because the setting an be an empty string.
29123        if (setting === false) {
29124          throw new SyntaxError('An invalid or illegal string was specified.');
29125        }
29126        _vertical = setting;
29127        this.hasBeenReset = true;
29128      }
29129    }));
29130    Object.defineProperty(cue, 'snapToLines', extend({}, baseObj, {
29131      get: function () {
29132        return _snapToLines;
29133      },
29134      set: function (value) {
29135        _snapToLines = !!value;
29136        this.hasBeenReset = true;
29137      }
29138    }));
29139    Object.defineProperty(cue, 'line', extend({}, baseObj, {
29140      get: function () {
29141        return _line;
29142      },
29143      set: function (value) {
29144        if (typeof value !== 'number' && value !== 'auto') {
29145          throw new SyntaxError('An invalid number or illegal string was specified.');
29146        }
29147        _line = value;
29148        this.hasBeenReset = true;
29149      }
29150    }));
29151    Object.defineProperty(cue, 'lineAlign', extend({}, baseObj, {
29152      get: function () {
29153        return _lineAlign;
29154      },
29155      set: function (value) {
29156        const setting = findAlignSetting(value);
29157        if (!setting) {
29158          throw new SyntaxError('An invalid or illegal string was specified.');
29159        }
29160        _lineAlign = setting;
29161        this.hasBeenReset = true;
29162      }
29163    }));
29164    Object.defineProperty(cue, 'position', extend({}, baseObj, {
29165      get: function () {
29166        return _position;
29167      },
29168      set: function (value) {
29169        if (value < 0 || value > 100) {
29170          throw new Error('Position must be between 0 and 100.');
29171        }
29172        _position = value;
29173        this.hasBeenReset = true;
29174      }
29175    }));
29176    Object.defineProperty(cue, 'positionAlign', extend({}, baseObj, {
29177      get: function () {
29178        return _positionAlign;
29179      },
29180      set: function (value) {
29181        const setting = findAlignSetting(value);
29182        if (!setting) {
29183          throw new SyntaxError('An invalid or illegal string was specified.');
29184        }
29185        _positionAlign = setting;
29186        this.hasBeenReset = true;
29187      }
29188    }));
29189    Object.defineProperty(cue, 'size', extend({}, baseObj, {
29190      get: function () {
29191        return _size;
29192      },
29193      set: function (value) {
29194        if (value < 0 || value > 100) {
29195          throw new Error('Size must be between 0 and 100.');
29196        }
29197        _size = value;
29198        this.hasBeenReset = true;
29199      }
29200    }));
29201    Object.defineProperty(cue, 'align', extend({}, baseObj, {
29202      get: function () {
29203        return _align;
29204      },
29205      set: function (value) {
29206        const setting = findAlignSetting(value);
29207        if (!setting) {
29208          throw new SyntaxError('An invalid or illegal string was specified.');
29209        }
29210        _align = setting;
29211        this.hasBeenReset = true;
29212      }
29213    }));
29214
29215    /**
29216     * Other <track> spec defined properties
29217     */
29218
29219    // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
29220    cue.displayState = undefined;
29221  }
29222
29223  /**
29224   * VTTCue methods
29225   */
29226
29227  VTTCue.prototype.getCueAsHTML = function () {
29228    // Assume WebVTT.convertCueToDOMTree is on the global.
29229    const WebVTT = self.WebVTT;
29230    return WebVTT.convertCueToDOMTree(self, this.text);
29231  };
29232  // this is a polyfill hack
29233  return VTTCue;
29234})();
29235
29236/*
29237 * Source: https://github.com/mozilla/vtt.js/blob/master/dist/vtt.js
29238 */
29239
29240class StringDecoder {
29241  decode(data, options) {
29242    if (!data) {
29243      return '';
29244    }
29245    if (typeof data !== 'string') {
29246      throw new Error('Error - expected string data.');
29247    }
29248    return decodeURIComponent(encodeURIComponent(data));
29249  }
29250}
29251
29252// Try to parse input as a time stamp.
29253function parseTimeStamp(input) {
29254  function computeSeconds(h, m, s, f) {
29255    return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + parseFloat(f || 0);
29256  }
29257  const m = input.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/);
29258  if (!m) {
29259    return null;
29260  }
29261  if (parseFloat(m[2]) > 59) {
29262    // Timestamp takes the form of [hours]:[minutes].[milliseconds]
29263    // First position is hours as it's over 59.
29264    return computeSeconds(m[2], m[3], 0, m[4]);
29265  }
29266  // Timestamp takes the form of [hours (optional)]:[minutes]:[seconds].[milliseconds]
29267  return computeSeconds(m[1], m[2], m[3], m[4]);
29268}
29269
29270// A settings object holds key/value pairs and will ignore anything but the first
29271// assignment to a specific key.
29272class Settings {
29273  constructor() {
29274    this.values = Object.create(null);
29275  }
29276  // Only accept the first assignment to any key.
29277  set(k, v) {
29278    if (!this.get(k) && v !== '') {
29279      this.values[k] = v;
29280    }
29281  }
29282  // Return the value for a key, or a default value.
29283  // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
29284  // a number of possible default values as properties where 'defaultKey' is
29285  // the key of the property that will be chosen; otherwise it's assumed to be
29286  // a single value.
29287  get(k, dflt, defaultKey) {
29288    if (defaultKey) {
29289      return this.has(k) ? this.values[k] : dflt[defaultKey];
29290    }
29291    return this.has(k) ? this.values[k] : dflt;
29292  }
29293  // Check whether we have a value for a key.
29294  has(k) {
29295    return k in this.values;
29296  }
29297  // Accept a setting if its one of the given alternatives.
29298  alt(k, v, a) {
29299    for (let n = 0; n < a.length; ++n) {
29300      if (v === a[n]) {
29301        this.set(k, v);
29302        break;
29303      }
29304    }
29305  }
29306  // Accept a setting if its a valid (signed) integer.
29307  integer(k, v) {
29308    if (/^-?\d+$/.test(v)) {
29309      // integer
29310      this.set(k, parseInt(v, 10));
29311    }
29312  }
29313  // Accept a setting if its a valid percentage.
29314  percent(k, v) {
29315    if (/^([\d]{1,3})(\.[\d]*)?%$/.test(v)) {
29316      const percent = parseFloat(v);
29317      if (percent >= 0 && percent <= 100) {
29318        this.set(k, percent);
29319        return true;
29320      }
29321    }
29322    return false;
29323  }
29324}
29325
29326// Helper function to parse input into groups separated by 'groupDelim', and
29327// interpret each group as a key/value pair separated by 'keyValueDelim'.
29328function parseOptions(input, callback, keyValueDelim, groupDelim) {
29329  const groups = groupDelim ? input.split(groupDelim) : [input];
29330  for (const i in groups) {
29331    if (typeof groups[i] !== 'string') {
29332      continue;
29333    }
29334    const kv = groups[i].split(keyValueDelim);
29335    if (kv.length !== 2) {
29336      continue;
29337    }
29338    const k = kv[0];
29339    const v = kv[1];
29340    callback(k, v);
29341  }
29342}
29343const defaults = new VTTCue(0, 0, '');
29344// 'middle' was changed to 'center' in the spec: https://github.com/w3c/webvtt/pull/244
29345//  Safari doesn't yet support this change, but FF and Chrome do.
29346const center = defaults.align === 'middle' ? 'middle' : 'center';
29347function parseCue(input, cue, regionList) {
29348  // Remember the original input if we need to throw an error.
29349  const oInput = input;
29350  // 4.1 WebVTT timestamp
29351  function consumeTimeStamp() {
29352    const ts = parseTimeStamp(input);
29353    if (ts === null) {
29354      throw new Error('Malformed timestamp: ' + oInput);
29355    }
29356
29357    // Remove time stamp from input.
29358    input = input.replace(/^[^\sa-zA-Z-]+/, '');
29359    return ts;
29360  }
29361
29362  // 4.4.2 WebVTT cue settings
29363  function consumeCueSettings(input, cue) {
29364    const settings = new Settings();
29365    parseOptions(input, function (k, v) {
29366      let vals;
29367      switch (k) {
29368        case 'region':
29369          // Find the last region we parsed with the same region id.
29370          for (let i = regionList.length - 1; i >= 0; i--) {
29371            if (regionList[i].id === v) {
29372              settings.set(k, regionList[i].region);
29373              break;
29374            }
29375          }
29376          break;
29377        case 'vertical':
29378          settings.alt(k, v, ['rl', 'lr']);
29379          break;
29380        case 'line':
29381          vals = v.split(',');
29382          settings.integer(k, vals[0]);
29383          if (settings.percent(k, vals[0])) {
29384            settings.set('snapToLines', false);
29385          }
29386          settings.alt(k, vals[0], ['auto']);
29387          if (vals.length === 2) {
29388            settings.alt('lineAlign', vals[1], ['start', center, 'end']);
29389          }
29390          break;
29391        case 'position':
29392          vals = v.split(',');
29393          settings.percent(k, vals[0]);
29394          if (vals.length === 2) {
29395            settings.alt('positionAlign', vals[1], ['start', center, 'end', 'line-left', 'line-right', 'auto']);
29396          }
29397          break;
29398        case 'size':
29399          settings.percent(k, v);
29400          break;
29401        case 'align':
29402          settings.alt(k, v, ['start', center, 'end', 'left', 'right']);
29403          break;
29404      }
29405    }, /:/, /\s/);
29406
29407    // Apply default values for any missing fields.
29408    cue.region = settings.get('region', null);
29409    cue.vertical = settings.get('vertical', '');
29410    let line = settings.get('line', 'auto');
29411    if (line === 'auto' && defaults.line === -1) {
29412      // set numeric line number for Safari
29413      line = -1;
29414    }
29415    cue.line = line;
29416    cue.lineAlign = settings.get('lineAlign', 'start');
29417    cue.snapToLines = settings.get('snapToLines', true);
29418    cue.size = settings.get('size', 100);
29419    cue.align = settings.get('align', center);
29420    let position = settings.get('position', 'auto');
29421    if (position === 'auto' && defaults.position === 50) {
29422      // set numeric position for Safari
29423      position = cue.align === 'start' || cue.align === 'left' ? 0 : cue.align === 'end' || cue.align === 'right' ? 100 : 50;
29424    }
29425    cue.position = position;
29426  }
29427  function skipWhitespace() {
29428    input = input.replace(/^\s+/, '');
29429  }
29430
29431  // 4.1 WebVTT cue timings.
29432  skipWhitespace();
29433  cue.startTime = consumeTimeStamp(); // (1) collect cue start time
29434  skipWhitespace();
29435  if (input.slice(0, 3) !== '-->') {
29436    // (3) next characters must match '-->'
29437    throw new Error("Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
29438  }
29439  input = input.slice(3);
29440  skipWhitespace();
29441  cue.endTime = consumeTimeStamp(); // (5) collect cue end time
29442
29443  // 4.1 WebVTT cue settings list.
29444  skipWhitespace();
29445  consumeCueSettings(input, cue);
29446}
29447function fixLineBreaks(input) {
29448  return input.replace(/<br(?: \/)?>/gi, '\n');
29449}
29450class VTTParser {
29451  constructor() {
29452    this.state = 'INITIAL';
29453    this.buffer = '';
29454    this.decoder = new StringDecoder();
29455    this.regionList = [];
29456    this.cue = null;
29457    this.oncue = void 0;
29458    this.onparsingerror = void 0;
29459    this.onflush = void 0;
29460  }
29461  parse(data) {
29462    const _this = this;
29463
29464    // If there is no data then we won't decode it, but will just try to parse
29465    // whatever is in buffer already. This may occur in circumstances, for
29466    // example when flush() is called.
29467    if (data) {
29468      // Try to decode the data that we received.
29469      _this.buffer += _this.decoder.decode(data, {
29470        stream: true
29471      });
29472    }
29473    function collectNextLine() {
29474      let buffer = _this.buffer;
29475      let pos = 0;
29476      buffer = fixLineBreaks(buffer);
29477      while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
29478        ++pos;
29479      }
29480      const line = buffer.slice(0, pos);
29481      // Advance the buffer early in case we fail below.
29482      if (buffer[pos] === '\r') {
29483        ++pos;
29484      }
29485      if (buffer[pos] === '\n') {
29486        ++pos;
29487      }
29488      _this.buffer = buffer.slice(pos);
29489      return line;
29490    }
29491
29492    // 3.2 WebVTT metadata header syntax
29493    function parseHeader(input) {
29494      parseOptions(input, function (k, v) {
29495        // switch (k) {
29496        // case 'region':
29497        // 3.3 WebVTT region metadata header syntax
29498        // console.log('parse region', v);
29499        // parseRegion(v);
29500        // break;
29501        // }
29502      }, /:/);
29503    }
29504
29505    // 5.1 WebVTT file parsing.
29506    try {
29507      let line = '';
29508      if (_this.state === 'INITIAL') {
29509        // We can't start parsing until we have the first line.
29510        if (!/\r\n|\n/.test(_this.buffer)) {
29511          return this;
29512        }
29513        line = collectNextLine();
29514        // strip of UTF-8 BOM if any
29515        // https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
29516        const m = line.match(/^()?WEBVTT([ \t].*)?$/);
29517        if (!(m != null && m[0])) {
29518          throw new Error('Malformed WebVTT signature.');
29519        }
29520        _this.state = 'HEADER';
29521      }
29522      let alreadyCollectedLine = false;
29523      while (_this.buffer) {
29524        // We can't parse a line until we have the full line.
29525        if (!/\r\n|\n/.test(_this.buffer)) {
29526          return this;
29527        }
29528        if (!alreadyCollectedLine) {
29529          line = collectNextLine();
29530        } else {
29531          alreadyCollectedLine = false;
29532        }
29533        switch (_this.state) {
29534          case 'HEADER':
29535            // 13-18 - Allow a header (metadata) under the WEBVTT line.
29536            if (/:/.test(line)) {
29537              parseHeader(line);
29538            } else if (!line) {
29539              // An empty line terminates the header and starts the body (cues).
29540              _this.state = 'ID';
29541            }
29542            continue;
29543          case 'NOTE':
29544            // Ignore NOTE blocks.
29545            if (!line) {
29546              _this.state = 'ID';
29547            }
29548            continue;
29549          case 'ID':
29550            // Check for the start of NOTE blocks.
29551            if (/^NOTE($|[ \t])/.test(line)) {
29552              _this.state = 'NOTE';
29553              break;
29554            }
29555            // 19-29 - Allow any number of line terminators, then initialize new cue values.
29556            if (!line) {
29557              continue;
29558            }
29559            _this.cue = new VTTCue(0, 0, '');
29560            _this.state = 'CUE';
29561            // 30-39 - Check if self line contains an optional identifier or timing data.
29562            if (line.indexOf('-->') === -1) {
29563              _this.cue.id = line;
29564              continue;
29565            }
29566          // Process line as start of a cue.
29567          /* falls through */
29568          case 'CUE':
29569            // 40 - Collect cue timings and settings.
29570            if (!_this.cue) {
29571              _this.state = 'BADCUE';
29572              continue;
29573            }
29574            try {
29575              parseCue(line, _this.cue, _this.regionList);
29576            } catch (e) {
29577              // In case of an error ignore rest of the cue.
29578              _this.cue = null;
29579              _this.state = 'BADCUE';
29580              continue;
29581            }
29582            _this.state = 'CUETEXT';
29583            continue;
29584          case 'CUETEXT':
29585            {
29586              const hasSubstring = line.indexOf('-->') !== -1;
29587              // 34 - If we have an empty line then report the cue.
29588              // 35 - If we have the special substring '-->' then report the cue,
29589              // but do not collect the line as we need to process the current
29590              // one as a new cue.
29591              if (!line || hasSubstring && (alreadyCollectedLine = true)) {
29592                // We are done parsing self cue.
29593                if (_this.oncue && _this.cue) {
29594                  _this.oncue(_this.cue);
29595                }
29596                _this.cue = null;
29597                _this.state = 'ID';
29598                continue;
29599              }
29600              if (_this.cue === null) {
29601                continue;
29602              }
29603              if (_this.cue.text) {
29604                _this.cue.text += '\n';
29605              }
29606              _this.cue.text += line;
29607            }
29608            continue;
29609          case 'BADCUE':
29610            // 54-62 - Collect and discard the remaining cue.
29611            if (!line) {
29612              _this.state = 'ID';
29613            }
29614        }
29615      }
29616    } catch (e) {
29617      // If we are currently parsing a cue, report what we have.
29618      if (_this.state === 'CUETEXT' && _this.cue && _this.oncue) {
29619        _this.oncue(_this.cue);
29620      }
29621      _this.cue = null;
29622      // Enter BADWEBVTT state if header was not parsed correctly otherwise
29623      // another exception occurred so enter BADCUE state.
29624      _this.state = _this.state === 'INITIAL' ? 'BADWEBVTT' : 'BADCUE';
29625    }
29626    return this;
29627  }
29628  flush() {
29629    const _this = this;
29630    try {
29631      // Finish decoding the stream.
29632      // _this.buffer += _this.decoder.decode();
29633      // Synthesize the end of the current cue or region.
29634      if (_this.cue || _this.state === 'HEADER') {
29635        _this.buffer += '\n\n';
29636        _this.parse();
29637      }
29638      // If we've flushed, parsed, and we're still on the INITIAL state then
29639      // that means we don't have enough of the stream to parse the first
29640      // line.
29641      if (_this.state === 'INITIAL' || _this.state === 'BADWEBVTT') {
29642        throw new Error('Malformed WebVTT signature.');
29643      }
29644    } catch (e) {
29645      if (_this.onparsingerror) {
29646        _this.onparsingerror(e);
29647      }
29648    }
29649    if (_this.onflush) {
29650      _this.onflush();
29651    }
29652    return this;
29653  }
29654}
29655
29656const LINEBREAKS = /\r\n|\n\r|\n|\r/g;
29657
29658// String.prototype.startsWith is not supported in IE11
29659const startsWith = function startsWith(inputString, searchString, position = 0) {
29660  return inputString.slice(position, position + searchString.length) === searchString;
29661};
29662const cueString2millis = function cueString2millis(timeString) {
29663  let ts = parseInt(timeString.slice(-3));
29664  const secs = parseInt(timeString.slice(-6, -4));
29665  const mins = parseInt(timeString.slice(-9, -7));
29666  const hours = timeString.length > 9 ? parseInt(timeString.substring(0, timeString.indexOf(':'))) : 0;
29667  if (!isFiniteNumber(ts) || !isFiniteNumber(secs) || !isFiniteNumber(mins) || !isFiniteNumber(hours)) {
29668    throw Error(`Malformed X-TIMESTAMP-MAP: Local:${timeString}`);
29669  }
29670  ts += 1000 * secs;
29671  ts += 60 * 1000 * mins;
29672  ts += 60 * 60 * 1000 * hours;
29673  return ts;
29674};
29675
29676// Create a unique hash id for a cue based on start/end times and text.
29677// This helps timeline-controller to avoid showing repeated captions.
29678function generateCueId(startTime, endTime, text) {
29679  return hash(startTime.toString()) + hash(endTime.toString()) + hash(text);
29680}
29681const calculateOffset = function calculateOffset(vttCCs, cc, presentationTime) {
29682  let currCC = vttCCs[cc];
29683  let prevCC = vttCCs[currCC.prevCC];
29684
29685  // This is the first discontinuity or cues have been processed since the last discontinuity
29686  // Offset = current discontinuity time
29687  if (!prevCC || !prevCC.new && currCC.new) {
29688    vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start;
29689    currCC.new = false;
29690    return;
29691  }
29692
29693  // There have been discontinuities since cues were last parsed.
29694  // Offset = time elapsed
29695  while ((_prevCC = prevCC) != null && _prevCC.new) {
29696    var _prevCC;
29697    vttCCs.ccOffset += currCC.start - prevCC.start;
29698    currCC.new = false;
29699    currCC = prevCC;
29700    prevCC = vttCCs[currCC.prevCC];
29701  }
29702  vttCCs.presentationOffset = presentationTime;
29703};
29704function parseWebVTT(vttByteArray, initPTS, vttCCs, cc, timeOffset, callBack, errorCallBack) {
29705  const parser = new VTTParser();
29706  // Convert byteArray into string, replacing any somewhat exotic linefeeds with "\n", then split on that character.
29707  // Uint8Array.prototype.reduce is not implemented in IE11
29708  const vttLines = utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, '\n').split('\n');
29709  const cues = [];
29710  const init90kHz = initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale) : 0;
29711  let cueTime = '00:00.000';
29712  let timestampMapMPEGTS = 0;
29713  let timestampMapLOCAL = 0;
29714  let parsingError;
29715  let inHeader = true;
29716  parser.oncue = function (cue) {
29717    // Adjust cue timing; clamp cues to start no earlier than - and drop cues that don't end after - 0 on timeline.
29718    const currCC = vttCCs[cc];
29719    let cueOffset = vttCCs.ccOffset;
29720
29721    // Calculate subtitle PTS offset
29722    const webVttMpegTsMapOffset = (timestampMapMPEGTS - init90kHz) / 90000;
29723
29724    // Update offsets for new discontinuities
29725    if (currCC != null && currCC.new) {
29726      if (timestampMapLOCAL !== undefined) {
29727        // When local time is provided, offset = discontinuity start time - local time
29728        cueOffset = vttCCs.ccOffset = currCC.start;
29729      } else {
29730        calculateOffset(vttCCs, cc, webVttMpegTsMapOffset);
29731      }
29732    }
29733    if (webVttMpegTsMapOffset) {
29734      if (!initPTS) {
29735        parsingError = new Error('Missing initPTS for VTT MPEGTS');
29736        return;
29737      }
29738      // If we have MPEGTS, offset = presentation time + discontinuity offset
29739      cueOffset = webVttMpegTsMapOffset - vttCCs.presentationOffset;
29740    }
29741    const duration = cue.endTime - cue.startTime;
29742    const startTime = normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 90000, timeOffset * 90000) / 90000;
29743    cue.startTime = Math.max(startTime, 0);
29744    cue.endTime = Math.max(startTime + duration, 0);
29745
29746    //trim trailing webvtt block whitespaces
29747    const text = cue.text.trim();
29748
29749    // Fix encoding of special characters
29750    cue.text = decodeURIComponent(encodeURIComponent(text));
29751
29752    // If the cue was not assigned an id from the VTT file (line above the content), create one.
29753    if (!cue.id) {
29754      cue.id = generateCueId(cue.startTime, cue.endTime, text);
29755    }
29756    if (cue.endTime > 0) {
29757      cues.push(cue);
29758    }
29759  };
29760  parser.onparsingerror = function (error) {
29761    parsingError = error;
29762  };
29763  parser.onflush = function () {
29764    if (parsingError) {
29765      errorCallBack(parsingError);
29766      return;
29767    }
29768    callBack(cues);
29769  };
29770
29771  // Go through contents line by line.
29772  vttLines.forEach(line => {
29773    if (inHeader) {
29774      // Look for X-TIMESTAMP-MAP in header.
29775      if (startsWith(line, 'X-TIMESTAMP-MAP=')) {
29776        // Once found, no more are allowed anyway, so stop searching.
29777        inHeader = false;
29778        // Extract LOCAL and MPEGTS.
29779        line.slice(16).split(',').forEach(timestamp => {
29780          if (startsWith(timestamp, 'LOCAL:')) {
29781            cueTime = timestamp.slice(6);
29782          } else if (startsWith(timestamp, 'MPEGTS:')) {
29783            timestampMapMPEGTS = parseInt(timestamp.slice(7));
29784          }
29785        });
29786        try {
29787          // Convert cue time to seconds
29788          timestampMapLOCAL = cueString2millis(cueTime) / 1000;
29789        } catch (error) {
29790          parsingError = error;
29791        }
29792        // Return without parsing X-TIMESTAMP-MAP line.
29793        return;
29794      } else if (line === '') {
29795        inHeader = false;
29796      }
29797    }
29798    // Parse line by default.
29799    parser.parse(line + '\n');
29800  });
29801  parser.flush();
29802}
29803
29804const IMSC1_CODEC = 'stpp.ttml.im1t';
29805
29806// Time format: h:m:s:frames(.subframes)
29807const HMSF_REGEX = /^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/;
29808
29809// Time format: hours, minutes, seconds, milliseconds, frames, ticks
29810const TIME_UNIT_REGEX = /^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/;
29811const textAlignToLineAlign = {
29812  left: 'start',
29813  center: 'center',
29814  right: 'end',
29815  start: 'start',
29816  end: 'end'
29817};
29818function parseIMSC1(payload, initPTS, callBack, errorCallBack) {
29819  const results = findBox(new Uint8Array(payload), ['mdat']);
29820  if (results.length === 0) {
29821    errorCallBack(new Error('Could not parse IMSC1 mdat'));
29822    return;
29823  }
29824  const ttmlList = results.map(mdat => utf8ArrayToStr(mdat));
29825  const syncTime = toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale);
29826  try {
29827    ttmlList.forEach(ttml => callBack(parseTTML(ttml, syncTime)));
29828  } catch (error) {
29829    errorCallBack(error);
29830  }
29831}
29832function parseTTML(ttml, syncTime) {
29833  const parser = new DOMParser();
29834  const xmlDoc = parser.parseFromString(ttml, 'text/xml');
29835  const tt = xmlDoc.getElementsByTagName('tt')[0];
29836  if (!tt) {
29837    throw new Error('Invalid ttml');
29838  }
29839  const defaultRateInfo = {
29840    frameRate: 30,
29841    subFrameRate: 1,
29842    frameRateMultiplier: 0,
29843    tickRate: 0
29844  };
29845  const rateInfo = Object.keys(defaultRateInfo).reduce((result, key) => {
29846    result[key] = tt.getAttribute(`ttp:${key}`) || defaultRateInfo[key];
29847    return result;
29848  }, {});
29849  const trim = tt.getAttribute('xml:space') !== 'preserve';
29850  const styleElements = collectionToDictionary(getElementCollection(tt, 'styling', 'style'));
29851  const regionElements = collectionToDictionary(getElementCollection(tt, 'layout', 'region'));
29852  const cueElements = getElementCollection(tt, 'body', '[begin]');
29853  return [].map.call(cueElements, cueElement => {
29854    const cueText = getTextContent(cueElement, trim);
29855    if (!cueText || !cueElement.hasAttribute('begin')) {
29856      return null;
29857    }
29858    const startTime = parseTtmlTime(cueElement.getAttribute('begin'), rateInfo);
29859    const duration = parseTtmlTime(cueElement.getAttribute('dur'), rateInfo);
29860    let endTime = parseTtmlTime(cueElement.getAttribute('end'), rateInfo);
29861    if (startTime === null) {
29862      throw timestampParsingError(cueElement);
29863    }
29864    if (endTime === null) {
29865      if (duration === null) {
29866        throw timestampParsingError(cueElement);
29867      }
29868      endTime = startTime + duration;
29869    }
29870    const cue = new VTTCue(startTime - syncTime, endTime - syncTime, cueText);
29871    cue.id = generateCueId(cue.startTime, cue.endTime, cue.text);
29872    const region = regionElements[cueElement.getAttribute('region')];
29873    const style = styleElements[cueElement.getAttribute('style')];
29874
29875    // Apply styles to cue
29876    const styles = getTtmlStyles(region, style, styleElements);
29877    const {
29878      textAlign
29879    } = styles;
29880    if (textAlign) {
29881      // cue.positionAlign not settable in FF~2016
29882      const lineAlign = textAlignToLineAlign[textAlign];
29883      if (lineAlign) {
29884        cue.lineAlign = lineAlign;
29885      }
29886      cue.align = textAlign;
29887    }
29888    _extends(cue, styles);
29889    return cue;
29890  }).filter(cue => cue !== null);
29891}
29892function getElementCollection(fromElement, parentName, childName) {
29893  const parent = fromElement.getElementsByTagName(parentName)[0];
29894  if (parent) {
29895    return [].slice.call(parent.querySelectorAll(childName));
29896  }
29897  return [];
29898}
29899function collectionToDictionary(elementsWithId) {
29900  return elementsWithId.reduce((dict, element) => {
29901    const id = element.getAttribute('xml:id');
29902    if (id) {
29903      dict[id] = element;
29904    }
29905    return dict;
29906  }, {});
29907}
29908function getTextContent(element, trim) {
29909  return [].slice.call(element.childNodes).reduce((str, node, i) => {
29910    var _node$childNodes;
29911    if (node.nodeName === 'br' && i) {
29912      return str + '\n';
29913    }
29914    if ((_node$childNodes = node.childNodes) != null && _node$childNodes.length) {
29915      return getTextContent(node, trim);
29916    } else if (trim) {
29917      return str + node.textContent.trim().replace(/\s+/g, ' ');
29918    }
29919    return str + node.textContent;
29920  }, '');
29921}
29922function getTtmlStyles(region, style, styleElements) {
29923  const ttsNs = 'http://www.w3.org/ns/ttml#styling';
29924  let regionStyle = null;
29925  const styleAttributes = ['displayAlign', 'textAlign', 'color', 'backgroundColor', 'fontSize', 'fontFamily'
29926  // 'fontWeight',
29927  // 'lineHeight',
29928  // 'wrapOption',
29929  // 'fontStyle',
29930  // 'direction',
29931  // 'writingMode'
29932  ];
29933  const regionStyleName = region != null && region.hasAttribute('style') ? region.getAttribute('style') : null;
29934  if (regionStyleName && styleElements.hasOwnProperty(regionStyleName)) {
29935    regionStyle = styleElements[regionStyleName];
29936  }
29937  return styleAttributes.reduce((styles, name) => {
29938    const value = getAttributeNS(style, ttsNs, name) || getAttributeNS(region, ttsNs, name) || getAttributeNS(regionStyle, ttsNs, name);
29939    if (value) {
29940      styles[name] = value;
29941    }
29942    return styles;
29943  }, {});
29944}
29945function getAttributeNS(element, ns, name) {
29946  if (!element) {
29947    return null;
29948  }
29949  return element.hasAttributeNS(ns, name) ? element.getAttributeNS(ns, name) : null;
29950}
29951function timestampParsingError(node) {
29952  return new Error(`Could not parse ttml timestamp ${node}`);
29953}
29954function parseTtmlTime(timeAttributeValue, rateInfo) {
29955  if (!timeAttributeValue) {
29956    return null;
29957  }
29958  let seconds = parseTimeStamp(timeAttributeValue);
29959  if (seconds === null) {
29960    if (HMSF_REGEX.test(timeAttributeValue)) {
29961      seconds = parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo);
29962    } else if (TIME_UNIT_REGEX.test(timeAttributeValue)) {
29963      seconds = parseTimeUnits(timeAttributeValue, rateInfo);
29964    }
29965  }
29966  return seconds;
29967}
29968function parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo) {
29969  const m = HMSF_REGEX.exec(timeAttributeValue);
29970  const frames = (m[4] | 0) + (m[5] | 0) / rateInfo.subFrameRate;
29971  return (m[1] | 0) * 3600 + (m[2] | 0) * 60 + (m[3] | 0) + frames / rateInfo.frameRate;
29972}
29973function parseTimeUnits(timeAttributeValue, rateInfo) {
29974  const m = TIME_UNIT_REGEX.exec(timeAttributeValue);
29975  const value = Number(m[1]);
29976  const unit = m[2];
29977  switch (unit) {
29978    case 'h':
29979      return value * 3600;
29980    case 'm':
29981      return value * 60;
29982    case 'ms':
29983      return value * 1000;
29984    case 'f':
29985      return value / rateInfo.frameRate;
29986    case 't':
29987      return value / rateInfo.tickRate;
29988  }
29989  return value;
29990}
29991
29992class OutputFilter {
29993  constructor(timelineController, trackName) {
29994    this.timelineController = void 0;
29995    this.cueRanges = [];
29996    this.trackName = void 0;
29997    this.startTime = null;
29998    this.endTime = null;
29999    this.screen = null;
30000    this.timelineController = timelineController;
30001    this.trackName = trackName;
30002  }
30003  dispatchCue() {
30004    if (this.startTime === null) {
30005      return;
30006    }
30007    this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges);
30008    this.startTime = null;
30009  }
30010  newCue(startTime, endTime, screen) {
30011    if (this.startTime === null || this.startTime > startTime) {
30012      this.startTime = startTime;
30013    }
30014    this.endTime = endTime;
30015    this.screen = screen;
30016    this.timelineController.createCaptionsTrack(this.trackName);
30017  }
30018  reset() {
30019    this.cueRanges = [];
30020    this.startTime = null;
30021  }
30022}
30023
30024class TimelineController {
30025  constructor(hls) {
30026    this.hls = void 0;
30027    this.media = null;
30028    this.config = void 0;
30029    this.enabled = true;
30030    this.Cues = void 0;
30031    this.textTracks = [];
30032    this.tracks = [];
30033    this.initPTS = [];
30034    this.unparsedVttFrags = [];
30035    this.captionsTracks = {};
30036    this.nonNativeCaptionsTracks = {};
30037    this.cea608Parser1 = void 0;
30038    this.cea608Parser2 = void 0;
30039    this.lastCc = -1;
30040    // Last video (CEA-608) fragment CC
30041    this.lastSn = -1;
30042    // Last video (CEA-608) fragment MSN
30043    this.lastPartIndex = -1;
30044    // Last video (CEA-608) fragment Part Index
30045    this.prevCC = -1;
30046    // Last subtitle fragment CC
30047    this.vttCCs = newVTTCCs();
30048    this.captionsProperties = void 0;
30049    this.hls = hls;
30050    this.config = hls.config;
30051    this.Cues = hls.config.cueHandler;
30052    this.captionsProperties = {
30053      textTrack1: {
30054        label: this.config.captionsTextTrack1Label,
30055        languageCode: this.config.captionsTextTrack1LanguageCode
30056      },
30057      textTrack2: {
30058        label: this.config.captionsTextTrack2Label,
30059        languageCode: this.config.captionsTextTrack2LanguageCode
30060      },
30061      textTrack3: {
30062        label: this.config.captionsTextTrack3Label,
30063        languageCode: this.config.captionsTextTrack3LanguageCode
30064      },
30065      textTrack4: {
30066        label: this.config.captionsTextTrack4Label,
30067        languageCode: this.config.captionsTextTrack4LanguageCode
30068      }
30069    };
30070    hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
30071    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
30072    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
30073    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
30074    hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
30075    hls.on(Events.FRAG_LOADING, this.onFragLoading, this);
30076    hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
30077    hls.on(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);
30078    hls.on(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);
30079    hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
30080    hls.on(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
30081    hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
30082  }
30083  destroy() {
30084    const {
30085      hls
30086    } = this;
30087    hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
30088    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
30089    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
30090    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
30091    hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
30092    hls.off(Events.FRAG_LOADING, this.onFragLoading, this);
30093    hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
30094    hls.off(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this);
30095    hls.off(Events.FRAG_DECRYPTED, this.onFragDecrypted, this);
30096    hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
30097    hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this);
30098    hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
30099    // @ts-ignore
30100    this.hls = this.config = this.media = null;
30101    this.cea608Parser1 = this.cea608Parser2 = undefined;
30102  }
30103  initCea608Parsers() {
30104    const channel1 = new OutputFilter(this, 'textTrack1');
30105    const channel2 = new OutputFilter(this, 'textTrack2');
30106    const channel3 = new OutputFilter(this, 'textTrack3');
30107    const channel4 = new OutputFilter(this, 'textTrack4');
30108    this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
30109    this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
30110  }
30111  addCues(trackName, startTime, endTime, screen, cueRanges) {
30112    // skip cues which overlap more than 50% with previously parsed time ranges
30113    let merged = false;
30114    for (let i = cueRanges.length; i--;) {
30115      const cueRange = cueRanges[i];
30116      const overlap = intersection(cueRange[0], cueRange[1], startTime, endTime);
30117      if (overlap >= 0) {
30118        cueRange[0] = Math.min(cueRange[0], startTime);
30119        cueRange[1] = Math.max(cueRange[1], endTime);
30120        merged = true;
30121        if (overlap / (endTime - startTime) > 0.5) {
30122          return;
30123        }
30124      }
30125    }
30126    if (!merged) {
30127      cueRanges.push([startTime, endTime]);
30128    }
30129    if (this.config.renderTextTracksNatively) {
30130      const track = this.captionsTracks[trackName];
30131      this.Cues.newCue(track, startTime, endTime, screen);
30132    } else {
30133      const cues = this.Cues.newCue(null, startTime, endTime, screen);
30134      this.hls.trigger(Events.CUES_PARSED, {
30135        type: 'captions',
30136        cues,
30137        track: trackName
30138      });
30139    }
30140  }
30141
30142  // Triggered when an initial PTS is found; used for synchronisation of WebVTT.
30143  onInitPtsFound(event, {
30144    frag,
30145    id,
30146    initPTS,
30147    timescale,
30148    trackId
30149  }) {
30150    const {
30151      unparsedVttFrags
30152    } = this;
30153    if (id === PlaylistLevelType.MAIN) {
30154      this.initPTS[frag.cc] = {
30155        baseTime: initPTS,
30156        timescale,
30157        trackId
30158      };
30159    }
30160
30161    // Due to asynchronous processing, initial PTS may arrive later than the first VTT fragments are loaded.
30162    // Parse any unparsed fragments upon receiving the initial PTS.
30163    if (unparsedVttFrags.length) {
30164      this.unparsedVttFrags = [];
30165      unparsedVttFrags.forEach(data => {
30166        if (this.initPTS[data.frag.cc]) {
30167          this.onFragLoaded(Events.FRAG_LOADED, data);
30168        } else {
30169          this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
30170            success: false,
30171            frag: data.frag,
30172            error: new Error('Subtitle discontinuity domain does not match main')
30173          });
30174        }
30175      });
30176    }
30177  }
30178  getExistingTrack(label, language) {
30179    const {
30180      media
30181    } = this;
30182    if (media) {
30183      for (let i = 0; i < media.textTracks.length; i++) {
30184        const textTrack = media.textTracks[i];
30185        if (canReuseVttTextTrack(textTrack, {
30186          name: label,
30187          lang: language,
30188          characteristics: 'transcribes-spoken-dialog,describes-music-and-sound'})) {
30189          return textTrack;
30190        }
30191      }
30192    }
30193    return null;
30194  }
30195  createCaptionsTrack(trackName) {
30196    if (this.config.renderTextTracksNatively) {
30197      this.createNativeTrack(trackName);
30198    } else {
30199      this.createNonNativeTrack(trackName);
30200    }
30201  }
30202  createNativeTrack(trackName) {
30203    if (this.captionsTracks[trackName]) {
30204      return;
30205    }
30206    const {
30207      captionsProperties,
30208      captionsTracks,
30209      media
30210    } = this;
30211    const {
30212      label,
30213      languageCode
30214    } = captionsProperties[trackName];
30215    // Enable reuse of existing text track.
30216    const existingTrack = this.getExistingTrack(label, languageCode);
30217    if (!existingTrack) {
30218      const textTrack = this.createTextTrack('captions', label, languageCode);
30219      if (textTrack) {
30220        // Set a special property on the track so we know it's managed by Hls.js
30221        textTrack[trackName] = true;
30222        captionsTracks[trackName] = textTrack;
30223      }
30224    } else {
30225      captionsTracks[trackName] = existingTrack;
30226      clearCurrentCues(captionsTracks[trackName]);
30227      sendAddTrackEvent(captionsTracks[trackName], media);
30228    }
30229  }
30230  createNonNativeTrack(trackName) {
30231    if (this.nonNativeCaptionsTracks[trackName]) {
30232      return;
30233    }
30234    // Create a list of a single track for the provider to consume
30235    const trackProperties = this.captionsProperties[trackName];
30236    if (!trackProperties) {
30237      return;
30238    }
30239    const label = trackProperties.label;
30240    const track = {
30241      _id: trackName,
30242      label,
30243      kind: 'captions',
30244      default: trackProperties.media ? !!trackProperties.media.default : false,
30245      closedCaptions: trackProperties.media
30246    };
30247    this.nonNativeCaptionsTracks[trackName] = track;
30248    this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {
30249      tracks: [track]
30250    });
30251  }
30252  createTextTrack(kind, label, lang) {
30253    const media = this.media;
30254    if (!media) {
30255      return;
30256    }
30257    return media.addTextTrack(kind, label, lang);
30258  }
30259  onMediaAttaching(event, data) {
30260    this.media = data.media;
30261    if (!data.mediaSource) {
30262      this._cleanTracks();
30263    }
30264  }
30265  onMediaDetaching(event, data) {
30266    const transferringMedia = !!data.transferMedia;
30267    this.media = null;
30268    if (transferringMedia) {
30269      return;
30270    }
30271    const {
30272      captionsTracks
30273    } = this;
30274    Object.keys(captionsTracks).forEach(trackName => {
30275      clearCurrentCues(captionsTracks[trackName]);
30276      delete captionsTracks[trackName];
30277    });
30278    this.nonNativeCaptionsTracks = {};
30279  }
30280  onManifestLoading() {
30281    // Detect discontinuity in video fragment (CEA-608) parsing
30282    this.lastCc = -1;
30283    this.lastSn = -1;
30284    this.lastPartIndex = -1;
30285    // Detect discontinuity in subtitle manifests
30286    this.prevCC = -1;
30287    this.vttCCs = newVTTCCs();
30288    // Reset tracks
30289    this._cleanTracks();
30290    this.tracks = [];
30291    this.captionsTracks = {};
30292    this.nonNativeCaptionsTracks = {};
30293    this.textTracks = [];
30294    this.unparsedVttFrags = [];
30295    this.initPTS = [];
30296    if (this.cea608Parser1 && this.cea608Parser2) {
30297      this.cea608Parser1.reset();
30298      this.cea608Parser2.reset();
30299    }
30300  }
30301  _cleanTracks() {
30302    // clear outdated subtitles
30303    const {
30304      media
30305    } = this;
30306    if (!media) {
30307      return;
30308    }
30309    const textTracks = media.textTracks;
30310    if (textTracks) {
30311      for (let i = 0; i < textTracks.length; i++) {
30312        clearCurrentCues(textTracks[i]);
30313      }
30314    }
30315  }
30316  onSubtitleTracksUpdated(event, data) {
30317    const tracks = data.subtitleTracks || [];
30318    const hasIMSC1 = tracks.some(track => track.textCodec === IMSC1_CODEC);
30319    if (this.config.enableWebVTT || hasIMSC1 && this.config.enableIMSC1) {
30320      const listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks);
30321      if (listIsIdentical) {
30322        this.tracks = tracks;
30323        return;
30324      }
30325      this.textTracks = [];
30326      this.tracks = tracks;
30327      if (this.config.renderTextTracksNatively) {
30328        const media = this.media;
30329        const inUseTracks = media ? filterSubtitleTracks(media.textTracks) : null;
30330        this.tracks.forEach((track, index) => {
30331          // Reuse tracks with the same label and lang, but do not reuse 608/708 tracks
30332          let textTrack;
30333          if (inUseTracks) {
30334            let inUseTrack = null;
30335            for (let i = 0; i < inUseTracks.length; i++) {
30336              if (inUseTracks[i] && canReuseVttTextTrack(inUseTracks[i], track)) {
30337                inUseTrack = inUseTracks[i];
30338                inUseTracks[i] = null;
30339                break;
30340              }
30341            }
30342            if (inUseTrack) {
30343              textTrack = inUseTrack;
30344            }
30345          }
30346          if (textTrack) {
30347            clearCurrentCues(textTrack);
30348          } else {
30349            const textTrackKind = captionsOrSubtitlesFromCharacteristics(track);
30350            textTrack = this.createTextTrack(textTrackKind, track.name, track.lang);
30351            if (textTrack) {
30352              textTrack.mode = 'disabled';
30353            }
30354          }
30355          if (textTrack) {
30356            this.textTracks.push(textTrack);
30357          }
30358        });
30359        // Warn when video element has captions or subtitle TextTracks carried over from another source
30360        if (inUseTracks != null && inUseTracks.length) {
30361          const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
30362          if (unusedTextTracks.length) {
30363            this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
30364          }
30365        }
30366      } else if (this.tracks.length) {
30367        // Create a list of tracks for the provider to consume
30368        const tracksList = this.tracks.map(track => {
30369          return {
30370            label: track.name,
30371            kind: track.type.toLowerCase(),
30372            default: track.default,
30373            subtitleTrack: track
30374          };
30375        });
30376        this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, {
30377          tracks: tracksList
30378        });
30379      }
30380    }
30381  }
30382  onManifestLoaded(event, data) {
30383    if (this.config.enableCEA708Captions && data.captions) {
30384      data.captions.forEach(captionsTrack => {
30385        const instreamIdMatch = /(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId);
30386        if (!instreamIdMatch) {
30387          return;
30388        }
30389        const trackName = `textTrack${instreamIdMatch[1]}`;
30390        const trackProperties = this.captionsProperties[trackName];
30391        if (!trackProperties) {
30392          return;
30393        }
30394        trackProperties.label = captionsTrack.name;
30395        if (captionsTrack.lang) {
30396          // optional attribute
30397          trackProperties.languageCode = captionsTrack.lang;
30398        }
30399        trackProperties.media = captionsTrack;
30400      });
30401    }
30402  }
30403  closedCaptionsForLevel(frag) {
30404    const level = this.hls.levels[frag.level];
30405    return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
30406  }
30407  onFragLoading(event, data) {
30408    // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
30409    if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
30410      var _data$part$index, _data$part;
30411      const {
30412        cea608Parser1,
30413        cea608Parser2,
30414        lastSn
30415      } = this;
30416      const {
30417        cc,
30418        sn
30419      } = data.frag;
30420      const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
30421      if (cea608Parser1 && cea608Parser2) {
30422        if (sn !== lastSn + 1 || sn === lastSn && partIndex !== this.lastPartIndex + 1 || cc !== this.lastCc) {
30423          cea608Parser1.reset();
30424          cea608Parser2.reset();
30425        }
30426      }
30427      this.lastCc = cc;
30428      this.lastSn = sn;
30429      this.lastPartIndex = partIndex;
30430    }
30431  }
30432  onFragLoaded(event, data) {
30433    const {
30434      frag,
30435      payload
30436    } = data;
30437    if (frag.type === PlaylistLevelType.SUBTITLE) {
30438      // If fragment is subtitle type, parse as WebVTT.
30439      if (payload.byteLength) {
30440        const decryptData = frag.decryptdata;
30441        // fragment after decryption has a stats object
30442        const decrypted = 'stats' in data;
30443        // If the subtitles are not encrypted, parse VTTs now. Otherwise, we need to wait.
30444        if (decryptData == null || !decryptData.encrypted || decrypted) {
30445          const trackPlaylistMedia = this.tracks[frag.level];
30446          const vttCCs = this.vttCCs;
30447          if (!vttCCs[frag.cc]) {
30448            vttCCs[frag.cc] = {
30449              start: frag.start,
30450              prevCC: this.prevCC,
30451              new: true
30452            };
30453            this.prevCC = frag.cc;
30454          }
30455          if (trackPlaylistMedia && trackPlaylistMedia.textCodec === IMSC1_CODEC) {
30456            this._parseIMSC1(frag, payload);
30457          } else {
30458            this._parseVTTs(data);
30459          }
30460        }
30461      } else {
30462        // In case there is no payload, finish unsuccessfully.
30463        this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
30464          success: false,
30465          frag,
30466          error: new Error('Empty subtitle payload')
30467        });
30468      }
30469    }
30470  }
30471  _parseIMSC1(frag, payload) {
30472    const hls = this.hls;
30473    parseIMSC1(payload, this.initPTS[frag.cc], cues => {
30474      this._appendCues(cues, frag.level);
30475      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
30476        success: true,
30477        frag: frag
30478      });
30479    }, error => {
30480      hls.logger.log(`Failed to parse IMSC1: ${error}`);
30481      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
30482        success: false,
30483        frag: frag,
30484        error
30485      });
30486    });
30487  }
30488  _parseVTTs(data) {
30489    var _frag$initSegment;
30490    const {
30491      frag,
30492      payload
30493    } = data;
30494    // We need an initial synchronisation PTS. Store fragments as long as none has arrived
30495    const {
30496      initPTS,
30497      unparsedVttFrags
30498    } = this;
30499    const maxAvCC = initPTS.length - 1;
30500    if (!initPTS[frag.cc] && maxAvCC === -1) {
30501      unparsedVttFrags.push(data);
30502      return;
30503    }
30504    const hls = this.hls;
30505    // Parse the WebVTT file contents.
30506    const payloadWebVTT = (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)).buffer : payload;
30507    parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, cues => {
30508      this._appendCues(cues, frag.level);
30509      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
30510        success: true,
30511        frag: frag
30512      });
30513    }, error => {
30514      const missingInitPTS = error.message === 'Missing initPTS for VTT MPEGTS';
30515      if (missingInitPTS) {
30516        unparsedVttFrags.push(data);
30517      } else {
30518        this._fallbackToIMSC1(frag, payload);
30519      }
30520      // Something went wrong while parsing. Trigger event with success false.
30521      hls.logger.log(`Failed to parse VTT cue: ${error}`);
30522      if (missingInitPTS && maxAvCC > frag.cc) {
30523        return;
30524      }
30525      hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
30526        success: false,
30527        frag: frag,
30528        error
30529      });
30530    });
30531  }
30532  _fallbackToIMSC1(frag, payload) {
30533    // If textCodec is unknown, try parsing as IMSC1. Set textCodec based on the result
30534    const trackPlaylistMedia = this.tracks[frag.level];
30535    if (!trackPlaylistMedia.textCodec) {
30536      parseIMSC1(payload, this.initPTS[frag.cc], () => {
30537        trackPlaylistMedia.textCodec = IMSC1_CODEC;
30538        this._parseIMSC1(frag, payload);
30539      }, () => {
30540        trackPlaylistMedia.textCodec = 'wvtt';
30541      });
30542    }
30543  }
30544  _appendCues(cues, fragLevel) {
30545    const hls = this.hls;
30546    if (this.config.renderTextTracksNatively) {
30547      const textTrack = this.textTracks[fragLevel];
30548      // WebVTTParser.parse is an async method and if the currently selected text track mode is set to "disabled"
30549      // before parsing is done then don't try to access currentTrack.cues.getCueById as cues will be null
30550      // and trying to access getCueById method of cues will throw an exception
30551      // Because we check if the mode is disabled, we can force check `cues` below. They can't be null.
30552      if (!textTrack || textTrack.mode === 'disabled') {
30553        return;
30554      }
30555      cues.forEach(cue => addCueToTrack(textTrack, cue));
30556    } else {
30557      const currentTrack = this.tracks[fragLevel];
30558      if (!currentTrack) {
30559        return;
30560      }
30561      const track = currentTrack.default ? 'default' : 'subtitles' + fragLevel;
30562      hls.trigger(Events.CUES_PARSED, {
30563        type: 'subtitles',
30564        cues,
30565        track
30566      });
30567    }
30568  }
30569  onFragDecrypted(event, data) {
30570    const {
30571      frag
30572    } = data;
30573    if (frag.type === PlaylistLevelType.SUBTITLE) {
30574      this.onFragLoaded(Events.FRAG_LOADED, data);
30575    }
30576  }
30577  onSubtitleTracksCleared() {
30578    this.tracks = [];
30579    this.captionsTracks = {};
30580  }
30581  onFragParsingUserdata(event, data) {
30582    if (!this.enabled || !this.config.enableCEA708Captions) {
30583      return;
30584    }
30585    const {
30586      frag,
30587      samples
30588    } = data;
30589    if (frag.type === PlaylistLevelType.MAIN && this.closedCaptionsForLevel(frag) === 'NONE') {
30590      return;
30591    }
30592    // If the event contains captions (found in the bytes property), push all bytes into the parser immediately
30593    // It will create the proper timestamps based on the PTS value
30594    for (let i = 0; i < samples.length; i++) {
30595      const ccBytes = samples[i].bytes;
30596      if (ccBytes) {
30597        if (!this.cea608Parser1) {
30598          this.initCea608Parsers();
30599        }
30600        const ccdatas = this.extractCea608Data(ccBytes);
30601        this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
30602        this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
30603      }
30604    }
30605  }
30606  onBufferFlushing(event, {
30607    startOffset,
30608    endOffset,
30609    endOffsetSubtitles,
30610    type
30611  }) {
30612    const {
30613      media
30614    } = this;
30615    if (!media || media.currentTime < endOffset) {
30616      return;
30617    }
30618    // Clear 608 caption cues from the captions TextTracks when the video back buffer is flushed
30619    // Forward cues are never removed because we can loose streamed 608 content from recent fragments
30620    if (!type || type === 'video') {
30621      const {
30622        captionsTracks
30623      } = this;
30624      Object.keys(captionsTracks).forEach(trackName => removeCuesInRange(captionsTracks[trackName], startOffset, endOffset));
30625    }
30626    if (this.config.renderTextTracksNatively) {
30627      // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed
30628      if (startOffset === 0 && endOffsetSubtitles !== undefined) {
30629        const {
30630          textTracks
30631        } = this;
30632        Object.keys(textTracks).forEach(trackName => removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles));
30633      }
30634    }
30635  }
30636  extractCea608Data(byteArray) {
30637    const actualCCBytes = [[], []];
30638    const count = byteArray[0] & 0x1f;
30639    let position = 2;
30640    for (let j = 0; j < count; j++) {
30641      const tmpByte = byteArray[position++];
30642      const ccbyte1 = 0x7f & byteArray[position++];
30643      const ccbyte2 = 0x7f & byteArray[position++];
30644      if (ccbyte1 === 0 && ccbyte2 === 0) {
30645        continue;
30646      }
30647      const ccValid = (0x04 & tmpByte) !== 0; // Support all four channels
30648      if (ccValid) {
30649        const ccType = 0x03 & tmpByte;
30650        if (0x00 /* CEA608 field1*/ === ccType || 0x01 /* CEA608 field2*/ === ccType) {
30651          // Exclude CEA708 CC data.
30652          actualCCBytes[ccType].push(ccbyte1);
30653          actualCCBytes[ccType].push(ccbyte2);
30654        }
30655      }
30656    }
30657    return actualCCBytes;
30658  }
30659}
30660function captionsOrSubtitlesFromCharacteristics(track) {
30661  if (track.characteristics) {
30662    if (/transcribes-spoken-dialog/gi.test(track.characteristics) && /describes-music-and-sound/gi.test(track.characteristics)) {
30663      return 'captions';
30664    }
30665  }
30666  return 'subtitles';
30667}
30668function canReuseVttTextTrack(inUseTrack, manifestTrack) {
30669  return !!inUseTrack && inUseTrack.kind === captionsOrSubtitlesFromCharacteristics(manifestTrack) && subtitleTrackMatchesTextTrack(manifestTrack, inUseTrack);
30670}
30671function intersection(x1, x2, y1, y2) {
30672  return Math.min(x2, y2) - Math.max(x1, y1);
30673}
30674function newVTTCCs() {
30675  return {
30676    ccOffset: 0,
30677    presentationOffset: 0,
30678    0: {
30679      start: 0,
30680      prevCC: -1,
30681      new: true
30682    }
30683  };
30684}
30685
30686const WHITESPACE_CHAR = /\s/;
30687const Cues = {
30688  newCue(track, startTime, endTime, captionScreen) {
30689    const result = [];
30690    let row;
30691    // the type data states this is VTTCue, but it can potentially be a TextTrackCue on old browsers
30692    let cue;
30693    let indenting;
30694    let indent;
30695    let text;
30696    const Cue = self.VTTCue || self.TextTrackCue;
30697    for (let r = 0; r < captionScreen.rows.length; r++) {
30698      row = captionScreen.rows[r];
30699      indenting = true;
30700      indent = 0;
30701      text = '';
30702      if (!row.isEmpty()) {
30703        var _track$cues;
30704        for (let c = 0; c < row.chars.length; c++) {
30705          if (WHITESPACE_CHAR.test(row.chars[c].uchar) && indenting) {
30706            indent++;
30707          } else {
30708            text += row.chars[c].uchar;
30709            indenting = false;
30710          }
30711        }
30712        // To be used for cleaning-up orphaned roll-up captions
30713        row.cueStartTime = startTime;
30714
30715        // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE
30716        if (startTime === endTime) {
30717          endTime += 0.0001;
30718        }
30719        if (indent >= 16) {
30720          indent--;
30721        } else {
30722          indent++;
30723        }
30724        const cueText = fixLineBreaks(text.trim());
30725        const id = generateCueId(startTime, endTime, cueText);
30726
30727        // If this cue already exists in the track do not push it
30728        if (!(track != null && (_track$cues = track.cues) != null && _track$cues.getCueById(id))) {
30729          cue = new Cue(startTime, endTime, cueText);
30730          cue.id = id;
30731          cue.line = r + 1;
30732          cue.align = 'left';
30733          // Clamp the position between 10 and 80 percent (CEA-608 PAC indent code)
30734          // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608
30735          // Firefox throws an exception and captions break with out of bounds 0-100 values
30736          cue.position = 10 + Math.min(80, Math.floor(indent * 8 / 32) * 10);
30737          result.push(cue);
30738        }
30739      }
30740    }
30741    if (track && result.length) {
30742      // Sort bottom cues in reverse order so that they render in line order when overlapping in Chrome
30743      result.sort((cueA, cueB) => {
30744        if (cueA.line === 'auto' || cueB.line === 'auto') {
30745          return 0;
30746        }
30747        if (cueA.line > 8 && cueB.line > 8) {
30748          return cueB.line - cueA.line;
30749        }
30750        return cueA.line - cueB.line;
30751      });
30752      result.forEach(cue => addCueToTrack(track, cue));
30753    }
30754    return result;
30755  }
30756};
30757
30758function fetchSupported() {
30759  if (
30760  // @ts-ignore
30761  self.fetch && self.AbortController && self.ReadableStream && self.Request) {
30762    try {
30763      new self.ReadableStream({}); // eslint-disable-line no-new
30764      return true;
30765    } catch (e) {
30766      /* noop */
30767    }
30768  }
30769  return false;
30770}
30771const BYTERANGE = /(\d+)-(\d+)\/(\d+)/;
30772class FetchLoader {
30773  constructor(config) {
30774    this.fetchSetup = void 0;
30775    this.requestTimeout = void 0;
30776    this.request = null;
30777    this.response = null;
30778    this.controller = void 0;
30779    this.context = null;
30780    this.config = null;
30781    this.callbacks = null;
30782    this.stats = void 0;
30783    this.loader = null;
30784    this.fetchSetup = config.fetchSetup || getRequest;
30785    this.controller = new self.AbortController();
30786    this.stats = new LoadStats();
30787  }
30788  destroy() {
30789    this.loader = this.callbacks = this.context = this.config = this.request = null;
30790    this.abortInternal();
30791    this.response = null;
30792    // @ts-ignore
30793    this.fetchSetup = this.controller = this.stats = null;
30794  }
30795  abortInternal() {
30796    if (this.controller && !this.stats.loading.end) {
30797      this.stats.aborted = true;
30798      this.controller.abort();
30799    }
30800  }
30801  abort() {
30802    var _this$callbacks;
30803    this.abortInternal();
30804    if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) {
30805      this.callbacks.onAbort(this.stats, this.context, this.response);
30806    }
30807  }
30808  load(context, config, callbacks) {
30809    const stats = this.stats;
30810    if (stats.loading.start) {
30811      throw new Error('Loader can only be used once.');
30812    }
30813    stats.loading.start = self.performance.now();
30814    const initParams = getRequestParameters(context, this.controller.signal);
30815    const isArrayBuffer = context.responseType === 'arraybuffer';
30816    const LENGTH = isArrayBuffer ? 'byteLength' : 'length';
30817    const {
30818      maxTimeToFirstByteMs,
30819      maxLoadTimeMs
30820    } = config.loadPolicy;
30821    this.context = context;
30822    this.config = config;
30823    this.callbacks = callbacks;
30824    this.request = this.fetchSetup(context, initParams);
30825    self.clearTimeout(this.requestTimeout);
30826    config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
30827    this.requestTimeout = self.setTimeout(() => {
30828      if (this.callbacks) {
30829        this.abortInternal();
30830        this.callbacks.onTimeout(stats, context, this.response);
30831      }
30832    }, config.timeout);
30833    const fetchPromise = isPromise(this.request) ? this.request.then(self.fetch) : self.fetch(this.request);
30834    fetchPromise.then(response => {
30835      var _this$callbacks2;
30836      this.response = this.loader = response;
30837      const first = Math.max(self.performance.now(), stats.loading.start);
30838      self.clearTimeout(this.requestTimeout);
30839      config.timeout = maxLoadTimeMs;
30840      this.requestTimeout = self.setTimeout(() => {
30841        if (this.callbacks) {
30842          this.abortInternal();
30843          this.callbacks.onTimeout(stats, context, this.response);
30844        }
30845      }, maxLoadTimeMs - (first - stats.loading.start));
30846      if (!response.ok) {
30847        const {
30848          status,
30849          statusText
30850        } = response;
30851        throw new FetchError(statusText || 'fetch, bad network response', status, response);
30852      }
30853      stats.loading.first = first;
30854      stats.total = getContentLength(response.headers) || stats.total;
30855      const onProgress = (_this$callbacks2 = this.callbacks) == null ? void 0 : _this$callbacks2.onProgress;
30856      if (onProgress && isFiniteNumber(config.highWaterMark)) {
30857        return this.loadProgressively(response, stats, context, config.highWaterMark, onProgress);
30858      }
30859      if (isArrayBuffer) {
30860        return response.arrayBuffer();
30861      }
30862      if (context.responseType === 'json') {
30863        return response.json();
30864      }
30865      return response.text();
30866    }).then(responseData => {
30867      var _this$callbacks3, _this$callbacks4;
30868      const response = this.response;
30869      if (!response) {
30870        throw new Error('loader destroyed');
30871      }
30872      self.clearTimeout(this.requestTimeout);
30873      stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
30874      const total = responseData[LENGTH];
30875      if (total) {
30876        stats.loaded = stats.total = total;
30877      }
30878      const loaderResponse = {
30879        url: response.url,
30880        data: responseData,
30881        code: response.status
30882      };
30883      const onProgress = (_this$callbacks3 = this.callbacks) == null ? void 0 : _this$callbacks3.onProgress;
30884      if (onProgress && !isFiniteNumber(config.highWaterMark)) {
30885        onProgress(stats, context, responseData, response);
30886      }
30887      (_this$callbacks4 = this.callbacks) == null || _this$callbacks4.onSuccess(loaderResponse, stats, context, response);
30888    }).catch(error => {
30889      var _this$callbacks5;
30890      self.clearTimeout(this.requestTimeout);
30891      if (stats.aborted) {
30892        return;
30893      }
30894      // CORS errors result in an undefined code. Set it to 0 here to align with XHR's behavior
30895      // when destroying, 'error' itself can be undefined
30896      const code = !error ? 0 : error.code || 0;
30897      const text = !error ? null : error.message;
30898      (_this$callbacks5 = this.callbacks) == null || _this$callbacks5.onError({
30899        code,
30900        text
30901      }, context, error ? error.details : null, stats);
30902    });
30903  }
30904  getCacheAge() {
30905    let result = null;
30906    if (this.response) {
30907      const ageHeader = this.response.headers.get('age');
30908      result = ageHeader ? parseFloat(ageHeader) : null;
30909    }
30910    return result;
30911  }
30912  getResponseHeader(name) {
30913    return this.response ? this.response.headers.get(name) : null;
30914  }
30915  loadProgressively(response, stats, context, highWaterMark = 0, onProgress) {
30916    const chunkCache = new ChunkCache();
30917    const reader = response.body.getReader();
30918    const pump = () => {
30919      return reader.read().then(data => {
30920        if (data.done) {
30921          if (chunkCache.dataLength) {
30922            onProgress(stats, context, chunkCache.flush().buffer, response);
30923          }
30924          return Promise.resolve(new ArrayBuffer(0));
30925        }
30926        const chunk = data.value;
30927        const len = chunk.length;
30928        stats.loaded += len;
30929        if (len < highWaterMark || chunkCache.dataLength) {
30930          // The current chunk is too small to to be emitted or the cache already has data
30931          // Push it to the cache
30932          chunkCache.push(chunk);
30933          if (chunkCache.dataLength >= highWaterMark) {
30934            // flush in order to join the typed arrays
30935            onProgress(stats, context, chunkCache.flush().buffer, response);
30936          }
30937        } else {
30938          // If there's nothing cached already, and the chache is large enough
30939          // just emit the progress event
30940          onProgress(stats, context, chunk.buffer, response);
30941        }
30942        return pump();
30943      }).catch(() => {
30944        /* aborted */
30945        return Promise.reject();
30946      });
30947    };
30948    return pump();
30949  }
30950}
30951function getRequestParameters(context, signal) {
30952  const initParams = {
30953    method: 'GET',
30954    mode: 'cors',
30955    credentials: 'same-origin',
30956    signal,
30957    headers: new self.Headers(_extends({}, context.headers))
30958  };
30959  if (context.rangeEnd) {
30960    initParams.headers.set('Range', 'bytes=' + context.rangeStart + '-' + String(context.rangeEnd - 1));
30961  }
30962  return initParams;
30963}
30964function getByteRangeLength(byteRangeHeader) {
30965  const result = BYTERANGE.exec(byteRangeHeader);
30966  if (result) {
30967    return parseInt(result[2]) - parseInt(result[1]) + 1;
30968  }
30969}
30970function getContentLength(headers) {
30971  const contentRange = headers.get('Content-Range');
30972  if (contentRange) {
30973    const byteRangeLength = getByteRangeLength(contentRange);
30974    if (isFiniteNumber(byteRangeLength)) {
30975      return byteRangeLength;
30976    }
30977  }
30978  const contentLength = headers.get('Content-Length');
30979  if (contentLength) {
30980    return parseInt(contentLength);
30981  }
30982}
30983function getRequest(context, initParams) {
30984  return new self.Request(context.url, initParams);
30985}
30986class FetchError extends Error {
30987  constructor(message, code, details) {
30988    super(message);
30989    this.code = void 0;
30990    this.details = void 0;
30991    this.code = code;
30992    this.details = details;
30993  }
30994}
30995
30996const AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im;
30997class XhrLoader {
30998  constructor(config) {
30999    this.xhrSetup = void 0;
31000    this.requestTimeout = void 0;
31001    this.retryTimeout = void 0;
31002    this.retryDelay = void 0;
31003    this.config = null;
31004    this.callbacks = null;
31005    this.context = null;
31006    this.loader = null;
31007    this.stats = void 0;
31008    this.xhrSetup = config ? config.xhrSetup || null : null;
31009    this.stats = new LoadStats();
31010    this.retryDelay = 0;
31011  }
31012  destroy() {
31013    this.callbacks = null;
31014    this.abortInternal();
31015    this.loader = null;
31016    this.config = null;
31017    this.context = null;
31018    this.xhrSetup = null;
31019  }
31020  abortInternal() {
31021    const loader = this.loader;
31022    self.clearTimeout(this.requestTimeout);
31023    self.clearTimeout(this.retryTimeout);
31024    if (loader) {
31025      loader.onreadystatechange = null;
31026      loader.onprogress = null;
31027      if (loader.readyState !== 4) {
31028        this.stats.aborted = true;
31029        loader.abort();
31030      }
31031    }
31032  }
31033  abort() {
31034    var _this$callbacks;
31035    this.abortInternal();
31036    if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) {
31037      this.callbacks.onAbort(this.stats, this.context, this.loader);
31038    }
31039  }
31040  load(context, config, callbacks) {
31041    if (this.stats.loading.start) {
31042      throw new Error('Loader can only be used once.');
31043    }
31044    this.stats.loading.start = self.performance.now();
31045    this.context = context;
31046    this.config = config;
31047    this.callbacks = callbacks;
31048    this.loadInternal();
31049  }
31050  loadInternal() {
31051    const {
31052      config,
31053      context
31054    } = this;
31055    if (!config || !context) {
31056      return;
31057    }
31058    const xhr = this.loader = new self.XMLHttpRequest();
31059    const stats = this.stats;
31060    stats.loading.first = 0;
31061    stats.loaded = 0;
31062    stats.aborted = false;
31063    const xhrSetup = this.xhrSetup;
31064    if (xhrSetup) {
31065      Promise.resolve().then(() => {
31066        if (this.loader !== xhr || this.stats.aborted) return;
31067        return xhrSetup(xhr, context.url);
31068      }).catch(error => {
31069        if (this.loader !== xhr || this.stats.aborted) return;
31070        xhr.open('GET', context.url, true);
31071        return xhrSetup(xhr, context.url);
31072      }).then(() => {
31073        if (this.loader !== xhr || this.stats.aborted) return;
31074        this.openAndSendXhr(xhr, context, config);
31075      }).catch(error => {
31076        var _this$callbacks2;
31077        // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS
31078        (_this$callbacks2 = this.callbacks) == null || _this$callbacks2.onError({
31079          code: xhr.status,
31080          text: error.message
31081        }, context, xhr, stats);
31082        return;
31083      });
31084    } else {
31085      this.openAndSendXhr(xhr, context, config);
31086    }
31087  }
31088  openAndSendXhr(xhr, context, config) {
31089    if (!xhr.readyState) {
31090      xhr.open('GET', context.url, true);
31091    }
31092    const headers = context.headers;
31093    const {
31094      maxTimeToFirstByteMs,
31095      maxLoadTimeMs
31096    } = config.loadPolicy;
31097    if (headers) {
31098      for (const header in headers) {
31099        xhr.setRequestHeader(header, headers[header]);
31100      }
31101    }
31102    if (context.rangeEnd) {
31103      xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1));
31104    }
31105    xhr.onreadystatechange = this.readystatechange.bind(this);
31106    xhr.onprogress = this.loadprogress.bind(this);
31107    xhr.responseType = context.responseType;
31108    // setup timeout before we perform request
31109    self.clearTimeout(this.requestTimeout);
31110    config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs;
31111    this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout);
31112    xhr.send();
31113  }
31114  readystatechange() {
31115    const {
31116      context,
31117      loader: xhr,
31118      stats
31119    } = this;
31120    if (!context || !xhr) {
31121      return;
31122    }
31123    const readyState = xhr.readyState;
31124    const config = this.config;
31125
31126    // don't proceed if xhr has been aborted
31127    if (stats.aborted) {
31128      return;
31129    }
31130
31131    // >= HEADERS_RECEIVED
31132    if (readyState >= 2) {
31133      if (stats.loading.first === 0) {
31134        stats.loading.first = Math.max(self.performance.now(), stats.loading.start);
31135        // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet
31136        if (config.timeout !== config.loadPolicy.maxLoadTimeMs) {
31137          self.clearTimeout(this.requestTimeout);
31138          config.timeout = config.loadPolicy.maxLoadTimeMs;
31139          this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start));
31140        }
31141      }
31142      if (readyState === 4) {
31143        self.clearTimeout(this.requestTimeout);
31144        xhr.onreadystatechange = null;
31145        xhr.onprogress = null;
31146        const status = xhr.status;
31147        // http status between 200 to 299 are all successful
31148        const useResponseText = xhr.responseType === 'text' ? xhr.responseText : null;
31149        if (status >= 200 && status < 300) {
31150          const data = useResponseText != null ? useResponseText : xhr.response;
31151          if (data != null) {
31152            var _this$callbacks3, _this$callbacks4;
31153            stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
31154            const len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
31155            stats.loaded = stats.total = len;
31156            stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first);
31157            const onProgress = (_this$callbacks3 = this.callbacks) == null ? void 0 : _this$callbacks3.onProgress;
31158            if (onProgress) {
31159              onProgress(stats, context, data, xhr);
31160            }
31161            const _response = {
31162              url: xhr.responseURL,
31163              data: data,
31164              code: status
31165            };
31166            (_this$callbacks4 = this.callbacks) == null || _this$callbacks4.onSuccess(_response, stats, context, xhr);
31167            return;
31168          }
31169        }
31170
31171        // Handle bad status or nullish response
31172        const retryConfig = config.loadPolicy.errorRetry;
31173        const retryCount = stats.retry;
31174        // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
31175        const response = {
31176          url: context.url,
31177          data: undefined,
31178          code: status
31179        };
31180        if (shouldRetry(retryConfig, retryCount, false, response)) {
31181          this.retry(retryConfig);
31182        } else {
31183          var _this$callbacks5;
31184          logger.error(`${status} while loading ${context.url}`);
31185          (_this$callbacks5 = this.callbacks) == null || _this$callbacks5.onError({
31186            code: status,
31187            text: xhr.statusText
31188          }, context, xhr, stats);
31189        }
31190      }
31191    }
31192  }
31193  loadtimeout() {
31194    if (!this.config) return;
31195    const retryConfig = this.config.loadPolicy.timeoutRetry;
31196    const retryCount = this.stats.retry;
31197    if (shouldRetry(retryConfig, retryCount, true)) {
31198      this.retry(retryConfig);
31199    } else {
31200      var _this$context;
31201      logger.warn(`timeout while loading ${(_this$context = this.context) == null ? void 0 : _this$context.url}`);
31202      const callbacks = this.callbacks;
31203      if (callbacks) {
31204        this.abortInternal();
31205        callbacks.onTimeout(this.stats, this.context, this.loader);
31206      }
31207    }
31208  }
31209  retry(retryConfig) {
31210    const {
31211      context,
31212      stats
31213    } = this;
31214    this.retryDelay = getRetryDelay(retryConfig, stats.retry);
31215    stats.retry++;
31216    logger.warn(`${status ? 'HTTP Status ' + status : 'Timeout'} while loading ${context == null ? void 0 : context.url}, retrying ${stats.retry}/${retryConfig.maxNumRetry} in ${this.retryDelay}ms`);
31217    // abort and reset internal state
31218    this.abortInternal();
31219    this.loader = null;
31220    // schedule retry
31221    self.clearTimeout(this.retryTimeout);
31222    this.retryTimeout = self.setTimeout(this.loadInternal.bind(this), this.retryDelay);
31223  }
31224  loadprogress(event) {
31225    const stats = this.stats;
31226    stats.loaded = event.loaded;
31227    if (event.lengthComputable) {
31228      stats.total = event.total;
31229    }
31230  }
31231  getCacheAge() {
31232    let result = null;
31233    if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) {
31234      const ageHeader = this.loader.getResponseHeader('age');
31235      result = ageHeader ? parseFloat(ageHeader) : null;
31236    }
31237    return result;
31238  }
31239  getResponseHeader(name) {
31240    if (this.loader && new RegExp(`^${name}:\\s*[\\d.]+\\s*$`, 'im').test(this.loader.getAllResponseHeaders())) {
31241      return this.loader.getResponseHeader(name);
31242    }
31243    return null;
31244  }
31245}
31246
31247/**
31248 * @deprecated use fragLoadPolicy.default
31249 */
31250
31251/**
31252 * @deprecated use manifestLoadPolicy.default and playlistLoadPolicy.default
31253 */
31254
31255const defaultLoadPolicy = {
31256  maxTimeToFirstByteMs: 8000,
31257  maxLoadTimeMs: 20000,
31258  timeoutRetry: null,
31259  errorRetry: null
31260};
31261
31262/**
31263 * @ignore
31264 * If possible, keep hlsDefaultConfig shallow
31265 * It is cloned whenever a new Hls instance is created, by keeping the config
31266 * shallow the properties are cloned, and we don't end up manipulating the default
31267 */
31268const hlsDefaultConfig = _objectSpread2(_objectSpread2({
31269  autoStartLoad: true,
31270  // used by stream-controller
31271  startPosition: -1,
31272  // used by stream-controller
31273  defaultAudioCodec: undefined,
31274  // used by stream-controller
31275  debug: false,
31276  // used by logger
31277  capLevelOnFPSDrop: false,
31278  // used by fps-controller
31279  capLevelToPlayerSize: false,
31280  // used by cap-level-controller
31281  ignoreDevicePixelRatio: false,
31282  // used by cap-level-controller
31283  maxDevicePixelRatio: Number.POSITIVE_INFINITY,
31284  // used by cap-level-controller
31285  preferManagedMediaSource: true,
31286  initialLiveManifestSize: 1,
31287  // used by stream-controller
31288  maxBufferLength: 30,
31289  // used by stream-controller
31290  backBufferLength: Infinity,
31291  // used by buffer-controller
31292  frontBufferFlushThreshold: Infinity,
31293  startOnSegmentBoundary: false,
31294  // used by stream-controller
31295  maxBufferSize: 60 * 1000 * 1000,
31296  // used by stream-controller
31297  maxFragLookUpTolerance: 0.25,
31298  // used by stream-controller
31299  maxBufferHole: 0.1,
31300  // used by stream-controller and gap-controller
31301  detectStallWithCurrentTimeMs: 1250,
31302  // used by gap-controller
31303  highBufferWatchdogPeriod: 2,
31304  // used by gap-controller
31305  nudgeOffset: 0.1,
31306  // used by gap-controller
31307  nudgeMaxRetry: 3,
31308  // used by gap-controller
31309  nudgeOnVideoHole: true,
31310  // used by gap-controller
31311  liveSyncMode: 'edge',
31312  // used by stream-controller
31313  liveSyncDurationCount: 3,
31314  // used by latency-controller
31315  liveSyncOnStallIncrease: 1,
31316  // used by latency-controller
31317  liveMaxLatencyDurationCount: Infinity,
31318  // used by latency-controller
31319  liveSyncDuration: undefined,
31320  // used by latency-controller
31321  liveMaxLatencyDuration: undefined,
31322  // used by latency-controller
31323  maxLiveSyncPlaybackRate: 1,
31324  // used by latency-controller
31325  liveDurationInfinity: false,
31326  // used by buffer-controller
31327  /**
31328   * @deprecated use backBufferLength
31329   */
31330  liveBackBufferLength: null,
31331  // used by buffer-controller
31332  maxMaxBufferLength: 600,
31333  // used by stream-controller
31334  enableWorker: true,
31335  // used by transmuxer
31336  workerPath: null,
31337  // used by transmuxer
31338  enableSoftwareAES: true,
31339  // used by decrypter
31340  startLevel: undefined,
31341  // used by level-controller
31342  startFragPrefetch: false,
31343  // used by stream-controller
31344  fpsDroppedMonitoringPeriod: 5000,
31345  // used by fps-controller
31346  fpsDroppedMonitoringThreshold: 0.2,
31347  // used by fps-controller
31348  appendErrorMaxRetry: 3,
31349  // used by buffer-controller
31350  ignorePlaylistParsingErrors: false,
31351  loader: XhrLoader,
31352  // loader: FetchLoader,
31353  fLoader: undefined,
31354  // used by fragment-loader
31355  pLoader: undefined,
31356  // used by playlist-loader
31357  xhrSetup: undefined,
31358  // used by xhr-loader
31359  licenseXhrSetup: undefined,
31360  // used by eme-controller
31361  licenseResponseCallback: undefined,
31362  // used by eme-controller
31363  abrController: AbrController,
31364  bufferController: BufferController,
31365  capLevelController: CapLevelController,
31366  errorController: ErrorController,
31367  fpsController: FPSController,
31368  stretchShortVideoTrack: false,
31369  // used by mp4-remuxer
31370  maxAudioFramesDrift: 1,
31371  // used by mp4-remuxer
31372  forceKeyFrameOnDiscontinuity: true,
31373  // used by ts-demuxer
31374  abrEwmaFastLive: 3,
31375  // used by abr-controller
31376  abrEwmaSlowLive: 9,
31377  // used by abr-controller
31378  abrEwmaFastVoD: 3,
31379  // used by abr-controller
31380  abrEwmaSlowVoD: 9,
31381  // used by abr-controller
31382  abrEwmaDefaultEstimate: 5e5,
31383  // 500 kbps  // used by abr-controller
31384  abrEwmaDefaultEstimateMax: 5e6,
31385  // 5 mbps
31386  abrBandWidthFactor: 0.95,
31387  // used by abr-controller
31388  abrBandWidthUpFactor: 0.7,
31389  // used by abr-controller
31390  abrMaxWithRealBitrate: false,
31391  // used by abr-controller
31392  maxStarvationDelay: 4,
31393  // used by abr-controller
31394  maxLoadingDelay: 4,
31395  // used by abr-controller
31396  minAutoBitrate: 0,
31397  // used by hls
31398  emeEnabled: false,
31399  // used by eme-controller
31400  widevineLicenseUrl: undefined,
31401  // used by eme-controller
31402  drmSystems: {},
31403  // used by eme-controller
31404  drmSystemOptions: {},
31405  // used by eme-controller
31406  requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess ,
31407  // used by eme-controller
31408  requireKeySystemAccessOnStart: false,
31409  // used by eme-controller
31410  testBandwidth: true,
31411  progressive: false,
31412  lowLatencyMode: true,
31413  cmcd: undefined,
31414  enableDateRangeMetadataCues: true,
31415  enableEmsgMetadataCues: true,
31416  enableEmsgKLVMetadata: false,
31417  enableID3MetadataCues: true,
31418  enableInterstitialPlayback: true,
31419  interstitialAppendInPlace: true,
31420  interstitialLiveLookAhead: 10,
31421  useMediaCapabilities: true,
31422  preserveManualLevelOnError: false,
31423  certLoadPolicy: {
31424    default: defaultLoadPolicy
31425  },
31426  keyLoadPolicy: {
31427    default: {
31428      maxTimeToFirstByteMs: 8000,
31429      maxLoadTimeMs: 20000,
31430      timeoutRetry: {
31431        maxNumRetry: 1,
31432        retryDelayMs: 1000,
31433        maxRetryDelayMs: 20000,
31434        backoff: 'linear'
31435      },
31436      errorRetry: {
31437        maxNumRetry: 8,
31438        retryDelayMs: 1000,
31439        maxRetryDelayMs: 20000,
31440        backoff: 'linear'
31441      }
31442    }
31443  },
31444  manifestLoadPolicy: {
31445    default: {
31446      maxTimeToFirstByteMs: Infinity,
31447      maxLoadTimeMs: 20000,
31448      timeoutRetry: {
31449        maxNumRetry: 2,
31450        retryDelayMs: 0,
31451        maxRetryDelayMs: 0
31452      },
31453      errorRetry: {
31454        maxNumRetry: 1,
31455        retryDelayMs: 1000,
31456        maxRetryDelayMs: 8000
31457      }
31458    }
31459  },
31460  playlistLoadPolicy: {
31461    default: {
31462      maxTimeToFirstByteMs: 10000,
31463      maxLoadTimeMs: 20000,
31464      timeoutRetry: {
31465        maxNumRetry: 2,
31466        retryDelayMs: 0,
31467        maxRetryDelayMs: 0
31468      },
31469      errorRetry: {
31470        maxNumRetry: 2,
31471        retryDelayMs: 1000,
31472        maxRetryDelayMs: 8000
31473      }
31474    }
31475  },
31476  fragLoadPolicy: {
31477    default: {
31478      maxTimeToFirstByteMs: 10000,
31479      maxLoadTimeMs: 120000,
31480      timeoutRetry: {
31481        maxNumRetry: 4,
31482        retryDelayMs: 0,
31483        maxRetryDelayMs: 0
31484      },
31485      errorRetry: {
31486        maxNumRetry: 6,
31487        retryDelayMs: 1000,
31488        maxRetryDelayMs: 8000
31489      }
31490    }
31491  },
31492  steeringManifestLoadPolicy: {
31493    default: {
31494      maxTimeToFirstByteMs: 10000,
31495      maxLoadTimeMs: 20000,
31496      timeoutRetry: {
31497        maxNumRetry: 2,
31498        retryDelayMs: 0,
31499        maxRetryDelayMs: 0
31500      },
31501      errorRetry: {
31502        maxNumRetry: 1,
31503        retryDelayMs: 1000,
31504        maxRetryDelayMs: 8000
31505      }
31506    } 
31507  },
31508  interstitialAssetListLoadPolicy: {
31509    default: {
31510      maxTimeToFirstByteMs: 10000,
31511      maxLoadTimeMs: 30000,
31512      timeoutRetry: {
31513        maxNumRetry: 0,
31514        retryDelayMs: 0,
31515        maxRetryDelayMs: 0
31516      },
31517      errorRetry: {
31518        maxNumRetry: 0,
31519        retryDelayMs: 1000,
31520        maxRetryDelayMs: 8000
31521      }
31522    } 
31523  },
31524  // These default settings are deprecated in favor of the above policies
31525  // and are maintained for backwards compatibility
31526  manifestLoadingTimeOut: 10000,
31527  manifestLoadingMaxRetry: 1,
31528  manifestLoadingRetryDelay: 1000,
31529  manifestLoadingMaxRetryTimeout: 64000,
31530  levelLoadingTimeOut: 10000,
31531  levelLoadingMaxRetry: 4,
31532  levelLoadingRetryDelay: 1000,
31533  levelLoadingMaxRetryTimeout: 64000,
31534  fragLoadingTimeOut: 20000,
31535  fragLoadingMaxRetry: 6,
31536  fragLoadingRetryDelay: 1000,
31537  fragLoadingMaxRetryTimeout: 64000
31538}, timelineConfig()), {}, {
31539  subtitleStreamController: SubtitleStreamController ,
31540  subtitleTrackController: SubtitleTrackController ,
31541  timelineController: TimelineController ,
31542  audioStreamController: AudioStreamController ,
31543  audioTrackController: AudioTrackController ,
31544  emeController: EMEController ,
31545  cmcdController: CMCDController ,
31546  contentSteeringController: ContentSteeringController ,
31547  interstitialsController: InterstitialsController 
31548});
31549function timelineConfig() {
31550  return {
31551    cueHandler: Cues,
31552    // used by timeline-controller
31553    enableWebVTT: true,
31554    // used by timeline-controller
31555    enableIMSC1: true,
31556    // used by timeline-controller
31557    enableCEA708Captions: true,
31558    // used by timeline-controller
31559    captionsTextTrack1Label: 'English',
31560    // used by timeline-controller
31561    captionsTextTrack1LanguageCode: 'en',
31562    // used by timeline-controller
31563    captionsTextTrack2Label: 'Spanish',
31564    // used by timeline-controller
31565    captionsTextTrack2LanguageCode: 'es',
31566    // used by timeline-controller
31567    captionsTextTrack3Label: 'Unknown CC',
31568    // used by timeline-controller
31569    captionsTextTrack3LanguageCode: '',
31570    // used by timeline-controller
31571    captionsTextTrack4Label: 'Unknown CC',
31572    // used by timeline-controller
31573    captionsTextTrack4LanguageCode: '',
31574    // used by timeline-controller
31575    renderTextTracksNatively: true
31576  };
31577}
31578
31579/**
31580 * @ignore
31581 */
31582function mergeConfig(defaultConfig, userConfig, logger) {
31583  if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
31584    throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
31585  }
31586  if (userConfig.liveMaxLatencyDurationCount !== undefined && (userConfig.liveSyncDurationCount === undefined || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) {
31587    throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"');
31588  }
31589  if (userConfig.liveMaxLatencyDuration !== undefined && (userConfig.liveSyncDuration === undefined || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) {
31590    throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"');
31591  }
31592  const defaultsCopy = deepCpy(defaultConfig);
31593
31594  // Backwards compatibility with deprecated config values
31595  const deprecatedSettingTypes = ['manifest', 'level', 'frag'];
31596  const deprecatedSettings = ['TimeOut', 'MaxRetry', 'RetryDelay', 'MaxRetryTimeout'];
31597  deprecatedSettingTypes.forEach(type => {
31598    const policyName = `${type === 'level' ? 'playlist' : type}LoadPolicy`;
31599    const policyNotSet = userConfig[policyName] === undefined;
31600    const report = [];
31601    deprecatedSettings.forEach(setting => {
31602      const deprecatedSetting = `${type}Loading${setting}`;
31603      const value = userConfig[deprecatedSetting];
31604      if (value !== undefined && policyNotSet) {
31605        report.push(deprecatedSetting);
31606        const settings = defaultsCopy[policyName].default;
31607        userConfig[policyName] = {
31608          default: settings
31609        };
31610        switch (setting) {
31611          case 'TimeOut':
31612            settings.maxLoadTimeMs = value;
31613            settings.maxTimeToFirstByteMs = value;
31614            break;
31615          case 'MaxRetry':
31616            settings.errorRetry.maxNumRetry = value;
31617            settings.timeoutRetry.maxNumRetry = value;
31618            break;
31619          case 'RetryDelay':
31620            settings.errorRetry.retryDelayMs = value;
31621            settings.timeoutRetry.retryDelayMs = value;
31622            break;
31623          case 'MaxRetryTimeout':
31624            settings.errorRetry.maxRetryDelayMs = value;
31625            settings.timeoutRetry.maxRetryDelayMs = value;
31626            break;
31627        }
31628      }
31629    });
31630    if (report.length) {
31631      logger.warn(`hls.js config: "${report.join('", "')}" setting(s) are deprecated, use "${policyName}": ${stringify(userConfig[policyName])}`);
31632    }
31633  });
31634  return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig);
31635}
31636function deepCpy(obj) {
31637  if (obj && typeof obj === 'object') {
31638    if (Array.isArray(obj)) {
31639      return obj.map(deepCpy);
31640    }
31641    return Object.keys(obj).reduce((result, key) => {
31642      result[key] = deepCpy(obj[key]);
31643      return result;
31644    }, {});
31645  }
31646  return obj;
31647}
31648
31649/**
31650 * @ignore
31651 */
31652function enableStreamingMode(config, logger) {
31653  const currentLoader = config.loader;
31654  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
31655    // If a developer has configured their own loader, respect that choice
31656    logger.log('[config]: Custom loader detected, cannot enable progressive streaming');
31657    config.progressive = false;
31658  } else {
31659    const canStreamProgressively = fetchSupported();
31660    if (canStreamProgressively) {
31661      config.loader = FetchLoader;
31662      config.progressive = true;
31663      config.enableSoftwareAES = true;
31664      logger.log('[config]: Progressive streaming enabled, using FetchLoader');
31665    }
31666  }
31667}
31668
31669const MAX_START_GAP_JUMP = 2.0;
31670const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
31671const SKIP_BUFFER_RANGE_START = 0.05;
31672const TICK_INTERVAL$1 = 100;
31673class GapController extends TaskLoop {
31674  constructor(hls, fragmentTracker) {
31675    super('gap-controller', hls.logger);
31676    this.hls = void 0;
31677    this.fragmentTracker = void 0;
31678    this.media = null;
31679    this.mediaSource = void 0;
31680    this.nudgeRetry = 0;
31681    this.stallReported = false;
31682    this.stalled = null;
31683    this.moved = false;
31684    this.seeking = false;
31685    this.buffered = {};
31686    this.lastCurrentTime = 0;
31687    this.ended = 0;
31688    this.waiting = 0;
31689    this.onMediaPlaying = () => {
31690      this.ended = 0;
31691      this.waiting = 0;
31692    };
31693    this.onMediaWaiting = () => {
31694      var _this$media;
31695      if ((_this$media = this.media) != null && _this$media.seeking) {
31696        return;
31697      }
31698      this.waiting = self.performance.now();
31699      this.tick();
31700    };
31701    this.onMediaEnded = () => {
31702      if (this.hls) {
31703        var _this$media2;
31704        // ended is set when triggering MEDIA_ENDED so that we do not trigger it again on stall or on tick with media.ended
31705        this.ended = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || 1;
31706        this.hls.trigger(Events.MEDIA_ENDED, {
31707          stalled: false
31708        });
31709      }
31710    };
31711    this.hls = hls;
31712    this.fragmentTracker = fragmentTracker;
31713    this.registerListeners();
31714  }
31715  registerListeners() {
31716    const {
31717      hls
31718    } = this;
31719    if (hls) {
31720      hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
31721      hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
31722      hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
31723    }
31724  }
31725  unregisterListeners() {
31726    const {
31727      hls
31728    } = this;
31729    if (hls) {
31730      hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
31731      hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
31732      hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
31733    }
31734  }
31735  destroy() {
31736    super.destroy();
31737    this.unregisterListeners();
31738    this.media = this.hls = this.fragmentTracker = null;
31739    this.mediaSource = undefined;
31740  }
31741  onMediaAttached(event, data) {
31742    this.setInterval(TICK_INTERVAL$1);
31743    this.mediaSource = data.mediaSource;
31744    const media = this.media = data.media;
31745    addEventListener(media, 'playing', this.onMediaPlaying);
31746    addEventListener(media, 'waiting', this.onMediaWaiting);
31747    addEventListener(media, 'ended', this.onMediaEnded);
31748  }
31749  onMediaDetaching(event, data) {
31750    this.clearInterval();
31751    const {
31752      media
31753    } = this;
31754    if (media) {
31755      removeEventListener(media, 'playing', this.onMediaPlaying);
31756      removeEventListener(media, 'waiting', this.onMediaWaiting);
31757      removeEventListener(media, 'ended', this.onMediaEnded);
31758      this.media = null;
31759    }
31760    this.mediaSource = undefined;
31761  }
31762  onBufferAppended(event, data) {
31763    this.buffered = data.timeRanges;
31764  }
31765  get hasBuffered() {
31766    return Object.keys(this.buffered).length > 0;
31767  }
31768  tick() {
31769    var _this$media3;
31770    if (!((_this$media3 = this.media) != null && _this$media3.readyState) || !this.hasBuffered) {
31771      return;
31772    }
31773    const currentTime = this.media.currentTime;
31774    this.poll(currentTime, this.lastCurrentTime);
31775    this.lastCurrentTime = currentTime;
31776  }
31777
31778  /**
31779   * Checks if the playhead is stuck within a gap, and if so, attempts to free it.
31780   * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range).
31781   *
31782   * @param lastCurrentTime - Previously read playhead position
31783   */
31784  poll(currentTime, lastCurrentTime) {
31785    var _this$hls, _this$hls2;
31786    const config = (_this$hls = this.hls) == null ? void 0 : _this$hls.config;
31787    if (!config) {
31788      return;
31789    }
31790    const media = this.media;
31791    if (!media) {
31792      return;
31793    }
31794    const {
31795      seeking
31796    } = media;
31797    const seeked = this.seeking && !seeking;
31798    const beginSeek = !this.seeking && seeking;
31799    const pausedEndedOrHalted = media.paused && !seeking || media.ended || media.playbackRate === 0;
31800    this.seeking = seeking;
31801
31802    // The playhead is moving, no-op
31803    if (currentTime !== lastCurrentTime) {
31804      if (lastCurrentTime) {
31805        this.ended = 0;
31806      }
31807      this.moved = true;
31808      if (!seeking) {
31809        this.nudgeRetry = 0;
31810        // When crossing between buffered video time ranges, but not audio, flush pipeline with seek (Chrome)
31811        if (config.nudgeOnVideoHole && !pausedEndedOrHalted && currentTime > lastCurrentTime) {
31812          this.nudgeOnVideoHole(currentTime, lastCurrentTime);
31813        }
31814      }
31815      if (this.waiting === 0) {
31816        this.stallResolved(currentTime);
31817      }
31818      return;
31819    }
31820
31821    // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek
31822    if (beginSeek || seeked) {
31823      if (seeked) {
31824        this.stallResolved(currentTime);
31825      }
31826      return;
31827    }
31828
31829    // The playhead should not be moving
31830    if (pausedEndedOrHalted) {
31831      this.nudgeRetry = 0;
31832      this.stallResolved(currentTime);
31833      // Fire MEDIA_ENDED to workaround event not being dispatched by browser
31834      if (!this.ended && media.ended && this.hls) {
31835        this.ended = currentTime || 1;
31836        this.hls.trigger(Events.MEDIA_ENDED, {
31837          stalled: false
31838        });
31839      }
31840      return;
31841    }
31842    if (!BufferHelper.getBuffered(media).length) {
31843      this.nudgeRetry = 0;
31844      return;
31845    }
31846
31847    // Resolve stalls at buffer holes using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
31848    const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
31849    const nextStart = bufferInfo.nextStart || 0;
31850    const fragmentTracker = this.fragmentTracker;
31851    if (seeking && fragmentTracker && this.hls) {
31852      // Is there a fragment loading/parsing/appending before currentTime?
31853      const inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, currentTime);
31854
31855      // Waiting for seeking in a buffered range to complete
31856      const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP;
31857      // Next buffered range is too far ahead to jump to while still seeking
31858      const noBufferHole = !nextStart || inFlightDependency || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime);
31859      if (hasEnoughBuffer || noBufferHole) {
31860        return;
31861      }
31862      // Reset moved state when seeking to a point in or before a gap/hole
31863      this.moved = false;
31864    }
31865
31866    // Skip start gaps if we haven't played, but the last poll detected the start of a stall
31867    // The addition poll gives the browser a chance to jump the gap for us
31868    const levelDetails = (_this$hls2 = this.hls) == null ? void 0 : _this$hls2.latestLevelDetails;
31869    if (!this.moved && this.stalled !== null && fragmentTracker) {
31870      // There is no playable buffer (seeked, waiting for buffer)
31871      const isBuffered = bufferInfo.len > 0;
31872      if (!isBuffered && !nextStart) {
31873        return;
31874      }
31875      // Jump start gaps within jump threshold
31876      const startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime;
31877
31878      // When joining a live stream with audio tracks, account for live playlist window sliding by allowing
31879      // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
31880      // that begins over 1 target duration after the video start position.
31881      const isLive = !!(levelDetails != null && levelDetails.live);
31882      const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
31883      const appended = appendedFragAtPosition(currentTime, fragmentTracker);
31884      if (startJump > 0 && (startJump <= maxStartGapJump || appended)) {
31885        if (!media.paused) {
31886          this._trySkipBufferHole(appended);
31887        }
31888        return;
31889      }
31890    }
31891
31892    // Start tracking stall time
31893    const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs;
31894    const tnow = self.performance.now();
31895    const tWaiting = this.waiting;
31896    let stalled = this.stalled;
31897    if (stalled === null) {
31898      // Use time of recent "waiting" event
31899      if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) {
31900        stalled = this.stalled = tWaiting;
31901      } else {
31902        this.stalled = tnow;
31903        return;
31904      }
31905    }
31906    const stalledDuration = tnow - stalled;
31907    if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) {
31908      var _this$mediaSource;
31909      // Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
31910      if (((_this$mediaSource = this.mediaSource) == null ? void 0 : _this$mediaSource.readyState) === 'ended' && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
31911        if (this.ended) {
31912          return;
31913        }
31914        this.ended = currentTime || 1;
31915        this.hls.trigger(Events.MEDIA_ENDED, {
31916          stalled: true
31917        });
31918        return;
31919      }
31920      // Report stalling after trying to fix
31921      this._reportStall(bufferInfo);
31922      if (!this.media || !this.hls) {
31923        return;
31924      }
31925    }
31926    const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole);
31927    this._tryFixBufferStall(bufferedWithHoles, stalledDuration, currentTime);
31928  }
31929  stallResolved(currentTime) {
31930    const stalled = this.stalled;
31931    if (stalled && this.hls) {
31932      this.stalled = null;
31933      // The playhead is now moving, but was previously stalled
31934      if (this.stallReported) {
31935        const stalledDuration = self.performance.now() - stalled;
31936        this.log(`playback not stuck anymore @${currentTime}, after ${Math.round(stalledDuration)}ms`);
31937        this.stallReported = false;
31938        this.waiting = 0;
31939        this.hls.trigger(Events.STALL_RESOLVED, {});
31940      }
31941    }
31942  }
31943  nudgeOnVideoHole(currentTime, lastCurrentTime) {
31944    var _this$buffered$audio;
31945    // Chrome will play one second past a hole in video buffered time ranges without rendering any video from the subsequent range and then stall as long as audio is buffered:
31946    // https://github.com/video-dev/hls.js/issues/5631
31947    // https://issues.chromium.org/issues/40280613#comment10
31948    // Detect the potential for this situation and proactively seek to flush the video pipeline once the playhead passes the start of the video hole.
31949    // When there are audio and video buffers and currentTime is past the end of the first video buffered range...
31950    const videoSourceBuffered = this.buffered.video;
31951    if (this.hls && this.media && this.fragmentTracker && (_this$buffered$audio = this.buffered.audio) != null && _this$buffered$audio.length && videoSourceBuffered && videoSourceBuffered.length > 1 && currentTime > videoSourceBuffered.end(0)) {
31952      // and audio is buffered at the playhead
31953      const audioBufferInfo = BufferHelper.bufferedInfo(BufferHelper.timeRangesToArray(this.buffered.audio), currentTime, 0);
31954      if (audioBufferInfo.len > 1 && lastCurrentTime >= audioBufferInfo.start) {
31955        const videoTimes = BufferHelper.timeRangesToArray(videoSourceBuffered);
31956        const lastBufferedIndex = BufferHelper.bufferedInfo(videoTimes, lastCurrentTime, 0).bufferedIndex;
31957        // nudge when crossing into another video buffered range (hole).
31958        if (lastBufferedIndex > -1 && lastBufferedIndex < videoTimes.length - 1) {
31959          const bufferedIndex = BufferHelper.bufferedInfo(videoTimes, currentTime, 0).bufferedIndex;
31960          const holeStart = videoTimes[lastBufferedIndex].end;
31961          const holeEnd = videoTimes[lastBufferedIndex + 1].start;
31962          if ((bufferedIndex === -1 || bufferedIndex > lastBufferedIndex) && holeEnd - holeStart < 1 &&
31963          // `maxBufferHole` may be too small and setting it to 0 should not disable this feature
31964          currentTime - holeStart < 2) {
31965            const error = new Error(`nudging playhead to flush pipeline after video hole. currentTime: ${currentTime} hole: ${holeStart} -> ${holeEnd} buffered index: ${bufferedIndex}`);
31966            this.warn(error.message);
31967            // Magic number to flush the pipeline without interuption to audio playback:
31968            this.media.currentTime += 0.000001;
31969            let frag = appendedFragAtPosition(currentTime, this.fragmentTracker);
31970            if (frag && 'fragment' in frag) {
31971              frag = frag.fragment;
31972            } else if (!frag) {
31973              frag = undefined;
31974            }
31975            const bufferInfo = BufferHelper.bufferInfo(this.media, currentTime, 0);
31976            this.hls.trigger(Events.ERROR, {
31977              type: ErrorTypes.MEDIA_ERROR,
31978              details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
31979              fatal: false,
31980              error,
31981              reason: error.message,
31982              frag,
31983              buffer: bufferInfo.len,
31984              bufferInfo
31985            });
31986          }
31987        }
31988      }
31989    }
31990  }
31991
31992  /**
31993   * Detects and attempts to fix known buffer stalling issues.
31994   * @param bufferInfo - The properties of the current buffer.
31995   * @param stalledDurationMs - The amount of time Hls.js has been stalling for.
31996   * @private
31997   */
31998  _tryFixBufferStall(bufferInfo, stalledDurationMs, currentTime) {
31999    var _this$hls3, _this$hls4;
32000    const {
32001      fragmentTracker,
32002      media
32003    } = this;
32004    const config = (_this$hls3 = this.hls) == null ? void 0 : _this$hls3.config;
32005    if (!media || !fragmentTracker || !config) {
32006      return;
32007    }
32008    const levelDetails = (_this$hls4 = this.hls) == null ? void 0 : _this$hls4.latestLevelDetails;
32009    const appended = appendedFragAtPosition(currentTime, fragmentTracker);
32010    if (appended || levelDetails != null && levelDetails.live && currentTime < levelDetails.fragmentStart) {
32011      // Try to skip over the buffer hole caused by a partial fragment
32012      // This method isn't limited by the size of the gap between buffered ranges
32013      const targetTime = this._trySkipBufferHole(appended);
32014      // we return here in this case, meaning
32015      // the branch below only executes when we haven't seeked to a new position
32016      if (targetTime || !this.media) {
32017        return;
32018      }
32019    }
32020
32021    // if we haven't had to skip over a buffer hole of a partial fragment
32022    // we may just have to "nudge" the playlist as the browser decoding/rendering engine
32023    // needs to cross some sort of threshold covering all source-buffers content
32024    // to start playing properly.
32025    const bufferedRanges = bufferInfo.buffered;
32026    const adjacentTraversal = this.adjacentTraversal(bufferInfo, currentTime);
32027    if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && (bufferInfo.nextStart - currentTime < config.maxBufferHole || adjacentTraversal)) && (stalledDurationMs > config.highBufferWatchdogPeriod * 1000 || this.waiting)) {
32028      this.warn('Trying to nudge playhead over buffer-hole');
32029      // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
32030      // We only try to jump the hole if it's under the configured size
32031      this._tryNudgeBuffer(bufferInfo);
32032    }
32033  }
32034  adjacentTraversal(bufferInfo, currentTime) {
32035    const fragmentTracker = this.fragmentTracker;
32036    const nextStart = bufferInfo.nextStart;
32037    if (fragmentTracker && nextStart) {
32038      const current = fragmentTracker.getFragAtPos(currentTime, PlaylistLevelType.MAIN);
32039      const next = fragmentTracker.getFragAtPos(nextStart, PlaylistLevelType.MAIN);
32040      if (current && next) {
32041        return next.sn - current.sn < 2;
32042      }
32043    }
32044    return false;
32045  }
32046
32047  /**
32048   * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period.
32049   * @param bufferLen - The playhead distance from the end of the current buffer segment.
32050   * @private
32051   */
32052  _reportStall(bufferInfo) {
32053    const {
32054      hls,
32055      media,
32056      stallReported,
32057      stalled
32058    } = this;
32059    if (!stallReported && stalled !== null && media && hls) {
32060      // Report stalled error once
32061      this.stallReported = true;
32062      const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${stringify(bufferInfo)})`);
32063      this.warn(error.message);
32064      hls.trigger(Events.ERROR, {
32065        type: ErrorTypes.MEDIA_ERROR,
32066        details: ErrorDetails.BUFFER_STALLED_ERROR,
32067        fatal: false,
32068        error,
32069        buffer: bufferInfo.len,
32070        bufferInfo,
32071        stalled: {
32072          start: stalled
32073        }
32074      });
32075    }
32076  }
32077
32078  /**
32079   * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments
32080   * @param appended - The fragment or part found at the current time (where playback is stalling).
32081   * @private
32082   */
32083  _trySkipBufferHole(appended) {
32084    var _this$hls5;
32085    const {
32086      fragmentTracker,
32087      media
32088    } = this;
32089    const config = (_this$hls5 = this.hls) == null ? void 0 : _this$hls5.config;
32090    if (!media || !fragmentTracker || !config) {
32091      return 0;
32092    }
32093
32094    // Check if currentTime is between unbuffered regions of partial fragments
32095    const currentTime = media.currentTime;
32096    const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0);
32097    const startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart;
32098    if (startTime && this.hls) {
32099      const bufferStarved = bufferInfo.len <= config.maxBufferHole;
32100      const waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3;
32101      const gapLength = startTime - currentTime;
32102      if (gapLength > 0 && (bufferStarved || waiting)) {
32103        // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial
32104        if (gapLength > config.maxBufferHole) {
32105          let startGap = false;
32106          if (currentTime === 0) {
32107            const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN);
32108            if (startFrag && startTime < startFrag.end) {
32109              startGap = true;
32110            }
32111          }
32112          if (!startGap && appended) {
32113            var _this$hls$loadLevelOb;
32114            // Do not seek when selected variant playlist is unloaded
32115            if (!((_this$hls$loadLevelOb = this.hls.loadLevelObj) != null && _this$hls$loadLevelOb.details)) {
32116              return 0;
32117            }
32118            // Do not seek when required fragments are inflight or appending
32119            const inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, startTime);
32120            if (inFlightDependency) {
32121              return 0;
32122            }
32123            // Do not seek if we can't walk tracked fragments to end of gap
32124            let moreToLoad = false;
32125            let pos = appended.end;
32126            while (pos < startTime) {
32127              const provisioned = appendedFragAtPosition(pos, fragmentTracker);
32128              if (provisioned) {
32129                pos += provisioned.duration;
32130              } else {
32131                moreToLoad = true;
32132                break;
32133              }
32134            }
32135            if (moreToLoad) {
32136              return 0;
32137            }
32138          }
32139        }
32140        const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
32141        this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
32142        this.moved = true;
32143        media.currentTime = targetTime;
32144        if (!(appended != null && appended.gap)) {
32145          const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`);
32146          const errorData = {
32147            type: ErrorTypes.MEDIA_ERROR,
32148            details: ErrorDetails.BUFFER_SEEK_OVER_HOLE,
32149            fatal: false,
32150            error,
32151            reason: error.message,
32152            buffer: bufferInfo.len,
32153            bufferInfo
32154          };
32155          if (appended) {
32156            if ('fragment' in appended) {
32157              errorData.part = appended;
32158            } else {
32159              errorData.frag = appended;
32160            }
32161          }
32162          this.hls.trigger(Events.ERROR, errorData);
32163        }
32164        return targetTime;
32165      }
32166    }
32167    return 0;
32168  }
32169
32170  /**
32171   * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount.
32172   * @private
32173   */
32174  _tryNudgeBuffer(bufferInfo) {
32175    const {
32176      hls,
32177      media,
32178      nudgeRetry
32179    } = this;
32180    const config = hls == null ? void 0 : hls.config;
32181    if (!media || !config) {
32182      return 0;
32183    }
32184    const currentTime = media.currentTime;
32185    this.nudgeRetry++;
32186    if (nudgeRetry < config.nudgeMaxRetry) {
32187      const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
32188      // playback stalled in buffered area ... let's nudge currentTime to try to overcome this
32189      const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
32190      this.warn(error.message);
32191      media.currentTime = targetTime;
32192      hls.trigger(Events.ERROR, {
32193        type: ErrorTypes.MEDIA_ERROR,
32194        details: ErrorDetails.BUFFER_NUDGE_ON_STALL,
32195        error,
32196        fatal: false,
32197        buffer: bufferInfo.len,
32198        bufferInfo
32199      });
32200    } else {
32201      const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
32202      this.error(error.message);
32203      hls.trigger(Events.ERROR, {
32204        type: ErrorTypes.MEDIA_ERROR,
32205        details: ErrorDetails.BUFFER_STALLED_ERROR,
32206        error,
32207        fatal: true,
32208        buffer: bufferInfo.len,
32209        bufferInfo
32210      });
32211    }
32212  }
32213}
32214function getInFlightDependency(inFlightFragments, currentTime) {
32215  const main = inFlight(inFlightFragments.main);
32216  if (main && main.start <= currentTime) {
32217    return main;
32218  }
32219  const audio = inFlight(inFlightFragments.audio);
32220  if (audio && audio.start <= currentTime) {
32221    return audio;
32222  }
32223  return null;
32224}
32225function inFlight(inFlightData) {
32226  if (!inFlightData) {
32227    return null;
32228  }
32229  switch (inFlightData.state) {
32230    case State.IDLE:
32231    case State.STOPPED:
32232    case State.ENDED:
32233    case State.ERROR:
32234      return null;
32235  }
32236  return inFlightData.frag;
32237}
32238function appendedFragAtPosition(pos, fragmentTracker) {
32239  return fragmentTracker.getAppendedFrag(pos, PlaylistLevelType.MAIN) || fragmentTracker.getPartialFragment(pos);
32240}
32241
32242const MIN_CUE_DURATION = 0.25;
32243function getCueClass() {
32244  if (typeof self === 'undefined') return undefined;
32245  return self.VTTCue || self.TextTrackCue;
32246}
32247function createCueWithDataFields(Cue, startTime, endTime, data, type) {
32248  let cue = new Cue(startTime, endTime, '');
32249  try {
32250    cue.value = data;
32251    if (type) {
32252      cue.type = type;
32253    }
32254  } catch (e) {
32255    cue = new Cue(startTime, endTime, stringify(type ? _objectSpread2({
32256      type
32257    }, data) : data));
32258  }
32259  return cue;
32260}
32261
32262// VTTCue latest draft allows an infinite duration, fallback
32263// to MAX_VALUE if necessary
32264const MAX_CUE_ENDTIME = (() => {
32265  const Cue = getCueClass();
32266  try {
32267    Cue && new Cue(0, Number.POSITIVE_INFINITY, '');
32268  } catch (e) {
32269    return Number.MAX_VALUE;
32270  }
32271  return Number.POSITIVE_INFINITY;
32272})();
32273class ID3TrackController {
32274  constructor(hls) {
32275    this.hls = void 0;
32276    this.id3Track = null;
32277    this.media = null;
32278    this.dateRangeCuesAppended = {};
32279    this.removeCues = true;
32280    this.assetCue = void 0;
32281    this.onEventCueEnter = () => {
32282      if (!this.hls) {
32283        return;
32284      }
32285      this.hls.trigger(Events.EVENT_CUE_ENTER, {});
32286    };
32287    this.hls = hls;
32288    this._registerListeners();
32289  }
32290  destroy() {
32291    this._unregisterListeners();
32292    this.id3Track = null;
32293    this.media = null;
32294    this.dateRangeCuesAppended = {};
32295    // @ts-ignore
32296    this.hls = this.onEventCueEnter = null;
32297  }
32298  _registerListeners() {
32299    const {
32300      hls
32301    } = this;
32302    if (hls) {
32303      hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
32304      hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
32305      hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
32306      hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
32307      hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
32308      hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
32309      hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
32310      hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
32311    }
32312  }
32313  _unregisterListeners() {
32314    const {
32315      hls
32316    } = this;
32317    if (hls) {
32318      hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this);
32319      hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
32320      hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
32321      hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
32322      hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this);
32323      hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
32324      hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
32325      hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this);
32326    }
32327  }
32328  // Add ID3 metatadata text track.
32329  onMediaAttaching(event, data) {
32330    var _data$overrides;
32331    this.media = data.media;
32332    if (((_data$overrides = data.overrides) == null ? void 0 : _data$overrides.cueRemoval) === false) {
32333      this.removeCues = false;
32334    }
32335  }
32336  onMediaAttached() {
32337    var _this$hls;
32338    const details = (_this$hls = this.hls) == null ? void 0 : _this$hls.latestLevelDetails;
32339    if (details) {
32340      this.updateDateRangeCues(details);
32341    }
32342  }
32343  onMediaDetaching(event, data) {
32344    this.media = null;
32345    const transferringMedia = !!data.transferMedia;
32346    if (transferringMedia) {
32347      return;
32348    }
32349    if (this.id3Track) {
32350      if (this.removeCues) {
32351        clearCurrentCues(this.id3Track, this.onEventCueEnter);
32352      }
32353      this.id3Track = null;
32354    }
32355    this.dateRangeCuesAppended = {};
32356  }
32357  onManifestLoading() {
32358    this.dateRangeCuesAppended = {};
32359  }
32360  createTrack(media) {
32361    const track = this.getID3Track(media.textTracks);
32362    track.mode = 'hidden';
32363    return track;
32364  }
32365  getID3Track(textTracks) {
32366    if (!this.media) {
32367      return;
32368    }
32369    for (let i = 0; i < textTracks.length; i++) {
32370      const textTrack = textTracks[i];
32371      if (textTrack.kind === 'metadata' && textTrack.label === 'id3') {
32372        // send 'addtrack' when reusing the textTrack for metadata,
32373        // same as what we do for captions
32374        sendAddTrackEvent(textTrack, this.media);
32375        return textTrack;
32376      }
32377    }
32378    return this.media.addTextTrack('metadata', 'id3');
32379  }
32380  onFragParsingMetadata(event, data) {
32381    if (!this.media || !this.hls) {
32382      return;
32383    }
32384    const {
32385      enableEmsgMetadataCues,
32386      enableID3MetadataCues
32387    } = this.hls.config;
32388    if (!enableEmsgMetadataCues && !enableID3MetadataCues) {
32389      return;
32390    }
32391    const {
32392      samples
32393    } = data;
32394
32395    // create track dynamically
32396    if (!this.id3Track) {
32397      this.id3Track = this.createTrack(this.media);
32398    }
32399    const Cue = getCueClass();
32400    if (!Cue) {
32401      return;
32402    }
32403    for (let i = 0; i < samples.length; i++) {
32404      const type = samples[i].type;
32405      if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) {
32406        continue;
32407      }
32408      const frames = getId3Frames(samples[i].data);
32409      const startTime = samples[i].pts;
32410      let endTime = startTime + samples[i].duration;
32411      if (endTime > MAX_CUE_ENDTIME) {
32412        endTime = MAX_CUE_ENDTIME;
32413      }
32414      const timeDiff = endTime - startTime;
32415      if (timeDiff <= 0) {
32416        endTime = startTime + MIN_CUE_DURATION;
32417      }
32418      for (let j = 0; j < frames.length; j++) {
32419        const frame = frames[j];
32420        // Safari doesn't put the timestamp frame in the TextTrack
32421        if (!isId3TimestampFrame(frame)) {
32422          // add a bounds to any unbounded cues
32423          this.updateId3CueEnds(startTime, type);
32424          const cue = createCueWithDataFields(Cue, startTime, endTime, frame, type);
32425          if (cue) {
32426            this.id3Track.addCue(cue);
32427          }
32428        }
32429      }
32430    }
32431  }
32432  updateId3CueEnds(startTime, type) {
32433    var _this$id3Track;
32434    const cues = (_this$id3Track = this.id3Track) == null ? void 0 : _this$id3Track.cues;
32435    if (cues) {
32436      for (let i = cues.length; i--;) {
32437        const cue = cues[i];
32438        if (cue.type === type && cue.startTime < startTime && cue.endTime === MAX_CUE_ENDTIME) {
32439          cue.endTime = startTime;
32440        }
32441      }
32442    }
32443  }
32444  onBufferFlushing(event, {
32445    startOffset,
32446    endOffset,
32447    type
32448  }) {
32449    const {
32450      id3Track,
32451      hls
32452    } = this;
32453    if (!hls) {
32454      return;
32455    }
32456    const {
32457      config: {
32458        enableEmsgMetadataCues,
32459        enableID3MetadataCues
32460      }
32461    } = hls;
32462    if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) {
32463      let predicate;
32464      if (type === 'audio') {
32465        predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues;
32466      } else if (type === 'video') {
32467        predicate = cue => cue.type === MetadataSchema.emsg && enableEmsgMetadataCues;
32468      } else {
32469        predicate = cue => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues || cue.type === MetadataSchema.emsg && enableEmsgMetadataCues;
32470      }
32471      removeCuesInRange(id3Track, startOffset, endOffset, predicate);
32472    }
32473  }
32474  onLevelUpdated(event, {
32475    details
32476  }) {
32477    this.updateDateRangeCues(details, true);
32478  }
32479  onLevelPtsUpdated(event, data) {
32480    if (Math.abs(data.drift) > 0.01) {
32481      this.updateDateRangeCues(data.details);
32482    }
32483  }
32484  updateDateRangeCues(details, removeOldCues) {
32485    if (!this.hls || !this.media) {
32486      return;
32487    }
32488    const {
32489      assetPlayerId,
32490      timelineOffset,
32491      enableDateRangeMetadataCues,
32492      interstitialsController
32493    } = this.hls.config;
32494    if (!enableDateRangeMetadataCues) {
32495      return;
32496    }
32497    const Cue = getCueClass();
32498    if (assetPlayerId && timelineOffset && !interstitialsController) {
32499      const {
32500        fragmentStart,
32501        fragmentEnd
32502      } = details;
32503      let cue = this.assetCue;
32504      if (cue) {
32505        cue.startTime = fragmentStart;
32506        cue.endTime = fragmentEnd;
32507      } else if (Cue) {
32508        cue = this.assetCue = createCueWithDataFields(Cue, fragmentStart, fragmentEnd, {
32509          assetPlayerId: this.hls.config.assetPlayerId
32510        }, 'hlsjs.interstitial.asset');
32511        if (cue) {
32512          cue.id = assetPlayerId;
32513          this.id3Track || (this.id3Track = this.createTrack(this.media));
32514          this.id3Track.addCue(cue);
32515          cue.addEventListener('enter', this.onEventCueEnter);
32516        }
32517      }
32518    }
32519    if (!details.hasProgramDateTime) {
32520      return;
32521    }
32522    const {
32523      id3Track
32524    } = this;
32525    const {
32526      dateRanges
32527    } = details;
32528    const ids = Object.keys(dateRanges);
32529    let dateRangeCuesAppended = this.dateRangeCuesAppended;
32530    // Remove cues from track not found in details.dateRanges
32531    if (id3Track && removeOldCues) {
32532      var _id3Track$cues;
32533      if ((_id3Track$cues = id3Track.cues) != null && _id3Track$cues.length) {
32534        const idsToRemove = Object.keys(dateRangeCuesAppended).filter(id => !ids.includes(id));
32535        for (let i = idsToRemove.length; i--;) {
32536          var _dateRangeCuesAppende;
32537          const id = idsToRemove[i];
32538          const cues = (_dateRangeCuesAppende = dateRangeCuesAppended[id]) == null ? void 0 : _dateRangeCuesAppende.cues;
32539          delete dateRangeCuesAppended[id];
32540          if (cues) {
32541            Object.keys(cues).forEach(key => {
32542              const cue = cues[key];
32543              if (cue) {
32544                cue.removeEventListener('enter', this.onEventCueEnter);
32545                try {
32546                  id3Track.removeCue(cue);
32547                } catch (e) {
32548                  /* no-op */
32549                }
32550              }
32551            });
32552          }
32553        }
32554      } else {
32555        dateRangeCuesAppended = this.dateRangeCuesAppended = {};
32556      }
32557    }
32558    // Exit if the playlist does not have Date Ranges or does not have Program Date Time
32559    const lastFragment = details.fragments[details.fragments.length - 1];
32560    if (ids.length === 0 || !isFiniteNumber(lastFragment == null ? void 0 : lastFragment.programDateTime)) {
32561      return;
32562    }
32563    this.id3Track || (this.id3Track = this.createTrack(this.media));
32564    for (let i = 0; i < ids.length; i++) {
32565      const id = ids[i];
32566      const dateRange = dateRanges[id];
32567      const startTime = dateRange.startTime;
32568
32569      // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT)
32570      const appendedDateRangeCues = dateRangeCuesAppended[id];
32571      const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {};
32572      let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false;
32573      let endTime = MAX_CUE_ENDTIME;
32574      const {
32575        duration,
32576        endDate
32577      } = dateRange;
32578      if (endDate && duration !== null) {
32579        endTime = startTime + duration;
32580        durationKnown = true;
32581      } else if (dateRange.endOnNext && !durationKnown) {
32582        const nextDateRangeWithSameClass = ids.reduce((candidateDateRange, id) => {
32583          if (id !== dateRange.id) {
32584            const otherDateRange = dateRanges[id];
32585            if (otherDateRange.class === dateRange.class && otherDateRange.startDate > dateRange.startDate && (!candidateDateRange || dateRange.startDate < candidateDateRange.startDate)) {
32586              return otherDateRange;
32587            }
32588          }
32589          return candidateDateRange;
32590        }, null);
32591        if (nextDateRangeWithSameClass) {
32592          endTime = nextDateRangeWithSameClass.startTime;
32593          durationKnown = true;
32594        }
32595      }
32596
32597      // Create TextTrack Cues for each MetadataGroup Item (select DateRange attribute)
32598      // This is to emulate Safari HLS playback handling of DateRange tags
32599      const attributes = Object.keys(dateRange.attr);
32600      for (let j = 0; j < attributes.length; j++) {
32601        const key = attributes[j];
32602        if (!isDateRangeCueAttribute(key)) {
32603          continue;
32604        }
32605        const cue = cues[key];
32606        if (cue) {
32607          if (durationKnown && !(appendedDateRangeCues != null && appendedDateRangeCues.durationKnown)) {
32608            cue.endTime = endTime;
32609          } else if (Math.abs(cue.startTime - startTime) > 0.01) {
32610            cue.startTime = startTime;
32611            cue.endTime = endTime;
32612          }
32613        } else if (Cue) {
32614          let data = dateRange.attr[key];
32615          if (isSCTE35Attribute(key)) {
32616            data = hexToArrayBuffer(data);
32617          }
32618          const payload = {
32619            key,
32620            data
32621          };
32622          const _cue = createCueWithDataFields(Cue, startTime, endTime, payload, MetadataSchema.dateRange);
32623          if (_cue) {
32624            _cue.id = id;
32625            this.id3Track.addCue(_cue);
32626            cues[key] = _cue;
32627            if (interstitialsController) {
32628              if (key === 'X-ASSET-LIST' || key === 'X-ASSET-URL') {
32629                _cue.addEventListener('enter', this.onEventCueEnter);
32630              }
32631            }
32632          }
32633        }
32634      }
32635
32636      // Keep track of processed DateRanges by ID for updating cues with new DateRange tag attributes
32637      dateRangeCuesAppended[id] = {
32638        cues,
32639        dateRange,
32640        durationKnown
32641      };
32642    }
32643  }
32644}
32645
32646class LatencyController {
32647  constructor(hls) {
32648    this.hls = void 0;
32649    this.config = void 0;
32650    this.media = null;
32651    this.currentTime = 0;
32652    this.stallCount = 0;
32653    this._latency = null;
32654    this._targetLatencyUpdated = false;
32655    this.onTimeupdate = () => {
32656      const {
32657        media
32658      } = this;
32659      const levelDetails = this.levelDetails;
32660      if (!media || !levelDetails) {
32661        return;
32662      }
32663      this.currentTime = media.currentTime;
32664      const latency = this.computeLatency();
32665      if (latency === null) {
32666        return;
32667      }
32668      this._latency = latency;
32669
32670      // Adapt playbackRate to meet target latency in low-latency mode
32671      const {
32672        lowLatencyMode,
32673        maxLiveSyncPlaybackRate
32674      } = this.config;
32675      if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
32676        return;
32677      }
32678      const targetLatency = this.targetLatency;
32679      if (targetLatency === null) {
32680        return;
32681      }
32682      const distanceFromTarget = latency - targetLatency;
32683      // Only adjust playbackRate when within one target duration of targetLatency
32684      // and more than one second from under-buffering.
32685      // Playback further than one target duration from target can be considered DVR playback.
32686      const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
32687      const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
32688      if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
32689        const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
32690        const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
32691        const playbackRate = Math.min(max, Math.max(1, rate));
32692        this.changeMediaPlaybackRate(media, playbackRate);
32693      } else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
32694        this.changeMediaPlaybackRate(media, 1);
32695      }
32696    };
32697    this.hls = hls;
32698    this.config = hls.config;
32699    this.registerListeners();
32700  }
32701  get levelDetails() {
32702    var _this$hls;
32703    return ((_this$hls = this.hls) == null ? void 0 : _this$hls.latestLevelDetails) || null;
32704  }
32705  get latency() {
32706    return this._latency || 0;
32707  }
32708  get maxLatency() {
32709    const {
32710      config
32711    } = this;
32712    if (config.liveMaxLatencyDuration !== undefined) {
32713      return config.liveMaxLatencyDuration;
32714    }
32715    const levelDetails = this.levelDetails;
32716    return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0;
32717  }
32718  get targetLatency() {
32719    const levelDetails = this.levelDetails;
32720    if (levelDetails === null || this.hls === null) {
32721      return null;
32722    }
32723    const {
32724      holdBack,
32725      partHoldBack,
32726      targetduration
32727    } = levelDetails;
32728    const {
32729      liveSyncDuration,
32730      liveSyncDurationCount,
32731      lowLatencyMode
32732    } = this.config;
32733    const userConfig = this.hls.userConfig;
32734    let targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack;
32735    if (this._targetLatencyUpdated || userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) {
32736      targetLatency = liveSyncDuration !== undefined ? liveSyncDuration : liveSyncDurationCount * targetduration;
32737    }
32738    const maxLiveSyncOnStallIncrease = targetduration;
32739    return targetLatency + Math.min(this.stallCount * this.config.liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease);
32740  }
32741  set targetLatency(latency) {
32742    this.stallCount = 0;
32743    this.config.liveSyncDuration = latency;
32744    this._targetLatencyUpdated = true;
32745  }
32746  get liveSyncPosition() {
32747    const liveEdge = this.estimateLiveEdge();
32748    const targetLatency = this.targetLatency;
32749    if (liveEdge === null || targetLatency === null) {
32750      return null;
32751    }
32752    const levelDetails = this.levelDetails;
32753    if (levelDetails === null) {
32754      return null;
32755    }
32756    const edge = levelDetails.edge;
32757    const syncPosition = liveEdge - targetLatency - this.edgeStalled;
32758    const min = edge - levelDetails.totalduration;
32759    const max = edge - (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration);
32760    return Math.min(Math.max(min, syncPosition), max);
32761  }
32762  get drift() {
32763    const levelDetails = this.levelDetails;
32764    if (levelDetails === null) {
32765      return 1;
32766    }
32767    return levelDetails.drift;
32768  }
32769  get edgeStalled() {
32770    const levelDetails = this.levelDetails;
32771    if (levelDetails === null) {
32772      return 0;
32773    }
32774    const maxLevelUpdateAge = (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3;
32775    return Math.max(levelDetails.age - maxLevelUpdateAge, 0);
32776  }
32777  get forwardBufferLength() {
32778    const {
32779      media
32780    } = this;
32781    const levelDetails = this.levelDetails;
32782    if (!media || !levelDetails) {
32783      return 0;
32784    }
32785    const bufferedRanges = media.buffered.length;
32786    return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime;
32787  }
32788  destroy() {
32789    this.unregisterListeners();
32790    this.onMediaDetaching();
32791    this.hls = null;
32792  }
32793  registerListeners() {
32794    const {
32795      hls
32796    } = this;
32797    if (!hls) {
32798      return;
32799    }
32800    hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
32801    hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
32802    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
32803    hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
32804    hls.on(Events.ERROR, this.onError, this);
32805  }
32806  unregisterListeners() {
32807    const {
32808      hls
32809    } = this;
32810    if (!hls) {
32811      return;
32812    }
32813    hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
32814    hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
32815    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
32816    hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
32817    hls.off(Events.ERROR, this.onError, this);
32818  }
32819  onMediaAttached(event, data) {
32820    this.media = data.media;
32821    this.media.addEventListener('timeupdate', this.onTimeupdate);
32822  }
32823  onMediaDetaching() {
32824    if (this.media) {
32825      this.media.removeEventListener('timeupdate', this.onTimeupdate);
32826      this.media = null;
32827    }
32828  }
32829  onManifestLoading() {
32830    this._latency = null;
32831    this.stallCount = 0;
32832  }
32833  onLevelUpdated(event, {
32834    details
32835  }) {
32836    if (details.advanced) {
32837      this.onTimeupdate();
32838    }
32839    if (!details.live && this.media) {
32840      this.media.removeEventListener('timeupdate', this.onTimeupdate);
32841    }
32842  }
32843  onError(event, data) {
32844    var _this$levelDetails;
32845    if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) {
32846      return;
32847    }
32848    this.stallCount++;
32849    if (this.hls && (_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
32850      this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
32851    }
32852  }
32853  changeMediaPlaybackRate(media, playbackRate) {
32854    var _this$hls2, _this$targetLatency;
32855    if (media.playbackRate === playbackRate) {
32856      return;
32857    }
32858    (_this$hls2 = this.hls) == null || _this$hls2.logger.debug(`[latency-controller]: latency=${this.latency.toFixed(3)}, targetLatency=${(_this$targetLatency = this.targetLatency) == null ? void 0 : _this$targetLatency.toFixed(3)}, forwardBufferLength=${this.forwardBufferLength.toFixed(3)}: adjusting playback rate from ${media.playbackRate} to ${playbackRate}`);
32859    media.playbackRate = playbackRate;
32860  }
32861  estimateLiveEdge() {
32862    const levelDetails = this.levelDetails;
32863    if (levelDetails === null) {
32864      return null;
32865    }
32866    return levelDetails.edge + levelDetails.age;
32867  }
32868  computeLatency() {
32869    const liveEdge = this.estimateLiveEdge();
32870    if (liveEdge === null) {
32871      return null;
32872    }
32873    return liveEdge - this.currentTime;
32874  }
32875}
32876
32877class LevelController extends BasePlaylistController {
32878  constructor(hls, contentSteeringController) {
32879    super(hls, 'level-controller');
32880    this._levels = [];
32881    this._firstLevel = -1;
32882    this._maxAutoLevel = -1;
32883    this._startLevel = void 0;
32884    this.currentLevel = null;
32885    this.currentLevelIndex = -1;
32886    this.manualLevelIndex = -1;
32887    this.steering = void 0;
32888    this.onParsedComplete = void 0;
32889    this.steering = contentSteeringController;
32890    this._registerListeners();
32891  }
32892  _registerListeners() {
32893    const {
32894      hls
32895    } = this;
32896    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
32897    hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
32898    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
32899    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
32900    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
32901    hls.on(Events.ERROR, this.onError, this);
32902  }
32903  _unregisterListeners() {
32904    const {
32905      hls
32906    } = this;
32907    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
32908    hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
32909    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
32910    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
32911    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
32912    hls.off(Events.ERROR, this.onError, this);
32913  }
32914  destroy() {
32915    this._unregisterListeners();
32916    this.steering = null;
32917    this.resetLevels();
32918    super.destroy();
32919  }
32920  stopLoad() {
32921    const levels = this._levels;
32922
32923    // clean up live level details to force reload them, and reset load errors
32924    levels.forEach(level => {
32925      level.loadError = 0;
32926      level.fragmentError = 0;
32927    });
32928    super.stopLoad();
32929  }
32930  resetLevels() {
32931    this._startLevel = undefined;
32932    this.manualLevelIndex = -1;
32933    this.currentLevelIndex = -1;
32934    this.currentLevel = null;
32935    this._levels = [];
32936    this._maxAutoLevel = -1;
32937  }
32938  onManifestLoading(event, data) {
32939    this.resetLevels();
32940  }
32941  onManifestLoaded(event, data) {
32942    const preferManagedMediaSource = this.hls.config.preferManagedMediaSource;
32943    const levels = [];
32944    const redundantSet = {};
32945    const generatePathwaySet = {};
32946    let resolutionFound = false;
32947    let videoCodecFound = false;
32948    let audioCodecFound = false;
32949    data.levels.forEach(levelParsed => {
32950      const attributes = levelParsed.attrs;
32951      let {
32952        audioCodec,
32953        videoCodec
32954      } = levelParsed;
32955      if (audioCodec) {
32956        // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
32957        levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
32958      }
32959      if (videoCodec) {
32960        videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
32961      }
32962
32963      // only keep levels with supported audio/video codecs
32964      const {
32965        width,
32966        height,
32967        unknownCodecs
32968      } = levelParsed;
32969      const unknownUnsupportedCodecCount = (unknownCodecs == null ? void 0 : unknownCodecs.length) || 0;
32970      resolutionFound || (resolutionFound = !!(width && height));
32971      videoCodecFound || (videoCodecFound = !!videoCodec);
32972      audioCodecFound || (audioCodecFound = !!audioCodec);
32973      if (unknownUnsupportedCodecCount || audioCodec && !this.isAudioSupported(audioCodec) || videoCodec && !this.isVideoSupported(videoCodec)) {
32974        this.log(`Some or all CODECS not supported "${attributes.CODECS}"`);
32975        return;
32976      }
32977      const {
32978        CODECS,
32979        'FRAME-RATE': FRAMERATE,
32980        'HDCP-LEVEL': HDCP,
32981        'PATHWAY-ID': PATHWAY,
32982        RESOLUTION,
32983        'VIDEO-RANGE': VIDEO_RANGE
32984      } = attributes;
32985      const contentSteeringPrefix = `${PATHWAY || '.'}-`;
32986      const levelKey = `${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}-${VIDEO_RANGE}-${HDCP}`;
32987      if (!redundantSet[levelKey]) {
32988        const level = this.createLevel(levelParsed);
32989        redundantSet[levelKey] = level;
32990        generatePathwaySet[levelKey] = 1;
32991        levels.push(level);
32992      } else if (redundantSet[levelKey].uri !== levelParsed.url && !levelParsed.attrs['PATHWAY-ID']) {
32993        // Assign Pathway IDs to Redundant Streams (default Pathways is ".". Redundant Streams "..", "...", and so on.)
32994        // Content Steering controller to handles Pathway fallback on error
32995        const pathwayCount = generatePathwaySet[levelKey] += 1;
32996        levelParsed.attrs['PATHWAY-ID'] = new Array(pathwayCount + 1).join('.');
32997        const level = this.createLevel(levelParsed);
32998        redundantSet[levelKey] = level;
32999        levels.push(level);
33000      } else {
33001        redundantSet[levelKey].addGroupId('audio', attributes.AUDIO);
33002        redundantSet[levelKey].addGroupId('text', attributes.SUBTITLES);
33003      }
33004    });
33005    this.filterAndSortMediaOptions(levels, data, resolutionFound, videoCodecFound, audioCodecFound);
33006  }
33007  createLevel(levelParsed) {
33008    const level = new Level(levelParsed);
33009    const supplemental = levelParsed.supplemental;
33010    if (supplemental != null && supplemental.videoCodec && !this.isVideoSupported(supplemental.videoCodec)) {
33011      const error = new Error(`SUPPLEMENTAL-CODECS not supported "${supplemental.videoCodec}"`);
33012      this.log(error.message);
33013      level.supportedResult = getUnsupportedResult(error, []);
33014    }
33015    return level;
33016  }
33017  isAudioSupported(codec) {
33018    return areCodecsMediaSourceSupported(codec, 'audio', this.hls.config.preferManagedMediaSource);
33019  }
33020  isVideoSupported(codec) {
33021    return areCodecsMediaSourceSupported(codec, 'video', this.hls.config.preferManagedMediaSource);
33022  }
33023  filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound) {
33024    var _data$stats;
33025    let audioTracks = [];
33026    let subtitleTracks = [];
33027    let levels = filteredLevels;
33028    const statsParsing = ((_data$stats = data.stats) == null ? void 0 : _data$stats.parsing) || {};
33029
33030    // remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled
33031    if ((resolutionFound || videoCodecFound) && audioCodecFound) {
33032      levels = levels.filter(({
33033        videoCodec,
33034        videoRange,
33035        width,
33036        height
33037      }) => (!!videoCodec || !!(width && height)) && isVideoRange(videoRange));
33038    }
33039    if (levels.length === 0) {
33040      // Dispatch error after MANIFEST_LOADED is done propagating
33041      // eslint-disable-next-line @typescript-eslint/no-floating-promises
33042      Promise.resolve().then(() => {
33043        if (this.hls) {
33044          let message = 'no level with compatible codecs found in manifest';
33045          let reason = message;
33046          if (data.levels.length) {
33047            reason = `one or more CODECS in variant not supported: ${stringify(data.levels.map(level => level.attrs.CODECS).filter((value, index, array) => array.indexOf(value) === index))}`;
33048            this.warn(reason);
33049            message += ` (${reason})`;
33050          }
33051          const error = new Error(message);
33052          this.hls.trigger(Events.ERROR, {
33053            type: ErrorTypes.MEDIA_ERROR,
33054            details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR,
33055            fatal: true,
33056            url: data.url,
33057            error,
33058            reason
33059          });
33060        }
33061      });
33062      statsParsing.end = performance.now();
33063      return;
33064    }
33065    if (data.audioTracks) {
33066      audioTracks = data.audioTracks.filter(track => !track.audioCodec || this.isAudioSupported(track.audioCodec));
33067      // Assign ids after filtering as array indices by group-id
33068      assignTrackIdsByGroup(audioTracks);
33069    }
33070    if (data.subtitles) {
33071      subtitleTracks = data.subtitles;
33072      assignTrackIdsByGroup(subtitleTracks);
33073    }
33074    // start bitrate is the first bitrate of the manifest
33075    const unsortedLevels = levels.slice(0);
33076    // sort levels from lowest to highest
33077    levels.sort((a, b) => {
33078      if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) {
33079        return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '') ? 1 : -1;
33080      }
33081      // sort on height before bitrate for cap-level-controller
33082      if (resolutionFound && a.height !== b.height) {
33083        return a.height - b.height;
33084      }
33085      if (a.frameRate !== b.frameRate) {
33086        return a.frameRate - b.frameRate;
33087      }
33088      if (a.videoRange !== b.videoRange) {
33089        return VideoRangeValues.indexOf(a.videoRange) - VideoRangeValues.indexOf(b.videoRange);
33090      }
33091      if (a.videoCodec !== b.videoCodec) {
33092        const valueA = videoCodecPreferenceValue(a.videoCodec);
33093        const valueB = videoCodecPreferenceValue(b.videoCodec);
33094        if (valueA !== valueB) {
33095          return valueB - valueA;
33096        }
33097      }
33098      if (a.uri === b.uri && a.codecSet !== b.codecSet) {
33099        const valueA = codecsSetSelectionPreferenceValue(a.codecSet);
33100        const valueB = codecsSetSelectionPreferenceValue(b.codecSet);
33101        if (valueA !== valueB) {
33102          return valueB - valueA;
33103        }
33104      }
33105      if (a.averageBitrate !== b.averageBitrate) {
33106        return a.averageBitrate - b.averageBitrate;
33107      }
33108      return 0;
33109    });
33110    let firstLevelInPlaylist = unsortedLevels[0];
33111    if (this.steering) {
33112      levels = this.steering.filterParsedLevels(levels);
33113      if (levels.length !== unsortedLevels.length) {
33114        for (let i = 0; i < unsortedLevels.length; i++) {
33115          if (unsortedLevels[i].pathwayId === levels[0].pathwayId) {
33116            firstLevelInPlaylist = unsortedLevels[i];
33117            break;
33118          }
33119        }
33120      }
33121    }
33122    this._levels = levels;
33123
33124    // find index of first level in sorted levels
33125    for (let i = 0; i < levels.length; i++) {
33126      if (levels[i] === firstLevelInPlaylist) {
33127        var _this$hls$userConfig;
33128        this._firstLevel = i;
33129        const firstLevelBitrate = firstLevelInPlaylist.bitrate;
33130        const bandwidthEstimate = this.hls.bandwidthEstimate;
33131        this.log(`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`);
33132        // Update default bwe to first variant bitrate as long it has not been configured or set
33133        if (((_this$hls$userConfig = this.hls.userConfig) == null ? void 0 : _this$hls$userConfig.abrEwmaDefaultEstimate) === undefined) {
33134          const startingBwEstimate = Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax);
33135          if (startingBwEstimate > bandwidthEstimate && bandwidthEstimate === this.hls.abrEwmaDefaultEstimate) {
33136            this.hls.bandwidthEstimate = startingBwEstimate;
33137          }
33138        }
33139        break;
33140      }
33141    }
33142
33143    // Audio is only alternate if manifest include a URI along with the audio group tag,
33144    // and this is not an audio-only stream where levels contain audio-only
33145    const audioOnly = audioCodecFound && !videoCodecFound;
33146    const config = this.hls.config;
33147    const altAudioEnabled = !!(config.audioStreamController && config.audioTrackController);
33148    const edata = {
33149      levels,
33150      audioTracks,
33151      subtitleTracks,
33152      sessionData: data.sessionData,
33153      sessionKeys: data.sessionKeys,
33154      firstLevel: this._firstLevel,
33155      stats: data.stats,
33156      audio: audioCodecFound,
33157      video: videoCodecFound,
33158      altAudio: altAudioEnabled && !audioOnly && audioTracks.some(t => !!t.url)
33159    };
33160    statsParsing.end = performance.now();
33161    this.hls.trigger(Events.MANIFEST_PARSED, edata);
33162  }
33163  get levels() {
33164    if (this._levels.length === 0) {
33165      return null;
33166    }
33167    return this._levels;
33168  }
33169  get loadLevelObj() {
33170    return this.currentLevel;
33171  }
33172  get level() {
33173    return this.currentLevelIndex;
33174  }
33175  set level(newLevel) {
33176    const levels = this._levels;
33177    if (levels.length === 0) {
33178      return;
33179    }
33180    // check if level idx is valid
33181    if (newLevel < 0 || newLevel >= levels.length) {
33182      // invalid level id given, trigger error
33183      const error = new Error('invalid level idx');
33184      const fatal = newLevel < 0;
33185      this.hls.trigger(Events.ERROR, {
33186        type: ErrorTypes.OTHER_ERROR,
33187        details: ErrorDetails.LEVEL_SWITCH_ERROR,
33188        level: newLevel,
33189        fatal,
33190        error,
33191        reason: error.message
33192      });
33193      if (fatal) {
33194        return;
33195      }
33196      newLevel = Math.min(newLevel, levels.length - 1);
33197    }
33198    const lastLevelIndex = this.currentLevelIndex;
33199    const lastLevel = this.currentLevel;
33200    const lastPathwayId = lastLevel ? lastLevel.attrs['PATHWAY-ID'] : undefined;
33201    const level = levels[newLevel];
33202    const pathwayId = level.attrs['PATHWAY-ID'];
33203    this.currentLevelIndex = newLevel;
33204    this.currentLevel = level;
33205    if (lastLevelIndex === newLevel && lastLevel && lastPathwayId === pathwayId) {
33206      return;
33207    }
33208    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 : ''}`);
33209    const levelSwitchingData = {
33210      level: newLevel,
33211      attrs: level.attrs,
33212      details: level.details,
33213      bitrate: level.bitrate,
33214      averageBitrate: level.averageBitrate,
33215      maxBitrate: level.maxBitrate,
33216      realBitrate: level.realBitrate,
33217      width: level.width,
33218      height: level.height,
33219      codecSet: level.codecSet,
33220      audioCodec: level.audioCodec,
33221      videoCodec: level.videoCodec,
33222      audioGroups: level.audioGroups,
33223      subtitleGroups: level.subtitleGroups,
33224      loaded: level.loaded,
33225      loadError: level.loadError,
33226      fragmentError: level.fragmentError,
33227      name: level.name,
33228      id: level.id,
33229      uri: level.uri,
33230      url: level.url,
33231      urlId: 0,
33232      audioGroupIds: level.audioGroupIds,
33233      textGroupIds: level.textGroupIds
33234    };
33235    this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData);
33236    // check if we need to load playlist for this level
33237    const levelDetails = level.details;
33238    if (!levelDetails || levelDetails.live) {
33239      // level not retrieved yet, or live playlist we need to (re)load it
33240      const hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details, levelDetails);
33241      this.loadPlaylist(hlsUrlParameters);
33242    }
33243  }
33244  get manualLevel() {
33245    return this.manualLevelIndex;
33246  }
33247  set manualLevel(newLevel) {
33248    this.manualLevelIndex = newLevel;
33249    if (this._startLevel === undefined) {
33250      this._startLevel = newLevel;
33251    }
33252    if (newLevel !== -1) {
33253      this.level = newLevel;
33254    }
33255  }
33256  get firstLevel() {
33257    return this._firstLevel;
33258  }
33259  set firstLevel(newLevel) {
33260    this._firstLevel = newLevel;
33261  }
33262  get startLevel() {
33263    // Setting hls.startLevel (this._startLevel) overrides config.startLevel
33264    if (this._startLevel === undefined) {
33265      const configStartLevel = this.hls.config.startLevel;
33266      if (configStartLevel !== undefined) {
33267        return configStartLevel;
33268      }
33269      return this.hls.firstAutoLevel;
33270    }
33271    return this._startLevel;
33272  }
33273  set startLevel(newLevel) {
33274    this._startLevel = newLevel;
33275  }
33276  get pathways() {
33277    if (this.steering) {
33278      return this.steering.pathways();
33279    }
33280    return [];
33281  }
33282  get pathwayPriority() {
33283    if (this.steering) {
33284      return this.steering.pathwayPriority;
33285    }
33286    return null;
33287  }
33288  set pathwayPriority(pathwayPriority) {
33289    if (this.steering) {
33290      const pathwaysList = this.steering.pathways();
33291      const filteredPathwayPriority = pathwayPriority.filter(pathwayId => {
33292        return pathwaysList.indexOf(pathwayId) !== -1;
33293      });
33294      if (pathwayPriority.length < 1) {
33295        this.warn(`pathwayPriority ${pathwayPriority} should contain at least one pathway from list: ${pathwaysList}`);
33296        return;
33297      }
33298      this.steering.pathwayPriority = filteredPathwayPriority;
33299    }
33300  }
33301  onError(event, data) {
33302    if (data.fatal || !data.context) {
33303      return;
33304    }
33305    if (data.context.type === PlaylistContextType.LEVEL && data.context.level === this.level) {
33306      this.checkRetry(data);
33307    }
33308  }
33309
33310  // reset errors on the successful load of a fragment
33311  onFragBuffered(event, {
33312    frag
33313  }) {
33314    if (frag !== undefined && frag.type === PlaylistLevelType.MAIN) {
33315      const el = frag.elementaryStreams;
33316      if (!Object.keys(el).some(type => !!el[type])) {
33317        return;
33318      }
33319      const level = this._levels[frag.level];
33320      if (level != null && level.loadError) {
33321        this.log(`Resetting level error count of ${level.loadError} on frag buffered`);
33322        level.loadError = 0;
33323      }
33324    }
33325  }
33326  onLevelLoaded(event, data) {
33327    var _data$deliveryDirecti2;
33328    const {
33329      level,
33330      details
33331    } = data;
33332    const curLevel = data.levelInfo;
33333    if (!curLevel) {
33334      var _data$deliveryDirecti;
33335      this.warn(`Invalid level index ${level}`);
33336      if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) {
33337        details.deltaUpdateFailed = true;
33338      }
33339      return;
33340    }
33341
33342    // only process level loaded events matching with expected level or prior to switch when media playlist is loaded directly
33343    if (curLevel === this.currentLevel || data.withoutMultiVariant) {
33344      // reset level load error counter on successful level loaded only if there is no issues with fragments
33345      if (curLevel.fragmentError === 0) {
33346        curLevel.loadError = 0;
33347      }
33348      // Ignore matching details populated by loading a Media Playlist directly
33349      let previousDetails = curLevel.details;
33350      if (previousDetails === data.details && previousDetails.advanced) {
33351        previousDetails = undefined;
33352      }
33353      this.playlistLoaded(level, data, previousDetails);
33354    } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
33355      // received a delta playlist update that cannot be merged
33356      details.deltaUpdateFailed = true;
33357    }
33358  }
33359  loadPlaylist(hlsUrlParameters) {
33360    super.loadPlaylist();
33361    if (this.shouldLoadPlaylist(this.currentLevel)) {
33362      this.scheduleLoading(this.currentLevel, hlsUrlParameters);
33363    }
33364  }
33365  loadingPlaylist(currentLevel, hlsUrlParameters) {
33366    super.loadingPlaylist(currentLevel, hlsUrlParameters);
33367    const url = this.getUrlWithDirectives(currentLevel.uri, hlsUrlParameters);
33368    const currentLevelIndex = this.currentLevelIndex;
33369    const pathwayId = currentLevel.attrs['PATHWAY-ID'];
33370    const details = currentLevel.details;
33371    const age = details == null ? void 0 : details.age;
33372    this.log(`Loading level index ${currentLevelIndex}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== undefined ? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part : ''}${pathwayId ? ' Pathway ' + pathwayId : ''}${age && details.live ? ' age ' + age.toFixed(1) + (details.type ? ' ' + details.type || 
333720
vendor: 12,252 bytes, lines 33372-33715
33372 : '') : ''} ${url}`);
33373    this.hls.trigger(Events.LEVEL_LOADING, {
33374      url,
33375      level: currentLevelIndex,
33376      levelInfo: currentLevel,
33377      pathwayId: currentLevel.attrs['PATHWAY-ID'],
33378      id: 0,
33379      // Deprecated Level urlId
33380      deliveryDirectives: hlsUrlParameters || null
33381    });
33382  }
33383  get nextLoadLevel() {
33384    if (this.manualLevelIndex !== -1) {
33385      return this.manualLevelIndex;
33386    } else {
33387      return this.hls.nextAutoLevel;
33388    }
33389  }
33390  set nextLoadLevel(nextLevel) {
33391    this.level = nextLevel;
33392    if (this.manualLevelIndex === -1) {
33393      this.hls.nextAutoLevel = nextLevel;
33394    }
33395  }
33396  removeLevel(levelIndex) {
33397    var _this$currentLevel;
33398    if (this._levels.length === 1) {
33399      return;
33400    }
33401    const levels = this._levels.filter((level, index) => {
33402      if (index !== levelIndex) {
33403        return true;
33404      }
33405      if (this.steering) {
33406        this.steering.removeLevel(level);
33407      }
33408      if (level === this.currentLevel) {
33409        this.currentLevel = null;
33410        this.currentLevelIndex = -1;
33411        if (level.details) {
33412          level.details.fragments.forEach(f => f.level = -1);
33413        }
33414      }
33415      return false;
33416    });
33417    reassignFragmentLevelIndexes(levels);
33418    this._levels = levels;
33419    if (this.currentLevelIndex > -1 && (_this$currentLevel = this.currentLevel) != null && _this$currentLevel.details) {
33420      this.currentLevelIndex = this.currentLevel.details.fragments[0].level;
33421    }
33422    if (this.manualLevelIndex > -1) {
33423      this.manualLevelIndex = this.currentLevelIndex;
33424    }
33425    const maxLevel = levels.length - 1;
33426    this._firstLevel = Math.min(this._firstLevel, maxLevel);
33427    if (this._startLevel) {
33428      this._startLevel = Math.min(this._startLevel, maxLevel);
33429    }
33430    this.hls.trigger(Events.LEVELS_UPDATED, {
33431      levels
33432    });
33433  }
33434  onLevelsUpdated(event, {
33435    levels
33436  }) {
33437    this._levels = levels;
33438  }
33439  checkMaxAutoUpdated() {
33440    const {
33441      autoLevelCapping,
33442      maxAutoLevel,
33443      maxHdcpLevel
33444    } = this.hls;
33445    if (this._maxAutoLevel !== maxAutoLevel) {
33446      this._maxAutoLevel = maxAutoLevel;
33447      this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, {
33448        autoLevelCapping,
33449        levels: this.levels,
33450        maxAutoLevel,
33451        minAutoLevel: this.hls.minAutoLevel,
33452        maxHdcpLevel
33453      });
33454    }
33455  }
33456}
33457function assignTrackIdsByGroup(tracks) {
33458  const groups = {};
33459  tracks.forEach(track => {
33460    const groupId = track.groupId || '';
33461    track.id = groups[groupId] = groups[groupId] || 0;
33462    groups[groupId]++;
33463  });
33464}
33465
33466function getSourceBuffer() {
33467  return self.SourceBuffer || self.WebKitSourceBuffer;
33468}
33469function isMSESupported() {
33470  const mediaSource = getMediaSource();
33471  if (!mediaSource) {
33472    return false;
33473  }
33474
33475  // if SourceBuffer is exposed ensure its API is valid
33476  // Older browsers do not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible
33477  const sourceBuffer = getSourceBuffer();
33478  return !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === 'function' && typeof sourceBuffer.prototype.remove === 'function';
33479}
33480function isSupported() {
33481  if (!isMSESupported()) {
33482    return false;
33483  }
33484  const mediaSource = getMediaSource();
33485  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'))));
33486}
33487function changeTypeSupported() {
33488  var _sourceBuffer$prototy;
33489  const sourceBuffer = getSourceBuffer();
33490  return typeof (sourceBuffer == null || (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === 'function';
33491}
33492
33493const TICK_INTERVAL = 100; // how often to tick in ms
33494
33495class StreamController extends BaseStreamController {
33496  constructor(hls, fragmentTracker, keyLoader) {
33497    super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
33498    this.audioCodecSwap = false;
33499    this.level = -1;
33500    this._forceStartLoad = false;
33501    this._hasEnoughToStart = false;
33502    this.altAudio = 0;
33503    this.audioOnly = false;
33504    this.fragPlaying = null;
33505    this.fragLastKbps = 0;
33506    this.couldBacktrack = false;
33507    this.backtrackFragment = null;
33508    this.audioCodecSwitch = false;
33509    this.videoBuffer = null;
33510    this.onMediaPlaying = () => {
33511      // tick to speed up FRAG_CHANGED triggering
33512      this.tick();
33513    };
33514    this.onMediaSeeked = () => {
33515      const media = this.media;
33516      const currentTime = media ? media.currentTime : null;
33517      if (currentTime === null || !isFiniteNumber(currentTime)) {
33518        return;
33519      }
33520      this.log(`Media seeked to ${currentTime.toFixed(3)}`);
33521
33522      // If seeked was issued before buffer was appended do not tick immediately
33523      if (!this.getBufferedFrag(currentTime)) {
33524        return;
33525      }
33526      const bufferInfo = this.getFwdBufferInfoAtPos(media, currentTime, PlaylistLevelType.MAIN, 0);
33527      if (bufferInfo === null || bufferInfo.len === 0) {
33528        this.warn(`Main forward buffer length at ${currentTime} on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
33529        return;
33530      }
33531
33532      // tick to speed up FRAG_CHANGED triggering
33533      this.tick();
33534    };
33535    this.registerListeners();
33536  }
33537  registerListeners() {
33538    super.registerListeners();
33539    const {
33540      hls
33541    } = this;
33542    hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
33543    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
33544    hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
33545    hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
33546    hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
33547    hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
33548    hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
33549    hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
33550    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
33551    hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
33552  }
33553  unregisterListeners() {
33554    super.unregisterListeners();
33555    const {
33556      hls
33557    } = this;
33558    hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
33559    hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
33560    hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
33561    hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
33562    hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
33563    hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
33564    hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this);
33565    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
33566    hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
33567  }
33568  onHandlerDestroying() {
33569    // @ts-ignore
33570    this.onMediaPlaying = this.onMediaSeeked = null;
33571    this.unregisterListeners();
33572    super.onHandlerDestroying();
33573  }
33574  startLoad(startPosition, skipSeekToStartPosition) {
33575    if (this.levels) {
33576      const {
33577        lastCurrentTime,
33578        hls
33579      } = this;
33580      this.stopLoad();
33581      this.setInterval(TICK_INTERVAL);
33582      this.level = -1;
33583      if (!this.startFragRequested) {
33584        // determine load level
33585        let startLevel = hls.startLevel;
33586        if (startLevel === -1) {
33587          if (hls.config.testBandwidth && this.levels.length > 1) {
33588            // -1 : guess start Level by doing a bitrate test by loading first fragment of lowest quality level
33589            startLevel = 0;
33590            this.bitrateTest = true;
33591          } else {
33592            startLevel = hls.firstAutoLevel;
33593          }
33594        }
33595        // set new level to playlist loader : this will trigger start level load
33596        // hls.nextLoadLevel remains until it is set to a new value or until a new frag is successfully loaded
33597        hls.nextLoadLevel = startLevel;
33598        this.level = hls.loadLevel;
33599        this._hasEnoughToStart = !!skipSeekToStartPosition;
33600      }
33601      // if startPosition undefined but lastCurrentTime set, set startPosition to last currentTime
33602      if (lastCurrentTime > 0 && startPosition === -1 && !skipSeekToStartPosition) {
33603        this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`);
33604        startPosition = lastCurrentTime;
33605      }
33606      this.state = State.IDLE;
33607      this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset;
33608      this.startPosition = skipSeekToStartPosition ? -1 : startPosition;
33609      this.tick();
33610    } else {
33611      this._forceStartLoad = true;
33612      this.state = State.STOPPED;
33613    }
33614  }
33615  stopLoad() {
33616    this._forceStartLoad = false;
33617    super.stopLoad();
33618  }
33619  doTick() {
33620    switch (this.state) {
33621      case State.WAITING_LEVEL:
33622        {
33623          const {
33624            levels,
33625            level
33626          } = this;
33627          const currentLevel = levels == null ? void 0 : levels[level];
33628          const details = currentLevel == null ? void 0 : currentLevel.details;
33629          if (details && (!details.live || this.levelLastLoaded === currentLevel && !this.waitForLive(currentLevel))) {
33630            if (this.waitForCdnTuneIn(details)) {
33631              break;
33632            }
33633            this.state = State.IDLE;
33634            break;
33635          } else if (this.hls.nextLoadLevel !== this.level) {
33636            this.state = State.IDLE;
33637            break;
33638          }
33639          break;
33640        }
33641      case State.FRAG_LOADING_WAITING_RETRY:
33642        this.checkRetryDate();
33643        break;
33644    }
33645    if (this.state === State.IDLE) {
33646      this.doTickIdle();
33647    }
33648    this.onTickEnd();
33649  }
33650  onTickEnd() {
33651    var _this$media;
33652    super.onTickEnd();
33653    if ((_this$media = this.media) != null && _this$media.readyState && this.media.seeking === false) {
33654      this.lastCurrentTime = this.media.currentTime;
33655    }
33656    this.checkFragmentChanged();
33657  }
33658  doTickIdle() {
33659    const {
33660      hls,
33661      levelLastLoaded,
33662      levels,
33663      media
33664    } = this;
33665
33666    // if start level not parsed yet OR
33667    // if video not attached AND start fragment already requested OR start frag prefetch not enabled
33668    // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment
33669    if (levelLastLoaded === null || !media && !this.primaryPrefetch && (this.startFragRequested || !hls.config.startFragPrefetch)) {
33670      return;
33671    }
33672
33673    // If the "main" level is audio-only but we are loading an alternate track in the same group, do not load anything
33674    if (this.altAudio && this.audioOnly) {
33675      return;
33676    }
33677    const level = this.buffering ? hls.nextLoadLevel : hls.loadLevel;
33678    if (!(levels != null && levels[level])) {
33679      return;
33680    }
33681    const levelInfo = levels[level];
33682
33683    // if buffer length is less than maxBufLen try to load a new fragment
33684
33685    const bufferInfo = this.getMainFwdBufferInfo();
33686    if (bufferInfo === null) {
33687      return;
33688    }
33689    const lastDetails = this.getLevelDetails();
33690    if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) {
33691      const data = {};
33692      if (this.altAudio === 2) {
33693        data.type = 'video';
33694      }
33695      this.hls.trigger(Events.BUFFER_EOS, data);
33696      this.state = State.ENDED;
33697      return;
33698    }
33699    if (!this.buffering) {
33700      return;
33701    }
33702
33703    // set next load level : this will trigger a playlist load if needed
33704    if (hls.loadLevel !== level && hls.manualLevel === -1) {
33705      this.log(`Adapting to level ${level} from level ${this.level}`);
33706    }
33707    this.level = hls.nextLoadLevel = level;
33708    const levelDetails = levelInfo.details;
33709    // if level info not retrieved yet, switch state and wait for level retrieval
33710    // if live playlist, ensure that new playlist has been refreshed to avoid loading/try to load
33711    // a useless and outdated fragment (that might even introduce load error if it is already out of the live playlist)
33712    if (!levelDetails || this.state === State.WAITING_LEVEL || this.waitForLive(levelInfo)) {
33713      this.level = level;
33714      this.state = State.WAITING_LEVEL;
33715      this.startFragRequested = false;
vendor: 8,506 bytes, lines 33716-33917
33716      return;
33717    }
33718    const bufferLen = bufferInfo.len;
33719
33720    // 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
33721    const maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate);
33722
33723    // Stay idle if we are still with buffer margins
33724    if (bufferLen >= maxBufLen) {
33725      return;
33726    }
33727    if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) {
33728      this.backtrackFragment = null;
33729    }
33730    const targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end;
33731    let frag = this.getNextFragment(targetBufferTime, levelDetails);
33732    // Avoid backtracking by loading an earlier segment in streams with segments that do not start with a key frame (flagged by `couldBacktrack`)
33733    if (this.couldBacktrack && !this.fragPrevious && frag && isMediaFragment(frag) && this.fragmentTracker.getState(frag) !== FragmentState.OK) {
33734      var _this$backtrackFragme;
33735      const backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn;
33736      const fragIdx = backtrackSn - levelDetails.startSN;
33737      const backtrackFrag = levelDetails.fragments[fragIdx - 1];
33738      if (backtrackFrag && frag.cc === backtrackFrag.cc) {
33739        frag = backtrackFrag;
33740        this.fragmentTracker.removeFragment(backtrackFrag);
33741      }
33742    } else if (this.backtrackFragment && bufferInfo.len) {
33743      this.backtrackFragment = null;
33744    }
33745    // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
33746    if (frag && this.isLoopLoading(frag, targetBufferTime)) {
33747      const gapStart = frag.gap;
33748      if (!gapStart) {
33749        // Cleanup the fragment tracker before trying to find the next unbuffered fragment
33750        const type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO;
33751        const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
33752        if (mediaBuffer) {
33753          this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
33754        }
33755      }
33756      frag = this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen);
33757    }
33758    if (!frag) {
33759      return;
33760    }
33761    if (this.exceedsMaxBuffer(bufferInfo, maxBufLen, frag)) {
33762      return;
33763    }
33764    if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) {
33765      frag = frag.initSegment;
33766    }
33767    this.loadFragment(frag, levelInfo, targetBufferTime);
33768  }
33769  loadFragment(frag, level, targetBufferTime) {
33770    // Check if fragment is not loaded
33771    const fragState = this.fragmentTracker.getState(frag);
33772    if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) {
33773      if (!isMediaFragment(frag)) {
33774        this._loadInitSegment(frag, level);
33775      } else if (this.bitrateTest) {
33776        this.log(`Fragment ${frag.sn} of level ${frag.level} is being downloaded to test bitrate and will not be buffered`);
33777        this._loadBitrateTestFrag(frag, level);
33778      } else {
33779        super.loadFragment(frag, level, targetBufferTime);
33780      }
33781    } else {
33782      this.clearTrackerIfNeeded(frag);
33783    }
33784  }
33785  getBufferedFrag(position) {
33786    return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN);
33787  }
33788  followingBufferedFrag(frag) {
33789    if (frag) {
33790      // try to get range of next fragment (500ms after this range)
33791      return this.getBufferedFrag(frag.end + 0.5);
33792    }
33793    return null;
33794  }
33795
33796  /*
33797    on immediate level switch :
33798     - pause playback if playing
33799     - cancel any pending load request
33800     - and trigger a buffer flush
33801  */
33802  immediateLevelSwitch() {
33803    this.abortCurrentFrag();
33804    this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
33805    if (this.altAudio !== 0) {
33806      var _this$getLevelDetails;
33807      // If behind the live window, remove audio too to keep buffered media aligned
33808      const liveWindowStart = ((_this$getLevelDetails = this.getLevelDetails()) == null ? void 0 : _this$getLevelDetails.fragmentStart) || 0;
33809      if (liveWindowStart > this.lastCurrentTime) {
33810        super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
33811      }
33812    }
33813  }
33814
33815  /**
33816   * try to switch ASAP without breaking video playback:
33817   * in order to ensure smooth but quick level switching,
33818   * we need to find the next flushable buffer range
33819   * we should take into account new segment fetch time
33820   */
33821  nextLevelSwitch() {
33822    const {
33823      levels,
33824      media
33825    } = this;
33826    // ensure that media is defined and that metadata are available (to retrieve currentTime)
33827    if (media != null && media.readyState) {
33828      let fetchdelay;
33829      const fragPlayingCurrent = this.getAppendedFrag(media.currentTime);
33830      if (fragPlayingCurrent && fragPlayingCurrent.start > 1) {
33831        // flush buffer preceding current fragment (flush until current fragment start offset)
33832        // minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ...
33833        this.flushMainBuffer(0, fragPlayingCurrent.start - 1);
33834      }
33835      const levelDetails = this.getLevelDetails();
33836      if (levelDetails != null && levelDetails.live) {
33837        const bufferInfo = this.getMainFwdBufferInfo();
33838        // Do not flush in live stream with low buffer
33839        if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) {
33840          return;
33841        }
33842      }
33843      if (!media.paused && levels) {
33844        // add a safety delay of 1s
33845        const nextLevelId = this.hls.nextLoadLevel;
33846        const nextLevel = levels[nextLevelId];
33847        const fragLastKbps = this.fragLastKbps;
33848        if (fragLastKbps && this.fragCurrent) {
33849          fetchdelay = this.fragCurrent.duration * nextLevel.maxBitrate / (1000 * fragLastKbps) + 1;
33850        } else {
33851          fetchdelay = 0;
33852        }
33853      } else {
33854        fetchdelay = 0;
33855      }
33856      // this.log('fetchdelay:'+fetchdelay);
33857      // find buffer range that will be reached once new fragment will be fetched
33858      const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay);
33859      if (bufferedFrag) {
33860        // we can flush buffer range following this one without stalling playback
33861        const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag);
33862        if (nextBufferedFrag) {
33863          // if we are here, we can also cancel any loading/demuxing in progress, as they are useless
33864          this.abortCurrentFrag();
33865          // start flush position is in next buffered frag. Leave some padding for non-independent segments and smoother playback.
33866          const maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start;
33867          const fragDuration = nextBufferedFrag.duration;
33868          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)));
33869          this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY);
33870        }
33871      }
33872    }
33873  }
33874  abortCurrentFrag() {
33875    const fragCurrent = this.fragCurrent;
33876    this.fragCurrent = null;
33877    this.backtrackFragment = null;
33878    if (fragCurrent) {
33879      fragCurrent.abortRequests();
33880      this.fragmentTracker.removeFragment(fragCurrent);
33881    }
33882    switch (this.state) {
33883      case State.KEY_LOADING:
33884      case State.FRAG_LOADING:
33885      case State.FRAG_LOADING_WAITING_RETRY:
33886      case State.PARSING:
33887      case State.PARSED:
33888        this.state = State.IDLE;
33889        break;
33890    }
33891    this.nextLoadPosition = this.getLoadPosition();
33892  }
33893  flushMainBuffer(startOffset, endOffset) {
33894    super.flushMainBuffer(startOffset, endOffset, this.altAudio === 2 ? 'video' : null);
33895  }
33896  onMediaAttached(event, data) {
33897    super.onMediaAttached(event, data);
33898    const media = data.media;
33899    addEventListener(media, 'playing', this.onMediaPlaying);
33900    addEventListener(media, 'seeked', this.onMediaSeeked);
33901  }
33902  onMediaDetaching(event, data) {
33903    const {
33904      media
33905    } = this;
33906    if (media) {
33907      removeEventListener(media, 'playing', this.onMediaPlaying);
33908      removeEventListener(media, 'seeked', this.onMediaSeeked);
33909    }
33910    this.videoBuffer = null;
33911    this.fragPlaying = null;
33912    super.onMediaDetaching(event, data);
33913    const transferringMedia = !!data.transferMedia;
33914    if (transferringMedia) {
33915      return;
33916    }
33917    this._hasEnoughToStart = false;
vendor: 96,412 bytes, lines 33918-36714
33918  }
33919  onManifestLoading() {
33920    super.onManifestLoading();
33921    // reset buffer on manifest loading
33922    this.log('Trigger BUFFER_RESET');
33923    this.hls.trigger(Events.BUFFER_RESET, undefined);
33924    this.couldBacktrack = false;
33925    this.fragLastKbps = 0;
33926    this.fragPlaying = this.backtrackFragment = null;
33927    this.altAudio = 0;
33928    this.audioOnly = false;
33929  }
33930  onManifestParsed(event, data) {
33931    // detect if we have different kind of audio codecs used amongst playlists
33932    let aac = false;
33933    let heaac = false;
33934    for (let i = 0; i < data.levels.length; i++) {
33935      const codec = data.levels[i].audioCodec;
33936      if (codec) {
33937        aac = aac || codec.indexOf('mp4a.40.2') !== -1;
33938        heaac = heaac || codec.indexOf('mp4a.40.5') !== -1;
33939      }
33940    }
33941    this.audioCodecSwitch = aac && heaac && !changeTypeSupported();
33942    if (this.audioCodecSwitch) {
33943      this.log('Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC');
33944    }
33945    this.levels = data.levels;
33946    this.startFragRequested = false;
33947  }
33948  onLevelLoading(event, data) {
33949    const {
33950      levels
33951    } = this;
33952    if (!levels || this.state !== State.IDLE) {
33953      return;
33954    }
33955    const level = data.levelInfo;
33956    if (!level.details || level.details.live && (this.levelLastLoaded !== level || level.details.expired) || this.waitForCdnTuneIn(level.details)) {
33957      this.state = State.WAITING_LEVEL;
33958    }
33959  }
33960  onLevelLoaded(event, data) {
33961    var _curLevel$details;
33962    const {
33963      levels,
33964      startFragRequested
33965    } = this;
33966    const newLevelId = data.level;
33967    const newDetails = data.details;
33968    const duration = newDetails.totalduration;
33969    if (!levels) {
33970      this.warn(`Levels were reset while loading level ${newLevelId}`);
33971      return;
33972    }
33973    this.log(`Level ${newLevelId} loaded [${newDetails.startSN},${newDetails.endSN}]${newDetails.lastPartSn ? `[part-${newDetails.lastPartSn}-${newDetails.lastPartIndex}]` : ''}, cc [${newDetails.startCC}, ${newDetails.endCC}] duration:${duration}`);
33974    const curLevel = data.levelInfo;
33975    const fragCurrent = this.fragCurrent;
33976    if (fragCurrent && (this.state === State.FRAG_LOADING || this.state === State.FRAG_LOADING_WAITING_RETRY)) {
33977      if (fragCurrent.level !== data.level && fragCurrent.loader) {
33978        this.abortCurrentFrag();
33979      }
33980    }
33981    let sliding = 0;
33982    if (newDetails.live || (_curLevel$details = curLevel.details) != null && _curLevel$details.live) {
33983      var _this$levelLastLoaded;
33984      this.checkLiveUpdate(newDetails);
33985      if (newDetails.deltaUpdateFailed) {
33986        return;
33987      }
33988      sliding = this.alignPlaylists(newDetails, curLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details);
33989    }
33990    // override level info
33991    curLevel.details = newDetails;
33992    this.levelLastLoaded = curLevel;
33993    if (!startFragRequested) {
33994      this.setStartPosition(newDetails, sliding);
33995    }
33996    this.hls.trigger(Events.LEVEL_UPDATED, {
33997      details: newDetails,
33998      level: newLevelId
33999    });
34000
34001    // only switch back to IDLE state if we were waiting for level to start downloading a new fragment
34002    if (this.state === State.WAITING_LEVEL) {
34003      if (this.waitForCdnTuneIn(newDetails)) {
34004        // Wait for Low-Latency CDN Tune-in
34005        return;
34006      }
34007      this.state = State.IDLE;
34008    }
34009    if (startFragRequested && newDetails.live) {
34010      this.synchronizeToLiveEdge(newDetails);
34011    }
34012
34013    // trigger handler right now
34014    this.tick();
34015  }
34016  synchronizeToLiveEdge(levelDetails) {
34017    const {
34018      config,
34019      media
34020    } = this;
34021    if (!media) {
34022      return;
34023    }
34024    const liveSyncPosition = this.hls.liveSyncPosition;
34025    const currentTime = this.getLoadPosition();
34026    const start = levelDetails.fragmentStart;
34027    const end = levelDetails.edge;
34028    const withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end;
34029    // Continue if we can seek forward to sync position or if current time is outside of sliding window
34030    if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) {
34031      // Continue if buffer is starving or if current time is behind max latency
34032      const maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration;
34033      if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) {
34034        if (!this._hasEnoughToStart) {
34035          this.nextLoadPosition = liveSyncPosition;
34036        }
34037        // Only seek if ready and there is not a significant forward buffer available for playback
34038        if (media.readyState) {
34039          this.warn(`Playback: ${currentTime.toFixed(3)} is located too far from the end of live sliding playlist: ${end}, reset currentTime to : ${liveSyncPosition.toFixed(3)}`);
34040          if (this.config.liveSyncMode === 'buffered') {
34041            var _bufferInfo$buffered;
34042            const bufferInfo = BufferHelper.bufferInfo(media, liveSyncPosition, 0);
34043            if (!((_bufferInfo$buffered = bufferInfo.buffered) != null && _bufferInfo$buffered.length)) {
34044              media.currentTime = liveSyncPosition;
34045              return;
34046            }
34047            const isLiveSyncInBuffer = bufferInfo.start <= currentTime;
34048            if (isLiveSyncInBuffer) {
34049              media.currentTime = liveSyncPosition;
34050              return;
34051            }
34052            const {
34053              nextStart
34054            } = BufferHelper.bufferedInfo(bufferInfo.buffered, currentTime, 0);
34055            if (nextStart) {
34056              media.currentTime = nextStart;
34057            }
34058          } else {
34059            media.currentTime = liveSyncPosition;
34060          }
34061        }
34062      }
34063    }
34064  }
34065  _handleFragmentLoadProgress(data) {
34066    var _frag$initSegment;
34067    const frag = data.frag;
34068    const {
34069      part,
34070      payload
34071    } = data;
34072    const {
34073      levels
34074    } = this;
34075    if (!levels) {
34076      this.warn(`Levels were reset while fragment load was in progress. Fragment ${frag.sn} of level ${frag.level} will not be buffered`);
34077      return;
34078    }
34079    const currentLevel = levels[frag.level];
34080    if (!currentLevel) {
34081      this.warn(`Level ${frag.level} not found on progress`);
34082      return;
34083    }
34084    const details = currentLevel.details;
34085    if (!details) {
34086      this.warn(`Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`);
34087      this.fragmentTracker.removeFragment(frag);
34088      return;
34089    }
34090    const videoCodec = currentLevel.videoCodec;
34091
34092    // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live)
34093    const accurateTimeOffset = details.PTSKnown || !details.live;
34094    const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data;
34095    const audioCodec = this._getAudioCodec(currentLevel);
34096
34097    // transmux the MPEG-TS data to ISO-BMFF segments
34098    // this.log(`Transmuxing ${frag.sn} of [${details.startSN} ,${details.endSN}],level ${frag.level}, cc ${frag.cc}`);
34099    const transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this));
34100    const partIndex = part ? part.index : -1;
34101    const partial = partIndex !== -1;
34102    const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial);
34103    const initPTS = this.initPTS[frag.cc];
34104    transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS);
34105  }
34106  onAudioTrackSwitching(event, data) {
34107    const hls = this.hls;
34108    // if any URL found on new audio track, it is an alternate audio track
34109    const fromAltAudio = this.altAudio !== 0;
34110    const altAudio = useAlternateAudio(data.url, hls);
34111    // if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered
34112    // don't do anything if we switch to alt audio: audio stream controller is handling it.
34113    // we will just have to change buffer scheduling on audioTrackSwitched
34114    if (!altAudio) {
34115      if (this.mediaBuffer !== this.media) {
34116        this.log('Switching on main audio, use media.buffered to schedule main fragment loading');
34117        this.mediaBuffer = this.media;
34118        const fragCurrent = this.fragCurrent;
34119        // we need to refill audio buffer from main: cancel any frag loading to speed up audio switch
34120        if (fragCurrent) {
34121          this.log('Switching to main audio track, cancel main fragment load');
34122          fragCurrent.abortRequests();
34123          this.fragmentTracker.removeFragment(fragCurrent);
34124        }
34125        // destroy transmuxer to force init segment generation (following audio switch)
34126        this.resetTransmuxer();
34127        // switch to IDLE state to load new fragment
34128        this.resetLoadingState();
34129      } else if (this.audioOnly) {
34130        // Reset audio transmuxer so when switching back to main audio we're not still appending where we left off
34131        this.resetTransmuxer();
34132      }
34133      // If switching from alt to main audio, flush all audio and trigger track switched
34134      if (fromAltAudio) {
34135        this.altAudio = 0;
34136        this.fragmentTracker.removeAllFragments();
34137        hls.once(Events.BUFFER_FLUSHED, () => {
34138          if (!this.hls) {
34139            return;
34140          }
34141          this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
34142        });
34143        hls.trigger(Events.BUFFER_FLUSHING, {
34144          startOffset: 0,
34145          endOffset: Number.POSITIVE_INFINITY,
34146          type: null
34147        });
34148        return;
34149      }
34150      hls.trigger(Events.AUDIO_TRACK_SWITCHED, data);
34151    } else {
34152      this.altAudio = 1;
34153    }
34154  }
34155  onAudioTrackSwitched(event, data) {
34156    const altAudio = useAlternateAudio(data.url, this.hls);
34157    if (altAudio) {
34158      const videoBuffer = this.videoBuffer;
34159      // if we switched on alternate audio, ensure that main fragment scheduling is synced with video sourcebuffer buffered
34160      if (videoBuffer && this.mediaBuffer !== videoBuffer) {
34161        this.log('Switching on alternate audio, use video.buffered to schedule main fragment loading');
34162        this.mediaBuffer = videoBuffer;
34163      }
34164    }
34165    this.altAudio = altAudio ? 2 : 0;
34166    this.tick();
34167  }
34168  onBufferCreated(event, data) {
34169    const tracks = data.tracks;
34170    let mediaTrack;
34171    let name;
34172    let alternate = false;
34173    for (const type in tracks) {
34174      const track = tracks[type];
34175      if (track.id === 'main') {
34176        name = type;
34177        mediaTrack = track;
34178        // keep video source buffer reference
34179        if (type === 'video') {
34180          const videoTrack = tracks[type];
34181          if (videoTrack) {
34182            this.videoBuffer = videoTrack.buffer;
34183          }
34184        }
34185      } else {
34186        alternate = true;
34187      }
34188    }
34189    if (alternate && mediaTrack) {
34190      this.log(`Alternate track found, use ${name}.buffered to schedule main fragment loading`);
34191      this.mediaBuffer = mediaTrack.buffer;
34192    } else {
34193      this.mediaBuffer = this.media;
34194    }
34195  }
34196  onFragBuffered(event, data) {
34197    const {
34198      frag,
34199      part
34200    } = data;
34201    const bufferedMainFragment = frag.type === PlaylistLevelType.MAIN;
34202    if (bufferedMainFragment) {
34203      if (this.fragContextChanged(frag)) {
34204        // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion
34205        // Avoid setting state back to IDLE, since that will interfere with a level switch
34206        this.warn(`Fragment ${frag.sn}${part ? ' p: ' + part.index : ''} of level ${frag.level} finished buffering, but was aborted. state: ${this.state}`);
34207        if (this.state === State.PARSED) {
34208          this.state = State.IDLE;
34209        }
34210        return;
34211      }
34212      const stats = part ? part.stats : frag.stats;
34213      this.fragLastKbps = Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first));
34214      if (isMediaFragment(frag)) {
34215        this.fragPrevious = frag;
34216      }
34217      this.fragBufferedComplete(frag, part);
34218    }
34219    const media = this.media;
34220    if (!media) {
34221      return;
34222    }
34223    if (!this._hasEnoughToStart && BufferHelper.getBuffered(media).length) {
34224      this._hasEnoughToStart = true;
34225      this.seekToStartPos();
34226    }
34227    if (bufferedMainFragment) {
34228      this.tick();
34229    }
34230  }
34231  get hasEnoughToStart() {
34232    return this._hasEnoughToStart;
34233  }
34234  onError(event, data) {
34235    var _data$context;
34236    if (data.fatal) {
34237      this.state = State.ERROR;
34238      return;
34239    }
34240    switch (data.details) {
34241      case ErrorDetails.FRAG_GAP:
34242      case ErrorDetails.FRAG_PARSING_ERROR:
34243      case ErrorDetails.FRAG_DECRYPT_ERROR:
34244      case ErrorDetails.FRAG_LOAD_ERROR:
34245      case ErrorDetails.FRAG_LOAD_TIMEOUT:
34246      case ErrorDetails.KEY_LOAD_ERROR:
34247      case ErrorDetails.KEY_LOAD_TIMEOUT:
34248        this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data);
34249        break;
34250      case ErrorDetails.LEVEL_LOAD_ERROR:
34251      case ErrorDetails.LEVEL_LOAD_TIMEOUT:
34252      case ErrorDetails.LEVEL_PARSING_ERROR:
34253        // in case of non fatal error while loading level, if level controller is not retrying to load level, switch back to IDLE
34254        if (!data.levelRetry && this.state === State.WAITING_LEVEL && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.LEVEL) {
34255          this.state = State.IDLE;
34256        }
34257        break;
34258      case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
34259      case ErrorDetails.BUFFER_APPEND_ERROR:
34260        if (data.parent !== 'main') {
34261          return;
34262        }
34263        if (this.reduceLengthAndFlushBuffer(data)) {
34264          this.resetLoadingState();
34265        }
34266        break;
34267      case ErrorDetails.BUFFER_FULL_ERROR:
34268        if (data.parent !== 'main') {
34269          return;
34270        }
34271        if (this.reduceLengthAndFlushBuffer(data)) {
34272          const isAssetPlayer = !this.config.interstitialsController && this.config.assetPlayerId;
34273          if (isAssetPlayer) {
34274            // Use currentTime in buffer estimate to prevent loading more until playback advances
34275            this._hasEnoughToStart = true;
34276          } else {
34277            this.flushMainBuffer(0, Number.POSITIVE_INFINITY);
34278          }
34279        }
34280        break;
34281      case ErrorDetails.INTERNAL_EXCEPTION:
34282        this.recoverWorkerError(data);
34283        break;
34284    }
34285  }
34286  onFragLoadEmergencyAborted() {
34287    this.state = State.IDLE;
34288    // if loadedmetadata is not set, it means that we are emergency switch down on first frag
34289    // in that case, reset startFragRequested flag
34290    if (!this._hasEnoughToStart) {
34291      this.startFragRequested = false;
34292      this.nextLoadPosition = this.lastCurrentTime;
34293    }
34294    this.tickImmediate();
34295  }
34296  onBufferFlushed(event, {
34297    type
34298  }) {
34299    if (type !== ElementaryStreamTypes.AUDIO || !this.altAudio) {
34300      const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media;
34301      if (mediaBuffer) {
34302        this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN);
34303        this.tick();
34304      }
34305    }
34306  }
34307  onLevelsUpdated(event, data) {
34308    if (this.level > -1 && this.fragCurrent) {
34309      this.level = this.fragCurrent.level;
34310      if (this.level === -1) {
34311        this.resetWhenMissingContext(this.fragCurrent);
34312      }
34313    }
34314    this.levels = data.levels;
34315  }
34316  swapAudioCodec() {
34317    this.audioCodecSwap = !this.audioCodecSwap;
34318  }
34319
34320  /**
34321   * Seeks to the set startPosition if not equal to the mediaElement's current time.
34322   */
34323  seekToStartPos() {
34324    const {
34325      media
34326    } = this;
34327    if (!media) {
34328      return;
34329    }
34330    const currentTime = media.currentTime;
34331    let startPosition = this.startPosition;
34332    // only adjust currentTime if different from startPosition or if startPosition not buffered
34333    // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered
34334    if (startPosition >= 0 && currentTime < startPosition) {
34335      if (media.seeking) {
34336        this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`);
34337        return;
34338      }
34339
34340      // Offset start position by timeline offset
34341      const timelineOffset = this.timelineOffset;
34342      if (timelineOffset && startPosition) {
34343        startPosition += timelineOffset;
34344      }
34345      const details = this.getLevelDetails();
34346      const buffered = BufferHelper.getBuffered(media);
34347      const bufferStart = buffered.length ? buffered.start(0) : 0;
34348      const delta = bufferStart - startPosition;
34349      const skipTolerance = Math.max(this.config.maxBufferHole, this.config.maxFragLookUpTolerance);
34350      if (this.config.startOnSegmentBoundary || delta > 0 && (delta < skipTolerance || this.loadingParts && delta < 2 * ((details == null ? void 0 : details.partTarget) || 0))) {
34351        this.log(`adjusting start position by ${delta} to match buffer start`);
34352        startPosition += delta;
34353        this.startPosition = startPosition;
34354      }
34355      if (currentTime < startPosition) {
34356        this.log(`seek to target start position ${startPosition} from current time ${currentTime} buffer start ${bufferStart}`);
34357        media.currentTime = startPosition;
34358      }
34359    }
34360  }
34361  _getAudioCodec(currentLevel) {
34362    let audioCodec = this.config.defaultAudioCodec || currentLevel.audioCodec;
34363    if (this.audioCodecSwap && audioCodec) {
34364      this.log('Swapping audio codec');
34365      if (audioCodec.indexOf('mp4a.40.5') !== -1) {
34366        audioCodec = 'mp4a.40.2';
34367      } else {
34368        audioCodec = 'mp4a.40.5';
34369      }
34370    }
34371    return audioCodec;
34372  }
34373  _loadBitrateTestFrag(fragment, level) {
34374    fragment.bitrateTest = true;
34375    this._doFragLoad(fragment, level).then(data => {
34376      const {
34377        hls
34378      } = this;
34379      const frag = data == null ? void 0 : data.frag;
34380      if (!frag || this.fragContextChanged(frag)) {
34381        return;
34382      }
34383      level.fragmentError = 0;
34384      this.state = State.IDLE;
34385      this.startFragRequested = false;
34386      this.bitrateTest = false;
34387      const stats = frag.stats;
34388      // Bitrate tests fragments are neither parsed nor buffered
34389      stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now();
34390      hls.trigger(Events.FRAG_LOADED, data);
34391      frag.bitrateTest = false;
34392    }).catch(reason => {
34393      if (this.state === State.STOPPED || this.state === State.ERROR) {
34394        return;
34395      }
34396      this.warn(reason);
34397      this.resetFragmentLoading(fragment);
34398    });
34399  }
34400  _handleTransmuxComplete(transmuxResult) {
34401    const id = this.playlistType;
34402    const {
34403      hls
34404    } = this;
34405    const {
34406      remuxResult,
34407      chunkMeta
34408    } = transmuxResult;
34409    const context = this.getCurrentContext(chunkMeta);
34410    if (!context) {
34411      this.resetWhenMissingContext(chunkMeta);
34412      return;
34413    }
34414    const {
34415      frag,
34416      part,
34417      level
34418    } = context;
34419    const {
34420      video,
34421      text,
34422      id3,
34423      initSegment
34424    } = remuxResult;
34425    const {
34426      details
34427    } = level;
34428    // The audio-stream-controller handles audio buffering if Hls.js is playing an alternate audio track
34429    const audio = this.altAudio ? undefined : remuxResult.audio;
34430
34431    // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level.
34432    // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed.
34433    if (this.fragContextChanged(frag)) {
34434      this.fragmentTracker.removeFragment(frag);
34435      return;
34436    }
34437    this.state = State.PARSING;
34438    if (initSegment) {
34439      const tracks = initSegment.tracks;
34440      if (tracks) {
34441        const mapFragment = frag.initSegment || frag;
34442        if (this.unhandledEncryptionError(initSegment, frag)) {
34443          return;
34444        }
34445        this._bufferInitSegment(level, tracks, mapFragment, chunkMeta);
34446        hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, {
34447          frag: mapFragment,
34448          id,
34449          tracks
34450        });
34451      }
34452      const baseTime = initSegment.initPTS;
34453      const timescale = initSegment.timescale;
34454      const initPTS = this.initPTS[frag.cc];
34455      if (isFiniteNumber(baseTime) && (!initPTS || initPTS.baseTime !== baseTime || initPTS.timescale !== timescale)) {
34456        const trackId = initSegment.trackId;
34457        this.initPTS[frag.cc] = {
34458          baseTime,
34459          timescale,
34460          trackId
34461        };
34462        hls.trigger(Events.INIT_PTS_FOUND, {
34463          frag,
34464          id,
34465          initPTS: baseTime,
34466          timescale,
34467          trackId
34468        });
34469      }
34470    }
34471
34472    // Avoid buffering if backtracking this fragment
34473    if (video && details) {
34474      if (audio && video.type === 'audiovideo') {
34475        this.logMuxedErr(frag);
34476      }
34477      const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
34478      const isFirstFragment = frag.sn === details.startSN;
34479      const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
34480      if (remuxResult.independent !== false) {
34481        const {
34482          startPTS,
34483          endPTS,
34484          startDTS,
34485          endDTS
34486        } = video;
34487        if (part) {
34488          part.elementaryStreams[video.type] = {
34489            startPTS,
34490            endPTS,
34491            startDTS,
34492            endDTS
34493          };
34494        } else {
34495          if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) {
34496            this.couldBacktrack = true;
34497          }
34498          if (video.dropped && video.independent) {
34499            // Backtrack if dropped frames create a gap after currentTime
34500
34501            const bufferInfo = this.getMainFwdBufferInfo();
34502            const targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole;
34503            const startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS;
34504            if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) {
34505              this.backtrack(frag);
34506              return;
34507            } else if (isFirstInDiscontinuity) {
34508              // Mark segment with a gap to avoid loop loading
34509              frag.gap = true;
34510            }
34511            // Set video stream start to fragment start so that truncated samples do not distort the timeline, and mark it partial
34512            frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true);
34513          } else if (isFirstFragment && startPTS - (details.appliedTimelineOffset || 0) > MAX_START_GAP_JUMP) {
34514            // Mark segment with a gap to skip large start gap
34515            frag.gap = true;
34516          }
34517        }
34518        frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS);
34519        if (this.backtrackFragment) {
34520          this.backtrackFragment = frag;
34521        }
34522        this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment || isFirstInDiscontinuity);
34523      } else if (isFirstFragment || isFirstInDiscontinuity) {
34524        // Mark segment with a gap to avoid loop loading
34525        frag.gap = true;
34526      } else {
34527        this.backtrack(frag);
34528        return;
34529      }
34530    }
34531    if (audio) {
34532      const {
34533        startPTS,
34534        endPTS,
34535        startDTS,
34536        endDTS
34537      } = audio;
34538      if (part) {
34539        part.elementaryStreams[ElementaryStreamTypes.AUDIO] = {
34540          startPTS,
34541          endPTS,
34542          startDTS,
34543          endDTS
34544        };
34545      }
34546      frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS);
34547      this.bufferFragmentData(audio, frag, part, chunkMeta);
34548    }
34549    if (details && id3 != null && id3.samples.length) {
34550      const emittedID3 = {
34551        id,
34552        frag,
34553        details,
34554        samples: id3.samples
34555      };
34556      hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3);
34557    }
34558    if (details && text) {
34559      const emittedText = {
34560        id,
34561        frag,
34562        details,
34563        samples: text.samples
34564      };
34565      hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText);
34566    }
34567  }
34568  logMuxedErr(frag) {
34569    this.warn(`${isMediaFragment(frag) ? 'Media' : 'Init'} segment with muxed audiovideo where only video expected: ${frag.url}`);
34570  }
34571  _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) {
34572    if (this.state !== State.PARSING) {
34573      return;
34574    }
34575    this.audioOnly = !!tracks.audio && !tracks.video;
34576
34577    // if audio track is expected to come from audio stream controller, discard any coming from main
34578    if (this.altAudio && !this.audioOnly) {
34579      delete tracks.audio;
34580      if (tracks.audiovideo) {
34581        this.logMuxedErr(frag);
34582      }
34583    }
34584    // include levelCodec in audio and video tracks
34585    const {
34586      audio,
34587      video,
34588      audiovideo
34589    } = tracks;
34590    if (audio) {
34591      const levelCodec = currentLevel.audioCodec;
34592      let audioCodec = pickMostCompleteCodecName(audio.codec, levelCodec);
34593      // Add level and profile to make up for remuxer not being able to parse full codec
34594      // (logger warning "Unhandled audio codec...")
34595      if (audioCodec === 'mp4a') {
34596        audioCodec = 'mp4a.40.5';
34597      }
34598      // Handle `audioCodecSwitch`
34599      const ua = navigator.userAgent.toLowerCase();
34600      if (this.audioCodecSwitch) {
34601        if (audioCodec) {
34602          if (audioCodec.indexOf('mp4a.40.5') !== -1) {
34603            audioCodec = 'mp4a.40.2';
34604          } else {
34605            audioCodec = 'mp4a.40.5';
34606          }
34607        }
34608        // In the case that AAC and HE-AAC audio codecs are signalled in manifest,
34609        // force HE-AAC, as it seems that most browsers prefers it.
34610        // don't force HE-AAC if mono stream, or in Firefox
34611        const audioMetadata = audio.metadata;
34612        if (audioMetadata && 'channelCount' in audioMetadata && (audioMetadata.channelCount || 1) !== 1 && ua.indexOf('firefox') === -1) {
34613          audioCodec = 'mp4a.40.5';
34614        }
34615      }
34616      // HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise
34617      if (audioCodec && audioCodec.indexOf('mp4a.40.5') !== -1 && ua.indexOf('android') !== -1 && audio.container !== 'audio/mpeg') {
34618        // Exclude mpeg audio
34619        audioCodec = 'mp4a.40.2';
34620        this.log(`Android: force audio codec to ${audioCodec}`);
34621      }
34622      if (levelCodec && levelCodec !== audioCodec) {
34623        this.log(`Swapping manifest audio codec "${levelCodec}" for "${audioCodec}"`);
34624      }
34625      audio.levelCodec = audioCodec;
34626      audio.id = PlaylistLevelType.MAIN;
34627      this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ''}/${levelCodec || ''}/${audio.codec}]`);
34628      delete tracks.audiovideo;
34629    }
34630    if (video) {
34631      video.levelCodec = currentLevel.videoCodec;
34632      video.id = PlaylistLevelType.MAIN;
34633      const parsedVideoCodec = video.codec;
34634      if ((parsedVideoCodec == null ? void 0 : parsedVideoCodec.length) === 4) {
34635        // Make up for passthrough-remuxer not being able to parse full codec
34636        // (logger warning "Unhandled video codec...")
34637        switch (parsedVideoCodec) {
34638          case 'hvc1':
34639          case 'hev1':
34640            video.codec = 'hvc1.1.6.L120.90';
34641            break;
34642          case 'av01':
34643            video.codec = 'av01.0.04M.08';
34644            break;
34645          case 'avc1':
34646            video.codec = 'avc1.42e01e';
34647            break;
34648        }
34649      }
34650      this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ''}/${parsedVideoCodec}]${video.codec !== parsedVideoCodec ? ' parsed-corrected=' + video.codec : ''}${video.supplemental ? ' supplemental=' + video.supplemental : ''}`);
34651      delete tracks.audiovideo;
34652    }
34653    if (audiovideo) {
34654      this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`);
34655      delete tracks.video;
34656      delete tracks.audio;
34657    }
34658    const trackTypes = Object.keys(tracks);
34659    if (trackTypes.length) {
34660      this.hls.trigger(Events.BUFFER_CODECS, tracks);
34661      if (!this.hls) {
34662        // Exit after fatal tracks error
34663        return;
34664      }
34665      // loop through tracks that are going to be provided to bufferController
34666      trackTypes.forEach(trackName => {
34667        const track = tracks[trackName];
34668        const initSegment = track.initSegment;
34669        if (initSegment != null && initSegment.byteLength) {
34670          this.hls.trigger(Events.BUFFER_APPENDING, {
34671            type: trackName,
34672            data: initSegment,
34673            frag,
34674            part: null,
34675            chunkMeta,
34676            parent: frag.type
34677          });
34678        }
34679      });
34680    }
34681    // trigger handler right now
34682    this.tickImmediate();
34683  }
34684  getMainFwdBufferInfo() {
34685    // Observe video SourceBuffer (this.mediaBuffer) only when alt-audio is used, otherwise observe combined media buffer
34686    const bufferOutput = this.mediaBuffer && this.altAudio === 2 ? this.mediaBuffer : this.media;
34687    return this.getFwdBufferInfo(bufferOutput, PlaylistLevelType.MAIN);
34688  }
34689  get maxBufferLength() {
34690    const {
34691      levels,
34692      level
34693    } = this;
34694    const levelInfo = levels == null ? void 0 : levels[level];
34695    if (!levelInfo) {
34696      return this.config.maxBufferLength;
34697    }
34698    return this.getMaxBufferLength(levelInfo.maxBitrate);
34699  }
34700  backtrack(frag) {
34701    this.couldBacktrack = true;
34702    // Causes findFragments to backtrack through fragments to find the keyframe
34703    this.backtrackFragment = frag;
34704    this.resetTransmuxer();
34705    this.flushBufferGap(frag);
34706    this.fragmentTracker.removeFragment(frag);
34707    this.fragPrevious = null;
34708    this.nextLoadPosition = frag.start;
34709    this.state = State.IDLE;
34710  }
34711  checkFragmentChanged() {
34712    const video = this.media;
34713    let fragPlayingCurrent = null;
34714    if (video && video.readyState > 1 && video.seeking === false) {
34715      const currentTime = video.currentTime;
34716      /* if video element is in seeked state, currentTime can only increase.
34717        (assuming that playback rate is positive ...)
34718        As sometimes currentTime jumps back to zero after a
34719        media decode error, check this, to avoid seeking back to
34720        wrong position after a media decode error
34721      */
34722
34723      if (BufferHelper.isBuffered(video, currentTime)) {
34724        fragPlayingCurrent = this.getAppendedFrag(currentTime);
34725      } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) {
34726        /* ensure that FRAG_CHANGED event is triggered at startup,
34727          when first video frame is displayed and playback is paused.
34728          add a tolerance of 100ms, in case current position is not buffered,
34729          check if current pos+100ms is buffered and use that buffer range
34730          for FRAG_CHANGED event reporting */
34731        fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1);
34732      }
34733      if (fragPlayingCurrent) {
34734        this.backtrackFragment = null;
34735        const fragPlaying = this.fragPlaying;
34736        const fragCurrentLevel = fragPlayingCurrent.level;
34737        if (!fragPlaying || fragPlayingCurrent.sn !== fragPlaying.sn || fragPlaying.level !== fragCurrentLevel) {
34738          this.fragPlaying = fragPlayingCurrent;
34739          this.hls.trigger(Events.FRAG_CHANGED, {
34740            frag: fragPlayingCurrent
34741          });
34742          if (!fragPlaying || fragPlaying.level !== fragCurrentLevel) {
34743            this.hls.trigger(Events.LEVEL_SWITCHED, {
34744              level: fragCurrentLevel
34745            });
34746          }
34747        }
34748      }
34749    }
34750  }
34751  get nextLevel() {
34752    const frag = this.nextBufferedFrag;
34753    if (frag) {
34754      return frag.level;
34755    }
34756    return -1;
34757  }
34758  get currentFrag() {
34759    var _this$media2;
34760    if (this.fragPlaying) {
34761      return this.fragPlaying;
34762    }
34763    const currentTime = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || this.lastCurrentTime;
34764    if (isFiniteNumber(currentTime)) {
34765      return this.getAppendedFrag(currentTime);
34766    }
34767    return null;
34768  }
34769  get currentProgramDateTime() {
34770    var _this$media3;
34771    const currentTime = ((_this$media3 = this.media) == null ? void 0 : _this$media3.currentTime) || this.lastCurrentTime;
34772    if (isFiniteNumber(currentTime)) {
34773      const details = this.getLevelDetails();
34774      const frag = this.currentFrag || (details ? findFragmentByPTS(null, details.fragments, currentTime) : null);
34775      if (frag) {
34776        const programDateTime = frag.programDateTime;
34777        if (programDateTime !== null) {
34778          const epocMs = programDateTime + (currentTime - frag.start) * 1000;
34779          return new Date(epocMs);
34780        }
34781      }
34782    }
34783    return null;
34784  }
34785  get currentLevel() {
34786    const frag = this.currentFrag;
34787    if (frag) {
34788      return frag.level;
34789    }
34790    return -1;
34791  }
34792  get nextBufferedFrag() {
34793    const frag = this.currentFrag;
34794    if (frag) {
34795      return this.followingBufferedFrag(frag);
34796    }
34797    return null;
34798  }
34799  get forceStartLoad() {
34800    return this._forceStartLoad;
34801  }
34802}
34803
34804class KeyLoader extends Logger {
34805  constructor(config, logger) {
34806    super('key-loader', logger);
34807    this.config = void 0;
34808    this.keyIdToKeyInfo = {};
34809    this.emeController = null;
34810    this.config = config;
34811  }
34812  abort(type) {
34813    for (const id in this.keyIdToKeyInfo) {
34814      const loader = this.keyIdToKeyInfo[id].loader;
34815      if (loader) {
34816        var _loader$context;
34817        if (type && type !== ((_loader$context = loader.context) == null ? void 0 : _loader$context.frag.type)) {
34818          return;
34819        }
34820        loader.abort();
34821      }
34822    }
34823  }
34824  detach() {
34825    for (const id in this.keyIdToKeyInfo) {
34826      const keyInfo = this.keyIdToKeyInfo[id];
34827      // Remove cached EME keys on detach
34828      if (keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption) {
34829        delete this.keyIdToKeyInfo[id];
34830      }
34831    }
34832  }
34833  destroy() {
34834    this.detach();
34835    for (const id in this.keyIdToKeyInfo) {
34836      const loader = this.keyIdToKeyInfo[id].loader;
34837      if (loader) {
34838        loader.destroy();
34839      }
34840    }
34841    this.keyIdToKeyInfo = {};
34842  }
34843  createKeyLoadError(frag, details = ErrorDetails.KEY_LOAD_ERROR, error, networkDetails, response) {
34844    return new LoadError({
34845      type: ErrorTypes.NETWORK_ERROR,
34846      details,
34847      fatal: false,
34848      frag,
34849      response,
34850      error,
34851      networkDetails
34852    });
34853  }
34854  loadClear(loadingFrag, encryptedFragments, startFragRequested) {
34855    if (this.emeController && this.config.emeEnabled && !this.emeController.getSelectedKeySystemFormats().length) {
34856      // Access key-system with nearest key on start (loading frag is unencrypted)
34857      if (encryptedFragments.length) {
34858        for (let i = 0, l = encryptedFragments.length; i < l; i++) {
34859          const frag = encryptedFragments[i];
34860          // Loading at or before segment with EXT-X-KEY, or first frag loading and last EXT-X-KEY
34861          if (loadingFrag.cc <= frag.cc && (!isMediaFragment(loadingFrag) || !isMediaFragment(frag) || loadingFrag.sn < frag.sn) || !startFragRequested && i == l - 1) {
34862            return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
34863              if (!this.emeController) {
34864                return;
34865              }
34866              frag.setKeyFormat(keySystemFormat);
34867              const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
34868              if (keySystem) {
34869                return this.emeController.getKeySystemAccess([keySystem]);
34870              }
34871            });
34872          }
34873        }
34874      }
34875      if (this.config.requireKeySystemAccessOnStart) {
34876        const keySystemsInConfig = getKeySystemsForConfig(this.config);
34877        if (keySystemsInConfig.length) {
34878          return this.emeController.getKeySystemAccess(keySystemsInConfig);
34879        }
34880      }
34881    }
34882    return null;
34883  }
34884  load(frag) {
34885    if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) {
34886      // Multiple keys, but none selected, resolve in eme-controller
34887      return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
34888        return this.loadInternal(frag, keySystemFormat);
34889      });
34890    }
34891    return this.loadInternal(frag);
34892  }
34893  loadInternal(frag, keySystemFormat) {
34894    var _keyInfo, _keyInfo2;
34895    if (keySystemFormat) {
34896      frag.setKeyFormat(keySystemFormat);
34897    }
34898    const decryptdata = frag.decryptdata;
34899    if (!decryptdata) {
34900      const error = new Error(keySystemFormat ? `Expected frag.decryptdata to be defined after setting format ${keySystemFormat}` : `Missing decryption data on fragment in onKeyLoading (emeEnabled with controller: ${this.emeController && this.config.emeEnabled})`);
34901      return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error));
34902    }
34903    const uri = decryptdata.uri;
34904    if (!uri) {
34905      return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Invalid key URI: "${uri}"`)));
34906    }
34907    const id = getKeyId(decryptdata);
34908    let keyInfo = this.keyIdToKeyInfo[id];
34909    if ((_keyInfo = keyInfo) != null && _keyInfo.decryptdata.key) {
34910      decryptdata.key = keyInfo.decryptdata.key;
34911      return Promise.resolve({
34912        frag,
34913        keyInfo
34914      });
34915    }
34916    // Return key load promise once it has a mediakey session with an usable key status
34917    if (this.emeController && (_keyInfo2 = keyInfo) != null && _keyInfo2.keyLoadPromise) {
34918      const keyStatus = this.emeController.getKeyStatus(keyInfo.decryptdata);
34919      switch (keyStatus) {
34920        case 'usable':
34921        case 'usable-in-future':
34922          return keyInfo.keyLoadPromise.then(keyLoadedData => {
34923            // Return the correct fragment with updated decryptdata key and loaded keyInfo
34924            const {
34925              keyInfo
34926            } = keyLoadedData;
34927            decryptdata.key = keyInfo.decryptdata.key;
34928            return {
34929              frag,
34930              keyInfo
34931            };
34932          });
34933      }
34934      // If we have a key session and status and it is not pending or usable, continue
34935      // This will go back to the eme-controller for expired keys to get a new keyLoadPromise
34936    }
34937
34938    // Load the key or return the loading promise
34939    this.log(`${this.keyIdToKeyInfo[id] ? 'Rel' : 'L'}oading${decryptdata.keyId ? ' keyId: ' + arrayToHex(decryptdata.keyId) : ''} URI: ${decryptdata.uri} from ${frag.type} ${frag.level}`);
34940    keyInfo = this.keyIdToKeyInfo[id] = {
34941      decryptdata,
34942      keyLoadPromise: null,
34943      loader: null,
34944      mediaKeySessionContext: null
34945    };
34946    switch (decryptdata.method) {
34947      case 'SAMPLE-AES':
34948      case 'SAMPLE-AES-CENC':
34949      case 'SAMPLE-AES-CTR':
34950        if (decryptdata.keyFormat === 'identity') {
34951          // loadKeyHTTP handles http(s) and data URLs
34952          return this.loadKeyHTTP(keyInfo, frag);
34953        }
34954        return this.loadKeyEME(keyInfo, frag);
34955      case 'AES-128':
34956      case 'AES-256':
34957      case 'AES-256-CTR':
34958        return this.loadKeyHTTP(keyInfo, frag);
34959      default:
34960        return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
34961    }
34962  }
34963  loadKeyEME(keyInfo, frag) {
34964    const keyLoadedData = {
34965      frag,
34966      keyInfo
34967    };
34968    if (this.emeController && this.config.emeEnabled) {
34969      var _frag$initSegment;
34970      if (!keyInfo.decryptdata.keyId && (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data) {
34971        const keyIds = parseKeyIdsFromTenc(frag.initSegment.data);
34972        if (keyIds.length) {
34973          let keyId = keyIds[0];
34974          if (keyId.some(b => b !== 0)) {
34975            this.log(`Using keyId found in init segment ${arrayToHex(keyId)}`);
34976            LevelKey.setKeyIdForUri(keyInfo.decryptdata.uri, keyId);
34977          } else {
34978            keyId = LevelKey.addKeyIdForUri(keyInfo.decryptdata.uri);
34979            this.log(`Generating keyId to patch media ${arrayToHex(keyId)}`);
34980          }
34981          keyInfo.decryptdata.keyId = keyId;
34982        }
34983      }
34984      if (!keyInfo.decryptdata.keyId && !isMediaFragment(frag)) {
34985        // Resolve so that unencrypted init segment is loaded
34986        // key id is extracted from tenc box when processing key for next segment above
34987        return Promise.resolve(keyLoadedData);
34988      }
34989      const keySessionContextPromise = this.emeController.loadKey(keyLoadedData);
34990      return (keyInfo.keyLoadPromise = keySessionContextPromise.then(keySessionContext => {
34991        keyInfo.mediaKeySessionContext = keySessionContext;
34992        return keyLoadedData;
34993      })).catch(error => {
34994        // Remove promise for license renewal or retry
34995        keyInfo.keyLoadPromise = null;
34996        if ('data' in error) {
34997          error.data.frag = frag;
34998        }
34999        throw error;
35000      });
35001    }
35002    return Promise.resolve(keyLoadedData);
35003  }
35004  loadKeyHTTP(keyInfo, frag) {
35005    const config = this.config;
35006    const Loader = config.loader;
35007    const keyLoader = new Loader(config);
35008    frag.keyLoader = keyInfo.loader = keyLoader;
35009    return keyInfo.keyLoadPromise = new Promise((resolve, reject) => {
35010      const loaderContext = {
35011        keyInfo,
35012        frag,
35013        responseType: 'arraybuffer',
35014        url: keyInfo.decryptdata.uri
35015      };
35016
35017      // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times,
35018      // key-loader will trigger an error and rely on stream-controller to handle retry logic.
35019      // this will also align retry logic with fragment-loader
35020      const loadPolicy = config.keyLoadPolicy.default;
35021      const loaderConfig = {
35022        loadPolicy,
35023        timeout: loadPolicy.maxLoadTimeMs,
35024        maxRetry: 0,
35025        retryDelay: 0,
35026        maxRetryDelay: 0
35027      };
35028      const loaderCallbacks = {
35029        onSuccess: (response, stats, context, networkDetails) => {
35030          const {
35031            frag,
35032            keyInfo
35033          } = context;
35034          const id = getKeyId(keyInfo.decryptdata);
35035          if (!frag.decryptdata || keyInfo !== this.keyIdToKeyInfo[id]) {
35036            return reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error('after key load, decryptdata unset or changed'), networkDetails));
35037          }
35038          keyInfo.decryptdata.key = frag.decryptdata.key = new Uint8Array(response.data);
35039
35040          // detach fragment key loader on load success
35041          frag.keyLoader = null;
35042          keyInfo.loader = null;
35043          resolve({
35044            frag,
35045            keyInfo
35046          });
35047        },
35048        onError: (response, context, networkDetails, stats) => {
35049          this.resetLoader(context);
35050          reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`HTTP Error ${response.code} loading key ${response.text}`), networkDetails, _objectSpread2({
35051            url: loaderContext.url,
35052            data: undefined
35053          }, response)));
35054        },
35055        onTimeout: (stats, context, networkDetails) => {
35056          this.resetLoader(context);
35057          reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error('key loading timed out'), networkDetails));
35058        },
35059        onAbort: (stats, context, networkDetails) => {
35060          this.resetLoader(context);
35061          reject(this.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error('key loading aborted'), networkDetails));
35062        }
35063      };
35064      keyLoader.load(loaderContext, loaderConfig, loaderCallbacks);
35065    });
35066  }
35067  resetLoader(context) {
35068    const {
35069      frag,
35070      keyInfo,
35071      url: uri
35072    } = context;
35073    const loader = keyInfo.loader;
35074    if (frag.keyLoader === loader) {
35075      frag.keyLoader = null;
35076      keyInfo.loader = null;
35077    }
35078    const id = getKeyId(keyInfo.decryptdata) || uri;
35079    delete this.keyIdToKeyInfo[id];
35080    if (loader) {
35081      loader.destroy();
35082    }
35083  }
35084}
35085function getKeyId(decryptdata) {
35086  if (decryptdata.keyFormat !== KeySystemFormats.FAIRPLAY) {
35087    const keyId = decryptdata.keyId;
35088    if (keyId) {
35089      return arrayToHex(keyId);
35090    }
35091  }
35092  return decryptdata.uri;
35093}
35094
35095function mapContextToLevelType(context) {
35096  const {
35097    type
35098  } = context;
35099  switch (type) {
35100    case PlaylistContextType.AUDIO_TRACK:
35101      return PlaylistLevelType.AUDIO;
35102    case PlaylistContextType.SUBTITLE_TRACK:
35103      return PlaylistLevelType.SUBTITLE;
35104    default:
35105      return PlaylistLevelType.MAIN;
35106  }
35107}
35108function getResponseUrl(response, context) {
35109  let url = response.url;
35110  // responseURL not supported on some browsers (it is used to detect URL redirection)
35111  // data-uri mode also not supported (but no need to detect redirection)
35112  if (url === undefined || url.indexOf('data:') === 0) {
35113    // fallback to initial URL
35114    url = context.url;
35115  }
35116  return url;
35117}
35118class PlaylistLoader {
35119  constructor(hls) {
35120    this.hls = void 0;
35121    this.loaders = Object.create(null);
35122    this.variableList = null;
35123    this.onManifestLoaded = this.checkAutostartLoad;
35124    this.hls = hls;
35125    this.registerListeners();
35126  }
35127  startLoad(startPosition) {}
35128  stopLoad() {
35129    this.destroyInternalLoaders();
35130  }
35131  registerListeners() {
35132    const {
35133      hls
35134    } = this;
35135    hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
35136    hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
35137    hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
35138    hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
35139    hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
35140  }
35141  unregisterListeners() {
35142    const {
35143      hls
35144    } = this;
35145    hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
35146    hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
35147    hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
35148    hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
35149    hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
35150  }
35151
35152  /**
35153   * Returns defaults or configured loader-type overloads (pLoader and loader config params)
35154   */
35155  createInternalLoader(context) {
35156    const config = this.hls.config;
35157    const PLoader = config.pLoader;
35158    const Loader = config.loader;
35159    const InternalLoader = PLoader || Loader;
35160    const loader = new InternalLoader(config);
35161    this.loaders[context.type] = loader;
35162    return loader;
35163  }
35164  getInternalLoader(context) {
35165    return this.loaders[context.type];
35166  }
35167  resetInternalLoader(contextType) {
35168    if (this.loaders[contextType]) {
35169      delete this.loaders[contextType];
35170    }
35171  }
35172
35173  /**
35174   * Call `destroy` on all internal loader instances mapped (one per context type)
35175   */
35176  destroyInternalLoaders() {
35177    for (const contextType in this.loaders) {
35178      const loader = this.loaders[contextType];
35179      if (loader) {
35180        loader.destroy();
35181      }
35182      this.resetInternalLoader(contextType);
35183    }
35184  }
35185  destroy() {
35186    this.variableList = null;
35187    this.unregisterListeners();
35188    this.destroyInternalLoaders();
35189  }
35190  onManifestLoading(event, data) {
35191    const {
35192      url
35193    } = data;
35194    this.variableList = null;
35195    this.load({
35196      id: null,
35197      level: 0,
35198      responseType: 'text',
35199      type: PlaylistContextType.MANIFEST,
35200      url,
35201      deliveryDirectives: null,
35202      levelOrTrack: null
35203    });
35204  }
35205  onLevelLoading(event, data) {
35206    const {
35207      id,
35208      level,
35209      pathwayId,
35210      url,
35211      deliveryDirectives,
35212      levelInfo
35213    } = data;
35214    this.load({
35215      id,
35216      level,
35217      pathwayId,
35218      responseType: 'text',
35219      type: PlaylistContextType.LEVEL,
35220      url,
35221      deliveryDirectives,
35222      levelOrTrack: levelInfo
35223    });
35224  }
35225  onAudioTrackLoading(event, data) {
35226    const {
35227      id,
35228      groupId,
35229      url,
35230      deliveryDirectives,
35231      track
35232    } = data;
35233    this.load({
35234      id,
35235      groupId,
35236      level: null,
35237      responseType: 'text',
35238      type: PlaylistContextType.AUDIO_TRACK,
35239      url,
35240      deliveryDirectives,
35241      levelOrTrack: track
35242    });
35243  }
35244  onSubtitleTrackLoading(event, data) {
35245    const {
35246      id,
35247      groupId,
35248      url,
35249      deliveryDirectives,
35250      track
35251    } = data;
35252    this.load({
35253      id,
35254      groupId,
35255      level: null,
35256      responseType: 'text',
35257      type: PlaylistContextType.SUBTITLE_TRACK,
35258      url,
35259      deliveryDirectives,
35260      levelOrTrack: track
35261    });
35262  }
35263  onLevelsUpdated(event, data) {
35264    // abort and delete loader of removed levels
35265    const loader = this.loaders[PlaylistContextType.LEVEL];
35266    if (loader) {
35267      const context = loader.context;
35268      if (context && !data.levels.some(lvl => lvl === context.levelOrTrack)) {
35269        loader.abort();
35270        delete this.loaders[PlaylistContextType.LEVEL];
35271      }
35272    }
35273  }
35274  load(context) {
35275    var _context$deliveryDire;
35276    const config = this.hls.config;
35277
35278    // logger.debug(`[playlist-loader]: Loading playlist of type ${context.type}, level: ${context.level}, id: ${context.id}`);
35279
35280    // Check if a loader for this context already exists
35281    let loader = this.getInternalLoader(context);
35282    if (loader) {
35283      const logger = this.hls.logger;
35284      const loaderContext = loader.context;
35285      if (loaderContext && loaderContext.levelOrTrack === context.levelOrTrack && (loaderContext.url === context.url || loaderContext.deliveryDirectives && !context.deliveryDirectives)) {
35286        // same URL can't overlap, or wait for blocking request
35287        if (loaderContext.url === context.url) {
35288          logger.log(`[playlist-loader]: ignore ${context.url} ongoing request`);
35289        } else {
35290          logger.log(`[playlist-loader]: ignore ${context.url} in favor of ${loaderContext.url}`);
35291        }
35292        return;
35293      }
35294      logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
35295      loader.abort();
35296    }
35297
35298    // apply different configs for retries depending on
35299    // context (manifest, level, audio/subs playlist)
35300    let loadPolicy;
35301    if (context.type === PlaylistContextType.MANIFEST) {
35302      loadPolicy = config.manifestLoadPolicy.default;
35303    } else {
35304      loadPolicy = _extends({}, config.playlistLoadPolicy.default, {
35305        timeoutRetry: null,
35306        errorRetry: null
35307      });
35308    }
35309    loader = this.createInternalLoader(context);
35310
35311    // Override level/track timeout for LL-HLS requests
35312    // (the default of 10000ms is counter productive to blocking playlist reload requests)
35313    if (isFiniteNumber((_context$deliveryDire = context.deliveryDirectives) == null ? void 0 : _context$deliveryDire.part)) {
35314      let levelDetails;
35315      if (context.type === PlaylistContextType.LEVEL && context.level !== null) {
35316        levelDetails = this.hls.levels[context.level].details;
35317      } else if (context.type === PlaylistContextType.AUDIO_TRACK && context.id !== null) {
35318        levelDetails = this.hls.audioTracks[context.id].details;
35319      } else if (context.type === PlaylistContextType.SUBTITLE_TRACK && context.id !== null) {
35320        levelDetails = this.hls.subtitleTracks[context.id].details;
35321      }
35322      if (levelDetails) {
35323        const partTarget = levelDetails.partTarget;
35324        const targetDuration = levelDetails.targetduration;
35325        if (partTarget && targetDuration) {
35326          const maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1000;
35327          loadPolicy = _extends({}, loadPolicy, {
35328            maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs),
35329            maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs)
35330          });
35331        }
35332      }
35333    }
35334    const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {};
35335    const loaderConfig = {
35336      loadPolicy,
35337      timeout: loadPolicy.maxLoadTimeMs,
35338      maxRetry: legacyRetryCompatibility.maxNumRetry || 0,
35339      retryDelay: legacyRetryCompatibility.retryDelayMs || 0,
35340      maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0
35341    };
35342    const loaderCallbacks = {
35343      onSuccess: (response, stats, context, networkDetails) => {
35344        const loader = this.getInternalLoader(context);
35345        this.resetInternalLoader(context.type);
35346        const string = response.data;
35347        stats.parsing.start = performance.now();
35348        if (M3U8Parser.isMediaPlaylist(string) || context.type !== PlaylistContextType.MANIFEST) {
35349          this.handleTrackOrLevelPlaylist(response, stats, context, networkDetails || null, loader);
35350        } else {
35351          this.handleMasterPlaylist(response, stats, context, networkDetails);
35352        }
35353      },
35354      onError: (response, context, networkDetails, stats) => {
35355        this.handleNetworkError(context, networkDetails, false, response, stats);
35356      },
35357      onTimeout: (stats, context, networkDetails) => {
35358        this.handleNetworkError(context, networkDetails, true, undefined, stats);
35359      }
35360    };
35361
35362    // logger.debug(`[playlist-loader]: Calling internal loader delegate for URL: ${context.url}`);
35363
35364    loader.load(context, loaderConfig, loaderCallbacks);
35365  }
35366  checkAutostartLoad() {
35367    if (!this.hls) {
35368      return;
35369    }
35370    const {
35371      config: {
35372        autoStartLoad,
35373        startPosition
35374      },
35375      forceStartLoad
35376    } = this.hls;
35377    if (autoStartLoad || forceStartLoad) {
35378      this.hls.logger.log(`${autoStartLoad ? 'auto' : 'force'} startLoad with configured startPosition ${startPosition}`);
35379      this.hls.startLoad(startPosition);
35380    }
35381  }
35382  handleMasterPlaylist(response, stats, context, networkDetails) {
35383    const hls = this.hls;
35384    const string = response.data;
35385    const url = getResponseUrl(response, context);
35386    const parsedResult = M3U8Parser.parseMasterPlaylist(string, url);
35387    if (parsedResult.playlistParsingError) {
35388      stats.parsing.end = performance.now();
35389      this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats);
35390      return;
35391    }
35392    const {
35393      contentSteering,
35394      levels,
35395      sessionData,
35396      sessionKeys,
35397      startTimeOffset,
35398      variableList
35399    } = parsedResult;
35400    this.variableList = variableList;
35401
35402    // Treat unknown codec as audio or video codec based on passing `isTypeSupported` check
35403    // (allows for playback of any supported codec even if not indexed in utils/codecs)
35404    levels.forEach(levelParsed => {
35405      const {
35406        unknownCodecs
35407      } = levelParsed;
35408      if (unknownCodecs) {
35409        const {
35410          preferManagedMediaSource
35411        } = this.hls.config;
35412        let {
35413          audioCodec,
35414          videoCodec
35415        } = levelParsed;
35416        for (let i = unknownCodecs.length; i--;) {
35417          const unknownCodec = unknownCodecs[i];
35418          if (areCodecsMediaSourceSupported(unknownCodec, 'audio', preferManagedMediaSource)) {
35419            levelParsed.audioCodec = audioCodec = audioCodec ? `${audioCodec},${unknownCodec}` : unknownCodec;
35420            sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2;
35421            unknownCodecs.splice(i, 1);
35422          } else if (areCodecsMediaSourceSupported(unknownCodec, 'video', preferManagedMediaSource)) {
35423            levelParsed.videoCodec = videoCodec = videoCodec ? `${videoCodec},${unknownCodec}` : unknownCodec;
35424            sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2;
35425            unknownCodecs.splice(i, 1);
35426          }
35427        }
35428      }
35429    });
35430    const {
35431      AUDIO: audioTracks = [],
35432      SUBTITLES: subtitles,
35433      'CLOSED-CAPTIONS': captions
35434    } = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult);
35435    if (audioTracks.length) {
35436      // check if we have found an audio track embedded in main playlist (audio track without URI attribute)
35437      const embeddedAudioFound = audioTracks.some(audioTrack => !audioTrack.url);
35438
35439      // if no embedded audio track defined, but audio codec signaled in quality level,
35440      // we need to signal this main audio track this could happen with playlists with
35441      // alt audio rendition in which quality levels (main)
35442      // contains both audio+video. but with mixed audio track not signaled
35443      if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
35444        this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
35445        audioTracks.unshift({
35446          type: 'main',
35447          name: 'main',
35448          groupId: 'main',
35449          default: false,
35450          autoselect: false,
35451          forced: false,
35452          id: -1,
35453          attrs: new AttrList({}),
35454          bitrate: 0,
35455          url: ''
35456        });
35457      }
35458    }
35459    hls.trigger(Events.MANIFEST_LOADED, {
35460      levels,
35461      audioTracks,
35462      subtitles,
35463      captions,
35464      contentSteering,
35465      url,
35466      stats,
35467      networkDetails,
35468      sessionData,
35469      sessionKeys,
35470      startTimeOffset,
35471      variableList
35472    });
35473  }
35474  handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) {
35475    const hls = this.hls;
35476    const {
35477      id,
35478      level,
35479      type
35480    } = context;
35481    const url = getResponseUrl(response, context);
35482    const levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0;
35483    const levelType = mapContextToLevelType(context);
35484    const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, 0, this.variableList);
35485
35486    // We have done our first request (Manifest-type) and receive
35487    // not a master playlist but a chunk-list (track/level)
35488    // We fire the manifest-loaded event anyway with the parsed level-details
35489    // by creating a single-level structure for it.
35490    if (type === PlaylistContextType.MANIFEST) {
35491      const singleLevel = {
35492        attrs: new AttrList({}),
35493        bitrate: 0,
35494        details: levelDetails,
35495        name: '',
35496        url
35497      };
35498      levelDetails.requestScheduled = stats.loading.start + computeReloadInterval(levelDetails, 0);
35499      hls.trigger(Events.MANIFEST_LOADED, {
35500        levels: [singleLevel],
35501        audioTracks: [],
35502        url,
35503        stats,
35504        networkDetails,
35505        sessionData: null,
35506        sessionKeys: null,
35507        contentSteering: null,
35508        startTimeOffset: null,
35509        variableList: null
35510      });
35511    }
35512
35513    // save parsing time
35514    stats.parsing.end = performance.now();
35515
35516    // extend the context with the new levelDetails property
35517    context.levelDetails = levelDetails;
35518    this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader);
35519  }
35520  handleManifestParsingError(response, context, error, networkDetails, stats) {
35521    this.hls.trigger(Events.ERROR, {
35522      type: ErrorTypes.NETWORK_ERROR,
35523      details: ErrorDetails.MANIFEST_PARSING_ERROR,
35524      fatal: context.type === PlaylistContextType.MANIFEST,
35525      url: response.url,
35526      err: error,
35527      error,
35528      reason: error.message,
35529      response,
35530      context,
35531      networkDetails,
35532      stats
35533    });
35534  }
35535  handleNetworkError(context, networkDetails, timeout = false, response, stats) {
35536    let message = `A network ${timeout ? 'timeout' : 'error' + (response ? ' (status ' + response.code + ')' : '')} occurred while loading ${context.type}`;
35537    if (context.type === PlaylistContextType.LEVEL) {
35538      message += `: ${context.level} id: ${context.id}`;
35539    } else if (context.type === PlaylistContextType.AUDIO_TRACK || context.type === PlaylistContextType.SUBTITLE_TRACK) {
35540      message += ` id: ${context.id} group-id: "${context.groupId}"`;
35541    }
35542    const error = new Error(message);
35543    this.hls.logger.warn(`[playlist-loader]: ${message}`);
35544    let details = ErrorDetails.UNKNOWN;
35545    let fatal = false;
35546    const loader = this.getInternalLoader(context);
35547    switch (context.type) {
35548      case PlaylistContextType.MANIFEST:
35549        details = timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR;
35550        fatal = true;
35551        break;
35552      case PlaylistContextType.LEVEL:
35553        details = timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR;
35554        fatal = false;
35555        break;
35556      case PlaylistContextType.AUDIO_TRACK:
35557        details = timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR;
35558        fatal = false;
35559        break;
35560      case PlaylistContextType.SUBTITLE_TRACK:
35561        details = timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT : ErrorDetails.SUBTITLE_LOAD_ERROR;
35562        fatal = false;
35563        break;
35564    }
35565    if (loader) {
35566      this.resetInternalLoader(context.type);
35567    }
35568    const errorData = {
35569      type: ErrorTypes.NETWORK_ERROR,
35570      details,
35571      fatal,
35572      url: context.url,
35573      loader,
35574      context,
35575      error,
35576      networkDetails,
35577      stats
35578    };
35579    if (response) {
35580      const url = (networkDetails == null ? void 0 : networkDetails.url) || context.url;
35581      errorData.response = _objectSpread2({
35582        url,
35583        data: undefined
35584      }, response);
35585    }
35586    this.hls.trigger(Events.ERROR, errorData);
35587  }
35588  handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) {
35589    const hls = this.hls;
35590    const {
35591      type,
35592      level,
35593      levelOrTrack,
35594      id,
35595      groupId,
35596      deliveryDirectives
35597    } = context;
35598    const url = getResponseUrl(response, context);
35599    const parent = mapContextToLevelType(context);
35600    let levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? level : undefined;
35601    const error = levelDetails.playlistParsingError;
35602    if (error) {
35603      this.hls.logger.warn(`${error} ${levelDetails.url}`);
35604      if (!hls.config.ignorePlaylistParsingErrors) {
35605        hls.trigger(Events.ERROR, {
35606          type: ErrorTypes.NETWORK_ERROR,
35607          details: ErrorDetails.LEVEL_PARSING_ERROR,
35608          fatal: false,
35609          url,
35610          error,
35611          reason: error.message,
35612          response,
35613          context,
35614          level: levelIndex,
35615          parent,
35616          networkDetails,
35617          stats
35618        });
35619        return;
35620      }
35621      levelDetails.playlistParsingError = null;
35622    }
35623    if (!levelDetails.fragments.length) {
35624      const _error = levelDetails.playlistParsingError = new Error('No Segments found in Playlist');
35625      hls.trigger(Events.ERROR, {
35626        type: ErrorTypes.NETWORK_ERROR,
35627        details: ErrorDetails.LEVEL_EMPTY_ERROR,
35628        fatal: false,
35629        url,
35630        error: _error,
35631        reason: _error.message,
35632        response,
35633        context,
35634        level: levelIndex,
35635        parent,
35636        networkDetails,
35637        stats
35638      });
35639      return;
35640    }
35641    if (levelDetails.live && loader) {
35642      if (loader.getCacheAge) {
35643        levelDetails.ageHeader = loader.getCacheAge() || 0;
35644      }
35645      if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) {
35646        levelDetails.ageHeader = 0;
35647      }
35648    }
35649    switch (type) {
35650      case PlaylistContextType.MANIFEST:
35651      case PlaylistContextType.LEVEL:
35652        if (levelIndex) {
35653          if (!levelOrTrack) {
35654            // fall-through to hls.levels[0]
35655            levelIndex = 0;
35656          } else {
35657            if (levelOrTrack !== hls.levels[levelIndex]) {
35658              // correct levelIndex when lower levels were removed from hls.levels
35659              const updatedIndex = hls.levels.indexOf(levelOrTrack);
35660              if (updatedIndex > -1) {
35661                levelIndex = updatedIndex;
35662              }
35663            }
35664          }
35665        }
35666        hls.trigger(Events.LEVEL_LOADED, {
35667          details: levelDetails,
35668          levelInfo: levelOrTrack || hls.levels[0],
35669          level: levelIndex || 0,
35670          id: id || 0,
35671          stats,
35672          networkDetails,
35673          deliveryDirectives,
35674          withoutMultiVariant: type === PlaylistContextType.MANIFEST
35675        });
35676        break;
35677      case PlaylistContextType.AUDIO_TRACK:
35678        hls.trigger(Events.AUDIO_TRACK_LOADED, {
35679          details: levelDetails,
35680          track: levelOrTrack,
35681          id: id || 0,
35682          groupId: groupId || '',
35683          stats,
35684          networkDetails,
35685          deliveryDirectives
35686        });
35687        break;
35688      case PlaylistContextType.SUBTITLE_TRACK:
35689        hls.trigger(Events.SUBTITLE_TRACK_LOADED, {
35690          details: levelDetails,
35691          track: levelOrTrack,
35692          id: id || 0,
35693          groupId: groupId || '',
35694          stats,
35695          networkDetails,
35696          deliveryDirectives
35697        });
35698        break;
35699    }
35700  }
35701}
35702
35703/**
35704 * The `Hls` class is the core of the HLS.js library used to instantiate player instances.
35705 * @public
35706 */
35707class Hls {
35708  /**
35709   * Get the video-dev/hls.js package version.
35710   */
35711  static get version() {
35712    return version;
35713  }
35714
35715  /**
35716   * Check if the required MediaSource Extensions are available.
35717   */
35718  static isMSESupported() {
35719    return isMSESupported();
35720  }
35721
35722  /**
35723   * Check if MediaSource Extensions are available and isTypeSupported checks pass for any baseline codecs.
35724   */
35725  static isSupported() {
35726    return isSupported();
35727  }
35728
35729  /**
35730   * Get the MediaSource global used for MSE playback (ManagedMediaSource, MediaSource, or WebKitMediaSource).
35731   */
35732  static getMediaSource() {
35733    return getMediaSource();
35734  }
35735  static get Events() {
35736    return Events;
35737  }
35738  static get MetadataSchema() {
35739    return MetadataSchema;
35740  }
35741  static get ErrorTypes() {
35742    return ErrorTypes;
35743  }
35744  static get ErrorDetails() {
35745    return ErrorDetails;
35746  }
35747
35748  /**
35749   * Get the default configuration applied to new instances.
35750   */
35751  static get DefaultConfig() {
35752    if (!Hls.defaultConfig) {
35753      return hlsDefaultConfig;
35754    }
35755    return Hls.defaultConfig;
35756  }
35757
35758  /**
35759   * Replace the default configuration applied to new instances.
35760   */
35761  static set DefaultConfig(defaultConfig) {
35762    Hls.defaultConfig = defaultConfig;
35763  }
35764
35765  /**
35766   * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`.
35767   * @param userConfig - Configuration options applied over `Hls.DefaultConfig`
35768   */
35769  constructor(userConfig = {}) {
35770    /**
35771     * The runtime configuration used by the player. At instantiation this is combination of `hls.userConfig` merged over `Hls.DefaultConfig`.
35772     */
35773    this.config = void 0;
35774    /**
35775     * The configuration object provided on player instantiation.
35776     */
35777    this.userConfig = void 0;
35778    /**
35779     * The logger functions used by this player instance, configured on player instantiation.
35780     */
35781    this.logger = void 0;
35782    this.coreComponents = void 0;
35783    this.networkControllers = void 0;
35784    this._emitter = new EventEmitter();
35785    this._autoLevelCapping = -1;
35786    this._maxHdcpLevel = null;
35787    this.abrController = void 0;
35788    this.bufferController = void 0;
35789    this.capLevelController = void 0;
35790    this.latencyController = void 0;
35791    this.levelController = void 0;
35792    this.streamController = void 0;
35793    this.audioStreamController = void 0;
35794    this.subtititleStreamController = void 0;
35795    this.audioTrackController = void 0;
35796    this.subtitleTrackController = void 0;
35797    this.interstitialsController = void 0;
35798    this.gapController = void 0;
35799    this.emeController = void 0;
35800    this.cmcdController = void 0;
35801    this._media = null;
35802    this._url = null;
35803    this._sessionId = void 0;
35804    this.triggeringException = void 0;
35805    this.started = false;
35806    const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance', userConfig.assetPlayerId);
35807    const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
35808    this.userConfig = userConfig;
35809    if (config.progressive) {
35810      enableStreamingMode(config, logger);
35811    }
35812
35813    // core controllers and network loaders
35814    const {
35815      abrController: _AbrController,
35816      bufferController: _BufferController,
35817      capLevelController: _CapLevelController,
35818      errorController: _ErrorController,
35819      fpsController: _FpsController
35820    } = config;
35821    const errorController = new _ErrorController(this);
35822    const abrController = this.abrController = new _AbrController(this);
35823    // FragmentTracker must be defined before StreamController because the order of event handling is important
35824    const fragmentTracker = new FragmentTracker(this);
35825    const _InterstitialsController = config.interstitialsController;
35826    const interstitialsController = _InterstitialsController ? this.interstitialsController = new _InterstitialsController(this, Hls) : null;
35827    const bufferController = this.bufferController = new _BufferController(this, fragmentTracker);
35828    const capLevelController = this.capLevelController = new _CapLevelController(this);
35829    const fpsController = new _FpsController(this);
35830    const playListLoader = new PlaylistLoader(this);
35831    const _ContentSteeringController = config.contentSteeringController;
35832    // Instantiate ConentSteeringController before LevelController to receive Multivariant Playlist events first
35833    const contentSteering = _ContentSteeringController ? new _ContentSteeringController(this) : null;
35834    const levelController = this.levelController = new LevelController(this, contentSteering);
35835    const id3TrackController = new ID3TrackController(this);
35836    const keyLoader = new KeyLoader(this.config, this.logger);
35837    const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
35838    const gapController = this.gapController = new GapController(this, fragmentTracker);
35839
35840    // Cap level controller uses streamController to flush the buffer
35841    capLevelController.setStreamController(streamController);
35842    // fpsController uses streamController to switch when frames are being dropped
35843    fpsController.setStreamController(streamController);
35844    const networkControllers = [playListLoader, levelController, streamController];
35845    if (interstitialsController) {
35846      networkControllers.splice(1, 0, interstitialsController);
35847    }
35848    if (contentSteering) {
35849      networkControllers.splice(1, 0, contentSteering);
35850    }
35851    this.networkControllers = networkControllers;
35852    const coreComponents = [abrController, bufferController, gapController, capLevelController, fpsController, id3TrackController, fragmentTracker];
35853    this.audioTrackController = this.createController(config.audioTrackController, networkControllers);
35854    const AudioStreamControllerClass = config.audioStreamController;
35855    if (AudioStreamControllerClass) {
35856      networkControllers.push(this.audioStreamController = new AudioStreamControllerClass(this, fragmentTracker, keyLoader));
35857    }
35858    // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
35859    this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers);
35860    const SubtitleStreamControllerClass = config.subtitleStreamController;
35861    if (SubtitleStreamControllerClass) {
35862      networkControllers.push(this.subtititleStreamController = new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader));
35863    }
35864    this.createController(config.timelineController, coreComponents);
35865    keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents);
35866    this.cmcdController = this.createController(config.cmcdController, coreComponents);
35867    this.latencyController = this.createController(LatencyController, coreComponents);
35868    this.coreComponents = coreComponents;
35869
35870    // Error controller handles errors before and after all other controllers
35871    // This listener will be invoked after all other controllers error listeners
35872    networkControllers.push(errorController);
35873    const onErrorOut = errorController.onErrorOut;
35874    if (typeof onErrorOut === 'function') {
35875      this.on(Events.ERROR, onErrorOut, errorController);
35876    }
35877    // Autostart load handler
35878    this.on(Events.MANIFEST_LOADED, playListLoader.onManifestLoaded, playListLoader);
35879  }
35880  createController(ControllerClass, components) {
35881    if (ControllerClass) {
35882      const controllerInstance = new ControllerClass(this);
35883      if (components) {
35884        components.push(controllerInstance);
35885      }
35886      return controllerInstance;
35887    }
35888    return null;
35889  }
35890
35891  // Delegate the EventEmitter through the public API of Hls.js
35892  on(event, listener, context = this) {
35893    this._emitter.on(event, listener, context);
35894  }
35895  once(event, listener, context = this) {
35896    this._emitter.once(event, listener, context);
35897  }
35898  removeAllListeners(event) {
35899    this._emitter.removeAllListeners(event);
35900  }
35901  off(event, listener, context = this, once) {
35902    this._emitter.off(event, listener, context, once);
35903  }
35904  listeners(event) {
35905    return this._emitter.listeners(event);
35906  }
35907  emit(event, name, eventObject) {
35908    return this._emitter.emit(event, name, eventObject);
35909  }
35910  trigger(event, eventObject) {
35911    if (this.config.debug) {
35912      return this.emit(event, event, eventObject);
35913    } else {
35914      try {
35915        return this.emit(event, event, eventObject);
35916      } catch (error) {
35917        this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
35918        // Prevent recursion in error event handlers that throw #5497
35919        if (!this.triggeringException) {
35920          this.triggeringException = true;
35921          const fatal = event === Events.ERROR;
35922          this.trigger(Events.ERROR, {
35923            type: ErrorTypes.OTHER_ERROR,
35924            details: ErrorDetails.INTERNAL_EXCEPTION,
35925            fatal,
35926            event,
35927            error
35928          });
35929          this.triggeringException = false;
35930        }
35931      }
35932    }
35933    return false;
35934  }
35935  listenerCount(event) {
35936    return this._emitter.listenerCount(event);
35937  }
35938
35939  /**
35940   * Dispose of the instance
35941   */
35942  destroy() {
35943    this.logger.log('destroy');
35944    this.trigger(Events.DESTROYING, undefined);
35945    this.detachMedia();
35946    this.removeAllListeners();
35947    this._autoLevelCapping = -1;
35948    this._url = null;
35949    this.networkControllers.forEach(component => component.destroy());
35950    this.networkControllers.length = 0;
35951    this.coreComponents.forEach(component => component.destroy());
35952    this.coreComponents.length = 0;
35953    // Remove any references that could be held in config options or callbacks
35954    const config = this.config;
35955    config.xhrSetup = config.fetchSetup = undefined;
35956    // @ts-ignore
35957    this.userConfig = null;
35958  }
35959
35960  /**
35961   * Attaches Hls.js to a media element
35962   */
35963  attachMedia(data) {
35964    if (!data || 'media' in data && !data.media) {
35965      const error = new Error(`attachMedia failed: invalid argument (${data})`);
35966      this.trigger(Events.ERROR, {
35967        type: ErrorTypes.OTHER_ERROR,
35968        details: ErrorDetails.ATTACH_MEDIA_ERROR,
35969        fatal: true,
35970        error
35971      });
35972      return;
35973    }
35974    this.logger.log(`attachMedia`);
35975    if (this._media) {
35976      this.logger.warn(`media must be detached before attaching`);
35977      this.detachMedia();
35978    }
35979    const attachMediaSource = 'media' in data;
35980    const media = attachMediaSource ? data.media : data;
35981    const attachingData = attachMediaSource ? data : {
35982      media
35983    };
35984    this._media = media;
35985    this.trigger(Events.MEDIA_ATTACHING, attachingData);
35986  }
35987
35988  /**
35989   * Detach Hls.js from the media
35990   */
35991  detachMedia() {
35992    this.logger.log('detachMedia');
35993    this.trigger(Events.MEDIA_DETACHING, {});
35994    this._media = null;
35995  }
35996
35997  /**
35998   * Detach HTMLMediaElement, MediaSource, and SourceBuffers without reset, for attaching to another instance
35999   */
36000  transferMedia() {
36001    this._media = null;
36002    const transferMedia = this.bufferController.transferMedia();
36003    this.trigger(Events.MEDIA_DETACHING, {
36004      transferMedia
36005    });
36006    return transferMedia;
36007  }
36008
36009  /**
36010   * Set the source URL. Can be relative or absolute.
36011   */
36012  loadSource(url) {
36013    this.stopLoad();
36014    const media = this.media;
36015    const loadedSource = this._url;
36016    const loadingSource = this._url = urlToolkitExports.buildAbsoluteURL(self.location.href, url, {
36017      alwaysNormalize: true
36018    });
36019    this._autoLevelCapping = -1;
36020    this._maxHdcpLevel = null;
36021    this.logger.log(`loadSource:${loadingSource}`);
36022    if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
36023      // Remove and re-create MediaSource
36024      this.detachMedia();
36025      this.attachMedia(media);
36026    }
36027    // when attaching to a source URL, trigger a playlist load
36028    this.trigger(Events.MANIFEST_LOADING, {
36029      url: url
36030    });
36031  }
36032
36033  /**
36034   * Gets the currently loaded URL
36035   */
36036  get url() {
36037    return this._url;
36038  }
36039
36040  /**
36041   * Whether or not enough has been buffered to seek to start position or use `media.currentTime` to determine next load position
36042   */
36043  get hasEnoughToStart() {
36044    return this.streamController.hasEnoughToStart;
36045  }
36046
36047  /**
36048   * Get the startPosition set on startLoad(position) or on autostart with config.startPosition
36049   */
36050  get startPosition() {
36051    return this.streamController.startPositionValue;
36052  }
36053
36054  /**
36055   * Start loading data from the stream source.
36056   * Depending on default config, client starts loading automatically when a source is set.
36057   *
36058   * @param startPosition - Set the start position to stream from.
36059   * Defaults to -1 (None: starts from earliest point)
36060   */
36061  startLoad(startPosition = -1, skipSeekToStartPosition) {
36062    this.logger.log(`startLoad(${startPosition + (skipSeekToStartPosition ? ', <skip seek to start>' : '')})`);
36063    this.started = true;
36064    this.resumeBuffering();
36065    for (let i = 0; i < this.networkControllers.length; i++) {
36066      this.networkControllers[i].startLoad(startPosition, skipSeekToStartPosition);
36067      if (!this.started || !this.networkControllers) {
36068        break;
36069      }
36070    }
36071  }
36072
36073  /**
36074   * Stop loading of any stream data.
36075   */
36076  stopLoad() {
36077    this.logger.log('stopLoad');
36078    this.started = false;
36079    for (let i = 0; i < this.networkControllers.length; i++) {
36080      this.networkControllers[i].stopLoad();
36081      if (this.started || !this.networkControllers) {
36082        break;
36083      }
36084    }
36085  }
36086
36087  /**
36088   * Returns whether loading, toggled with `startLoad()` and `stopLoad()`, is active or not`.
36089   */
36090  get loadingEnabled() {
36091    return this.started;
36092  }
36093
36094  /**
36095   * Returns state of fragment loading toggled by calling `pauseBuffering()` and `resumeBuffering()`.
36096   */
36097  get bufferingEnabled() {
36098    return this.streamController.bufferingEnabled;
36099  }
36100
36101  /**
36102   * Resumes stream controller segment loading after `pauseBuffering` has been called.
36103   */
36104  resumeBuffering() {
36105    if (!this.bufferingEnabled) {
36106      this.logger.log(`resume buffering`);
36107      this.networkControllers.forEach(controller => {
36108        if (controller.resumeBuffering) {
36109          controller.resumeBuffering();
36110        }
36111      });
36112    }
36113  }
36114
36115  /**
36116   * Prevents stream controller from loading new segments until `resumeBuffering` is called.
36117   * This allows for media buffering to be paused without interupting playlist loading.
36118   */
36119  pauseBuffering() {
36120    if (this.bufferingEnabled) {
36121      this.logger.log(`pause buffering`);
36122      this.networkControllers.forEach(controller => {
36123        if (controller.pauseBuffering) {
36124          controller.pauseBuffering();
36125        }
36126      });
36127    }
36128  }
36129  get inFlightFragments() {
36130    const inFlightData = {
36131      [PlaylistLevelType.MAIN]: this.streamController.inFlightFrag
36132    };
36133    if (this.audioStreamController) {
36134      inFlightData[PlaylistLevelType.AUDIO] = this.audioStreamController.inFlightFrag;
36135    }
36136    if (this.subtititleStreamController) {
36137      inFlightData[PlaylistLevelType.SUBTITLE] = this.subtititleStreamController.inFlightFrag;
36138    }
36139    return inFlightData;
36140  }
36141
36142  /**
36143   * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
36144   */
36145  swapAudioCodec() {
36146    this.logger.log('swapAudioCodec');
36147    this.streamController.swapAudioCodec();
36148  }
36149
36150  /**
36151   * When the media-element fails, this allows to detach and then re-attach it
36152   * as one call (convenience method).
36153   *
36154   * Automatic recovery of media-errors by this process is configurable.
36155   */
36156  recoverMediaError() {
36157    this.logger.log('recoverMediaError');
36158    const media = this._media;
36159    const time = media == null ? void 0 : media.currentTime;
36160    this.detachMedia();
36161    if (media) {
36162      this.attachMedia(media);
36163      if (time) {
36164        this.startLoad(time);
36165      }
36166    }
36167  }
36168  removeLevel(levelIndex) {
36169    this.levelController.removeLevel(levelIndex);
36170  }
36171
36172  /**
36173   * @returns a UUID for this player instance
36174   */
36175  get sessionId() {
36176    let _sessionId = this._sessionId;
36177    if (!_sessionId) {
36178      _sessionId = this._sessionId = uuid();
36179    }
36180    return _sessionId;
36181  }
36182
36183  /**
36184   * @returns an array of levels (variants) sorted by HDCP-LEVEL, RESOLUTION (height), FRAME-RATE, CODECS, VIDEO-RANGE, and BANDWIDTH
36185   */
36186  get levels() {
36187    const levels = this.levelController.levels;
36188    return levels ? levels : [];
36189  }
36190
36191  /**
36192   * @returns LevelDetails of last loaded level (variant) or `null` prior to loading a media playlist.
36193   */
36194  get latestLevelDetails() {
36195    return this.streamController.getLevelDetails() || null;
36196  }
36197
36198  /**
36199   * @returns Level object of selected level (variant) or `null` prior to selecting a level or once the level is removed.
36200   */
36201  get loadLevelObj() {
36202    return this.levelController.loadLevelObj;
36203  }
36204
36205  /**
36206   * Index of quality level (variant) currently played
36207   */
36208  get currentLevel() {
36209    return this.streamController.currentLevel;
36210  }
36211
36212  /**
36213   * 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.
36214   */
36215  set currentLevel(newLevel) {
36216    this.logger.log(`set currentLevel:${newLevel}`);
36217    this.levelController.manualLevel = newLevel;
36218    this.streamController.immediateLevelSwitch();
36219  }
36220
36221  /**
36222   * Index of next quality level loaded as scheduled by stream controller.
36223   */
36224  get nextLevel() {
36225    return this.streamController.nextLevel;
36226  }
36227
36228  /**
36229   * Set quality level index for next loaded data.
36230   * This will switch the video quality asap, without interrupting playback.
36231   * May abort current loading of data, and flush parts of buffer (outside currently played fragment region).
36232   * @param newLevel - Pass -1 for automatic level selection
36233   */
36234  set nextLevel(newLevel) {
36235    this.logger.log(`set nextLevel:${newLevel}`);
36236    this.levelController.manualLevel = newLevel;
36237    this.streamController.nextLevelSwitch();
36238  }
36239
36240  /**
36241   * Return the quality level of the currently or last (of none is loaded currently) segment
36242   */
36243  get loadLevel() {
36244    return this.levelController.level;
36245  }
36246
36247  /**
36248   * Set quality level index for next loaded data in a conservative way.
36249   * This will switch the quality without flushing, but interrupt current loading.
36250   * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer.
36251   * @param newLevel - Pass -1 for automatic level selection
36252   */
36253  set loadLevel(newLevel) {
36254    this.logger.log(`set loadLevel:${newLevel}`);
36255    this.levelController.manualLevel = newLevel;
36256  }
36257
36258  /**
36259   * get next quality level loaded
36260   */
36261  get nextLoadLevel() {
36262    return this.levelController.nextLoadLevel;
36263  }
36264
36265  /**
36266   * Set quality level of next loaded segment in a fully "non-destructive" way.
36267   * Same as `loadLevel` but will wait for next switch (until current loading is done).
36268   */
36269  set nextLoadLevel(level) {
36270    this.levelController.nextLoadLevel = level;
36271  }
36272
36273  /**
36274   * Return "first level": like a default level, if not set,
36275   * falls back to index of first level referenced in manifest
36276   */
36277  get firstLevel() {
36278    return Math.max(this.levelController.firstLevel, this.minAutoLevel);
36279  }
36280
36281  /**
36282   * Sets "first-level", see getter.
36283   */
36284  set firstLevel(newLevel) {
36285    this.logger.log(`set firstLevel:${newLevel}`);
36286    this.levelController.firstLevel = newLevel;
36287  }
36288
36289  /**
36290   * Return the desired start level for the first fragment that will be loaded.
36291   * The default value of -1 indicates automatic start level selection.
36292   * Setting hls.nextAutoLevel without setting a startLevel will result in
36293   * the nextAutoLevel value being used for one fragment load.
36294   */
36295  get startLevel() {
36296    const startLevel = this.levelController.startLevel;
36297    if (startLevel === -1 && this.abrController.forcedAutoLevel > -1) {
36298      return this.abrController.forcedAutoLevel;
36299    }
36300    return startLevel;
36301  }
36302
36303  /**
36304   * set  start level (level of first fragment that will be played back)
36305   * if not overrided by user, first level appearing in manifest will be used as start level
36306   * if -1 : automatic start level selection, playback will start from level matching download bandwidth
36307   * (determined from download of first segment)
36308   */
36309  set startLevel(newLevel) {
36310    this.logger.log(`set startLevel:${newLevel}`);
36311    // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
36312    if (newLevel !== -1) {
36313      newLevel = Math.max(newLevel, this.minAutoLevel);
36314    }
36315    this.levelController.startLevel = newLevel;
36316  }
36317
36318  /**
36319   * Whether level capping is enabled.
36320   * Default value is set via `config.capLevelToPlayerSize`.
36321   */
36322  get capLevelToPlayerSize() {
36323    return this.config.capLevelToPlayerSize;
36324  }
36325
36326  /**
36327   * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called.
36328   */
36329  set capLevelToPlayerSize(shouldStartCapping) {
36330    const newCapLevelToPlayerSize = !!shouldStartCapping;
36331    if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) {
36332      if (newCapLevelToPlayerSize) {
36333        this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size.
36334      } else {
36335        this.capLevelController.stopCapping();
36336        this.autoLevelCapping = -1;
36337        this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap.
36338      }
36339      this.config.capLevelToPlayerSize = newCapLevelToPlayerSize;
36340    }
36341  }
36342
36343  /**
36344   * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
36345   */
36346  get autoLevelCapping() {
36347    return this._autoLevelCapping;
36348  }
36349
36350  /**
36351   * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned.
36352   */
36353  get bandwidthEstimate() {
36354    const {
36355      bwEstimator
36356    } = this.abrController;
36357    if (!bwEstimator) {
36358      return NaN;
36359    }
36360    return bwEstimator.getEstimate();
36361  }
36362  set bandwidthEstimate(abrEwmaDefaultEstimate) {
36363    this.abrController.resetEstimator(abrEwmaDefaultEstimate);
36364  }
36365  get abrEwmaDefaultEstimate() {
36366    const {
36367      bwEstimator
36368    } = this.abrController;
36369    if (!bwEstimator) {
36370      return NaN;
36371    }
36372    return bwEstimator.defaultEstimate;
36373  }
36374
36375  /**
36376   * get time to first byte estimate
36377   * @type {number}
36378   */
36379  get ttfbEstimate() {
36380    const {
36381      bwEstimator
36382    } = this.abrController;
36383    if (!bwEstimator) {
36384      return NaN;
36385    }
36386    return bwEstimator.getEstimateTTFB();
36387  }
36388
36389  /**
36390   * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`)
36391   */
36392  set autoLevelCapping(newLevel) {
36393    if (this._autoLevelCapping !== newLevel) {
36394      this.logger.log(`set autoLevelCapping:${newLevel}`);
36395      this._autoLevelCapping = newLevel;
36396      this.levelController.checkMaxAutoUpdated();
36397    }
36398  }
36399  get maxHdcpLevel() {
36400    return this._maxHdcpLevel;
36401  }
36402  set maxHdcpLevel(value) {
36403    if (isHdcpLevel(value) && this._maxHdcpLevel !== value) {
36404      this._maxHdcpLevel = value;
36405      this.levelController.checkMaxAutoUpdated();
36406    }
36407  }
36408
36409  /**
36410   * True when automatic level selection enabled
36411   */
36412  get autoLevelEnabled() {
36413    return this.levelController.manualLevel === -1;
36414  }
36415
36416  /**
36417   * Level set manually (if any)
36418   */
36419  get manualLevel() {
36420    return this.levelController.manualLevel;
36421  }
36422
36423  /**
36424   * min level selectable in auto mode according to config.minAutoBitrate
36425   */
36426  get minAutoLevel() {
36427    const {
36428      levels,
36429      config: {
36430        minAutoBitrate
36431      }
36432    } = this;
36433    if (!levels) return 0;
36434    const len = levels.length;
36435    for (let i = 0; i < len; i++) {
36436      if (levels[i].maxBitrate >= minAutoBitrate) {
36437        return i;
36438      }
36439    }
36440    return 0;
36441  }
36442
36443  /**
36444   * max level selectable in auto mode according to autoLevelCapping
36445   */
36446  get maxAutoLevel() {
36447    const {
36448      levels,
36449      autoLevelCapping,
36450      maxHdcpLevel
36451    } = this;
36452    let maxAutoLevel;
36453    if (autoLevelCapping === -1 && levels != null && levels.length) {
36454      maxAutoLevel = levels.length - 1;
36455    } else {
36456      maxAutoLevel = autoLevelCapping;
36457    }
36458    if (maxHdcpLevel) {
36459      for (let i = maxAutoLevel; i--;) {
36460        const hdcpLevel = levels[i].attrs['HDCP-LEVEL'];
36461        if (hdcpLevel && hdcpLevel <= maxHdcpLevel) {
36462          return i;
36463        }
36464      }
36465    }
36466    return maxAutoLevel;
36467  }
36468  get firstAutoLevel() {
36469    return this.abrController.firstAutoLevel;
36470  }
36471
36472  /**
36473   * next automatically selected quality level
36474   */
36475  get nextAutoLevel() {
36476    return this.abrController.nextAutoLevel;
36477  }
36478
36479  /**
36480   * this setter is used to force next auto level.
36481   * this is useful to force a switch down in auto mode:
36482   * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example)
36483   * forced value is valid for one fragment. upon successful frag loading at forced level,
36484   * this value will be resetted to -1 by ABR controller.
36485   */
36486  set nextAutoLevel(nextLevel) {
36487    this.abrController.nextAutoLevel = nextLevel;
36488  }
36489
36490  /**
36491   * get the datetime value relative to media.currentTime for the active level Program Date Time if present
36492   */
36493  get playingDate() {
36494    return this.streamController.currentProgramDateTime;
36495  }
36496  get mainForwardBufferInfo() {
36497    return this.streamController.getMainFwdBufferInfo();
36498  }
36499  get maxBufferLength() {
36500    return this.streamController.maxBufferLength;
36501  }
36502
36503  /**
36504   * Find and select the best matching audio track, making a level switch when a Group change is necessary.
36505   * Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found.
36506   */
36507  setAudioOption(audioOption) {
36508    var _this$audioTrackContr;
36509    return ((_this$audioTrackContr = this.audioTrackController) == null ? void 0 : _this$audioTrackContr.setAudioOption(audioOption)) || null;
36510  }
36511  /**
36512   * Find and select the best matching subtitle track, making a level switch when a Group change is necessary.
36513   * Updates `hls.config.subtitlePreference`. Returns the selected track, or null when no matching track is found.
36514   */
36515  setSubtitleOption(subtitleOption) {
36516    var _this$subtitleTrackCo;
36517    return ((_this$subtitleTrackCo = this.subtitleTrackController) == null ? void 0 : _this$subtitleTrackCo.setSubtitleOption(subtitleOption)) || null;
36518  }
36519
36520  /**
36521   * Get the complete list of audio tracks across all media groups
36522   */
36523  get allAudioTracks() {
36524    const audioTrackController = this.audioTrackController;
36525    return audioTrackController ? audioTrackController.allAudioTracks : [];
36526  }
36527
36528  /**
36529   * Get the list of selectable audio tracks
36530   */
36531  get audioTracks() {
36532    const audioTrackController = this.audioTrackController;
36533    return audioTrackController ? audioTrackController.audioTracks : [];
36534  }
36535
36536  /**
36537   * index of the selected audio track (index in audio track lists)
36538   */
36539  get audioTrack() {
36540    const audioTrackController = this.audioTrackController;
36541    return audioTrackController ? audioTrackController.audioTrack : -1;
36542  }
36543
36544  /**
36545   * selects an audio track, based on its index in audio track lists
36546   */
36547  set audioTrack(audioTrackId) {
36548    const audioTrackController = this.audioTrackController;
36549    if (audioTrackController) {
36550      audioTrackController.audioTrack = audioTrackId;
36551    }
36552  }
36553
36554  /**
36555   * get the complete list of subtitle tracks across all media groups
36556   */
36557  get allSubtitleTracks() {
36558    const subtitleTrackController = this.subtitleTrackController;
36559    return subtitleTrackController ? subtitleTrackController.allSubtitleTracks : [];
36560  }
36561
36562  /**
36563   * get alternate subtitle tracks list from playlist
36564   */
36565  get subtitleTracks() {
36566    const subtitleTrackController = this.subtitleTrackController;
36567    return subtitleTrackController ? subtitleTrackController.subtitleTracks : [];
36568  }
36569
36570  /**
36571   * index of the selected subtitle track (index in subtitle track lists)
36572   */
36573  get subtitleTrack() {
36574    const subtitleTrackController = this.subtitleTrackController;
36575    return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1;
36576  }
36577  get media() {
36578    return this._media;
36579  }
36580
36581  /**
36582   * select an subtitle track, based on its index in subtitle track lists
36583   */
36584  set subtitleTrack(subtitleTrackId) {
36585    const subtitleTrackController = this.subtitleTrackController;
36586    if (subtitleTrackController) {
36587      subtitleTrackController.subtitleTrack = subtitleTrackId;
36588    }
36589  }
36590
36591  /**
36592   * Whether subtitle display is enabled or not
36593   */
36594  get subtitleDisplay() {
36595    const subtitleTrackController = this.subtitleTrackController;
36596    return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false;
36597  }
36598
36599  /**
36600   * Enable/disable subtitle display rendering
36601   */
36602  set subtitleDisplay(value) {
36603    const subtitleTrackController = this.subtitleTrackController;
36604    if (subtitleTrackController) {
36605      subtitleTrackController.subtitleDisplay = value;
36606    }
36607  }
36608
36609  /**
36610   * get mode for Low-Latency HLS loading
36611   */
36612  get lowLatencyMode() {
36613    return this.config.lowLatencyMode;
36614  }
36615
36616  /**
36617   * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK.
36618   */
36619  set lowLatencyMode(mode) {
36620    this.config.lowLatencyMode = mode;
36621  }
36622
36623  /**
36624   * Position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```)
36625   * @returns null prior to loading live Playlist
36626   */
36627  get liveSyncPosition() {
36628    return this.latencyController.liveSyncPosition;
36629  }
36630
36631  /**
36632   * Estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced)
36633   * @returns 0 before first playlist is loaded
36634   */
36635  get latency() {
36636    return this.latencyController.latency;
36637  }
36638
36639  /**
36640   * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition```
36641   * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration```
36642   * @returns 0 before first playlist is loaded
36643   */
36644  get maxLatency() {
36645    return this.latencyController.maxLatency;
36646  }
36647
36648  /**
36649   * target distance from the edge as calculated by the latency controller
36650   */
36651  get targetLatency() {
36652    return this.latencyController.targetLatency;
36653  }
36654  set targetLatency(latency) {
36655    this.latencyController.targetLatency = latency;
36656  }
36657
36658  /**
36659   * the rate at which the edge of the current live playlist is advancing or 1 if there is none
36660   */
36661  get drift() {
36662    return this.latencyController.drift;
36663  }
36664
36665  /**
36666   * set to true when startLoad is called before MANIFEST_PARSED event
36667   */
36668  get forceStartLoad() {
36669    return this.streamController.forceStartLoad;
36670  }
36671
36672  /**
36673   * ContentSteering pathways getter
36674   */
36675  get pathways() {
36676    return this.levelController.pathways;
36677  }
36678
36679  /**
36680   * ContentSteering pathwayPriority getter/setter
36681   */
36682  get pathwayPriority() {
36683    return this.levelController.pathwayPriority;
36684  }
36685  set pathwayPriority(pathwayPriority) {
36686    this.levelController.pathwayPriority = pathwayPriority;
36687  }
36688
36689  /**
36690   * returns true when all SourceBuffers are buffered to the end
36691   */
36692  get bufferedToEnd() {
36693    var _this$bufferControlle;
36694    return !!((_this$bufferControlle = this.bufferController) != null && _this$bufferControlle.bufferedToEnd);
36695  }
36696
36697  /**
36698   * returns Interstitials Program Manager
36699   */
36700  get interstitialsManager() {
36701    var _this$interstitialsCo;
36702    return ((_this$interstitialsCo = this.interstitialsController) == null ? void 0 : _this$interstitialsCo.interstitialsManager) || null;
36703  }
36704
36705  /**
36706   * returns mediaCapabilities.decodingInfo for a variant/rendition
36707   */
36708  getMediaDecodingInfo(level, audioTracks = this.allAudioTracks) {
36709    const audioTracksByGroup = getAudioTracksByGroup(audioTracks);
36710    return getMediaDecodingInfoPromise(level, audioTracksByGroup, navigator.mediaCapabilities);
36711  }
36712}
36713Hls.defaultConfig = void 0;
36714
36715
36716//# sourceMappingURL=hls.mjs.map
36717
36718
36719/***/ })
36720
36721}]);

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.