UNPKG

hls.js

Version:

JavaScript HLS client using MediaSourceExtension

1,154 lines (1,122 loc) • 868 kB
// https://caniuse.com/mdn-javascript_builtins_number_isfinite const isFiniteNumber = Number.isFinite || function (value) { return typeof value === 'number' && isFinite(value); }; // https://caniuse.com/mdn-javascript_builtins_number_issafeinteger const isSafeInteger = Number.isSafeInteger || function (value) { return typeof value === 'number' && Math.abs(value) <= MAX_SAFE_INTEGER; }; const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; let ErrorTypes = /*#__PURE__*/function (ErrorTypes) { // Identifier for a network error (loading error / timeout ...) ErrorTypes["NETWORK_ERROR"] = "networkError"; // Identifier for a media Error (video/parsing/mediasource error) ErrorTypes["MEDIA_ERROR"] = "mediaError"; // EME (encrypted media extensions) errors ErrorTypes["KEY_SYSTEM_ERROR"] = "keySystemError"; // Identifier for a mux Error (demuxing/remuxing) ErrorTypes["MUX_ERROR"] = "muxError"; // Identifier for all other errors ErrorTypes["OTHER_ERROR"] = "otherError"; return ErrorTypes; }({}); let ErrorDetails = /*#__PURE__*/function (ErrorDetails) { ErrorDetails["KEY_SYSTEM_NO_KEYS"] = "keySystemNoKeys"; ErrorDetails["KEY_SYSTEM_NO_ACCESS"] = "keySystemNoAccess"; ErrorDetails["KEY_SYSTEM_NO_SESSION"] = "keySystemNoSession"; ErrorDetails["KEY_SYSTEM_NO_CONFIGURED_LICENSE"] = "keySystemNoConfiguredLicense"; ErrorDetails["KEY_SYSTEM_LICENSE_REQUEST_FAILED"] = "keySystemLicenseRequestFailed"; ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED"] = "keySystemServerCertificateRequestFailed"; ErrorDetails["KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED"] = "keySystemServerCertificateUpdateFailed"; ErrorDetails["KEY_SYSTEM_SESSION_UPDATE_FAILED"] = "keySystemSessionUpdateFailed"; ErrorDetails["KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED"] = "keySystemStatusOutputRestricted"; ErrorDetails["KEY_SYSTEM_STATUS_INTERNAL_ERROR"] = "keySystemStatusInternalError"; ErrorDetails["KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR"] = "keySystemDestroyMediaKeysError"; ErrorDetails["KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR"] = "keySystemDestroyCloseSessionError"; ErrorDetails["KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR"] = "keySystemDestroyRemoveSessionError"; // Identifier for a manifest load error - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["MANIFEST_LOAD_ERROR"] = "manifestLoadError"; // Identifier for a manifest load timeout - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut"; // Identifier for a manifest parsing error - data: { url : faulty URL, reason : error reason} ErrorDetails["MANIFEST_PARSING_ERROR"] = "manifestParsingError"; // Identifier for a manifest with only incompatible codecs error - data: { url : faulty URL, reason : error reason} ErrorDetails["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError"; // Identifier for a level which contains no fragments - data: { url: faulty URL, reason: "no fragments found in level", level: index of the bad level } ErrorDetails["LEVEL_EMPTY_ERROR"] = "levelEmptyError"; // Identifier for a level load error - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["LEVEL_LOAD_ERROR"] = "levelLoadError"; // Identifier for a level load timeout - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut"; // Identifier for a level parse error - data: { url : faulty URL, error: Error, reason: error message } ErrorDetails["LEVEL_PARSING_ERROR"] = "levelParsingError"; // Identifier for a level switch error - data: { level : faulty level Id, event : error description} ErrorDetails["LEVEL_SWITCH_ERROR"] = "levelSwitchError"; // Identifier for an audio track load error - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError"; // Identifier for an audio track load timeout - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut"; // Identifier for a subtitle track load error - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError"; // Identifier for a subtitle track load timeout - data: { url : faulty URL, response : { code: error code, text: error text }} ErrorDetails["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut"; // Identifier for fragment load error - data: { frag : fragment object, response : { code: error code, text: error text }} ErrorDetails["FRAG_LOAD_ERROR"] = "fragLoadError"; // Identifier for fragment load timeout error - data: { frag : fragment object} ErrorDetails["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut"; // Identifier for a fragment decryption error event - data: {id : demuxer Id,frag: fragment object, reason : parsing error description } ErrorDetails["FRAG_DECRYPT_ERROR"] = "fragDecryptError"; // Identifier for a fragment parsing error event - data: { id : demuxer Id, reason : parsing error description } // will be renamed DEMUX_PARSING_ERROR and switched to MUX_ERROR in the next major release ErrorDetails["FRAG_PARSING_ERROR"] = "fragParsingError"; // Identifier for a fragment or part load skipped because of a GAP tag or attribute ErrorDetails["FRAG_GAP"] = "fragGap"; // 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 } ErrorDetails["REMUX_ALLOC_ERROR"] = "remuxAllocError"; // Identifier for decrypt key load error - data: { frag : fragment object, response : { code: error code, text: error text }} ErrorDetails["KEY_LOAD_ERROR"] = "keyLoadError"; // Identifier for decrypt key load timeout error - data: { frag : fragment object} ErrorDetails["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut"; // Triggered when an exception occurs while adding a sourceBuffer to MediaSource - data : { error : exception , mimeType : mimeType } ErrorDetails["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError"; // 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 } ErrorDetails["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError"; // Identifier for a buffer append error - data: append error description ErrorDetails["BUFFER_APPEND_ERROR"] = "bufferAppendError"; // Identifier for a buffer appending error event - data: appending error description ErrorDetails["BUFFER_APPENDING_ERROR"] = "bufferAppendingError"; // Identifier for a buffer stalled error event ErrorDetails["BUFFER_STALLED_ERROR"] = "bufferStalledError"; // Identifier for a buffer full event ErrorDetails["BUFFER_FULL_ERROR"] = "bufferFullError"; // Identifier for a buffer seek over hole event ErrorDetails["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole"; // Identifier for a buffer nudge on stall (playback is stuck although currentTime is in a buffered area) ErrorDetails["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall"; // Identifier for a Interstitial Asset List load error - data: { url: faulty URL, response: { code: error code, text: error text } } ErrorDetails["ASSET_LIST_LOAD_ERROR"] = "assetListLoadError"; // Identifier for a Interstitial Asset List load timeout - data: { url: faulty URL, response: { code: error code, text: error text } } ErrorDetails["ASSET_LIST_LOAD_TIMEOUT"] = "assetListLoadTimeout"; // Identifier for a Interstitial Asset List parsing error - data: { url : faulty URL, reason : error reason, response : { code: error code, text: error text }} ErrorDetails["ASSET_LIST_PARSING_ERROR"] = "assetListParsingError"; // Identifier for a Interstitial Asset List parsing error - data: { url : faulty URL, reason : error reason, response : { code: error code, text: error text }} ErrorDetails["INTERSTITIAL_ASSET_ITEM_ERROR"] = "interstitialAssetItemError"; // Identifier for an internal exception happening inside hls.js while handling an event ErrorDetails["INTERNAL_EXCEPTION"] = "internalException"; // Identifier for an internal call to abort a loader ErrorDetails["INTERNAL_ABORTED"] = "aborted"; // Triggered when attachMedia fails ErrorDetails["ATTACH_MEDIA_ERROR"] = "attachMediaError"; // Uncategorized error ErrorDetails["UNKNOWN"] = "unknown"; return ErrorDetails; }({}); let Events = /*#__PURE__*/function (Events) { // Fired before MediaSource is attaching to media element Events["MEDIA_ATTACHING"] = "hlsMediaAttaching"; // Fired when MediaSource has been successfully attached to media element Events["MEDIA_ATTACHED"] = "hlsMediaAttached"; // Fired before detaching MediaSource from media element Events["MEDIA_DETACHING"] = "hlsMediaDetaching"; // Fired when MediaSource has been detached from media element Events["MEDIA_DETACHED"] = "hlsMediaDetached"; // Fired when HTMLMediaElement dispatches "ended" event, or stalls at end of VOD program Events["MEDIA_ENDED"] = "hlsMediaEnded"; // Fired after playback stall is resolved with playing, seeked, or ended event following BUFFER_STALLED_ERROR Events["STALL_RESOLVED"] = "hlsStallResolved"; // Fired when the buffer is going to be reset Events["BUFFER_RESET"] = "hlsBufferReset"; // Fired when we know about the codecs that we need buffers for to push into - data: {tracks : { container, codec, levelCodec, initSegment, metadata }} Events["BUFFER_CODECS"] = "hlsBufferCodecs"; // fired when sourcebuffers have been created - data: { tracks : tracks } Events["BUFFER_CREATED"] = "hlsBufferCreated"; // fired when we append a segment to the buffer - data: { segment: segment object } Events["BUFFER_APPENDING"] = "hlsBufferAppending"; // 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} Events["BUFFER_APPENDED"] = "hlsBufferAppended"; // fired when the stream is finished and we want to notify the media buffer that there will be no more data - data: { } Events["BUFFER_EOS"] = "hlsBufferEos"; // fired when all buffers are full to the end of the program, after calling MediaSource.endOfStream() (unless restricted) Events["BUFFERED_TO_END"] = "hlsBufferedToEnd"; // fired when the media buffer should be flushed - data { startOffset, endOffset } Events["BUFFER_FLUSHING"] = "hlsBufferFlushing"; // fired when the media buffer has been flushed - data: { } Events["BUFFER_FLUSHED"] = "hlsBufferFlushed"; // fired to signal that a manifest loading starts - data: { url : manifestURL} Events["MANIFEST_LOADING"] = "hlsManifestLoading"; // fired after manifest has been loaded - data: { levels : [available quality levels], audioTracks : [ available audio tracks ], url : manifestURL, stats : LoaderStats } Events["MANIFEST_LOADED"] = "hlsManifestLoaded"; // fired after manifest has been parsed - data: { levels : [available quality levels], firstLevel : index of first quality level appearing in Manifest} Events["MANIFEST_PARSED"] = "hlsManifestParsed"; // fired when a level switch is requested - data: { level : id of new level } Events["LEVEL_SWITCHING"] = "hlsLevelSwitching"; // fired when a level switch is effective - data: { level : id of new level } Events["LEVEL_SWITCHED"] = "hlsLevelSwitched"; // fired when a level playlist loading starts - data: { url : level URL, level : id of level being loaded} Events["LEVEL_LOADING"] = "hlsLevelLoading"; // fired when a level playlist loading finishes - data: { details : levelDetails object, level : id of loaded level, stats : LoaderStats } Events["LEVEL_LOADED"] = "hlsLevelLoaded"; // 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 } Events["LEVEL_UPDATED"] = "hlsLevelUpdated"; // 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 } Events["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated"; // fired to notify that levels have changed after removing a level - data: { levels : [available quality levels] } Events["LEVELS_UPDATED"] = "hlsLevelsUpdated"; // fired to notify that audio track lists has been updated - data: { audioTracks : audioTracks } Events["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated"; // fired when an audio track switching is requested - data: { id : audio track id } Events["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching"; // fired when an audio track switch actually occurs - data: { id : audio track id } Events["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched"; // fired when an audio track loading starts - data: { url : audio track URL, id : audio track id } Events["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading"; // fired when an audio track loading finishes - data: { details : levelDetails object, id : audio track id, stats : LoaderStats } Events["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded"; // 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 } Events["AUDIO_TRACK_UPDATED"] = "hlsAudioTrackUpdated"; // fired to notify that subtitle track lists has been updated - data: { subtitleTracks : subtitleTracks } Events["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated"; // fired to notify that subtitle tracks were cleared as a result of stopping the media Events["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared"; // fired when an subtitle track switch occurs - data: { id : subtitle track id } Events["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch"; // fired when a subtitle track loading starts - data: { url : subtitle track URL, id : subtitle track id } Events["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading"; // fired when a subtitle track loading finishes - data: { details : levelDetails object, id : subtitle track id, stats : LoaderStats } Events["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded"; // 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 } Events["SUBTITLE_TRACK_UPDATED"] = "hlsSubtitleTrackUpdated"; // fired when a subtitle fragment has been processed - data: { success : boolean, frag : the processed frag } Events["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed"; // fired when a set of VTTCues to be managed externally has been parsed - data: { type: string, track: string, cues: [ VTTCue ] } Events["CUES_PARSED"] = "hlsCuesParsed"; // fired when a text track to be managed externally is found - data: { tracks: [ { label: string, kind: string, default: boolean } ] } Events["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound"; // fired when the first timestamp is found - data: { id : demuxer id, initPTS: initPTS, timescale: timescale, frag : fragment object } Events["INIT_PTS_FOUND"] = "hlsInitPtsFound"; // fired when a fragment loading starts - data: { frag : fragment object } Events["FRAG_LOADING"] = "hlsFragLoading"; // fired when a fragment loading is progressing - data: { frag : fragment object, { trequest, tfirst, loaded } } // FRAG_LOAD_PROGRESS = 'hlsFragLoadProgress', // Identifier for fragment load aborting for emergency switch down - data: { frag : fragment object } Events["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted"; // fired when a fragment loading is completed - data: { frag : fragment object, payload : fragment payload, stats : LoaderStats } Events["FRAG_LOADED"] = "hlsFragLoaded"; // fired when a fragment has finished decrypting - data: { id : demuxer id, frag: fragment object, payload : fragment payload, stats : { tstart, tdecrypt } } Events["FRAG_DECRYPTED"] = "hlsFragDecrypted"; // 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 } Events["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment"; // fired when parsing sei text is completed - data: { id : demuxer id, frag: fragment object, samples : [ sei samples pes ] } Events["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata"; // fired when parsing id3 is completed - data: { id : demuxer id, frag: fragment object, samples : [ id3 samples pes ] } Events["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata"; // 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} // FRAG_PARSING_DATA = 'hlsFragParsingData', // fired when fragment parsing is completed - data: { id : demuxer id, frag: fragment object } Events["FRAG_PARSED"] = "hlsFragParsed"; // fired when fragment remuxed MP4 boxes have all been appended into SourceBuffer - data: { id : demuxer id, frag : fragment object, stats : LoaderStats } Events["FRAG_BUFFERED"] = "hlsFragBuffered"; // fired when fragment matching with current media position is changing - data : { id : demuxer id, frag : fragment object } Events["FRAG_CHANGED"] = "hlsFragChanged"; // Identifier for a FPS drop event - data: { currentDropped, currentDecoded, totalDroppedFrames } Events["FPS_DROP"] = "hlsFpsDrop"; // triggered when FPS drop triggers auto level capping - data: { level, droppedLevel } Events["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping"; // triggered when maxAutoLevel changes - data { autoLevelCapping, levels, maxAutoLevel, minAutoLevel, maxHdcpLevel } Events["MAX_AUTO_LEVEL_UPDATED"] = "hlsMaxAutoLevelUpdated"; // 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 } Events["ERROR"] = "hlsError"; // 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: { } Events["DESTROYING"] = "hlsDestroying"; // fired when a decrypt key loading starts - data: { frag : fragment object } Events["KEY_LOADING"] = "hlsKeyLoading"; // fired when a decrypt key loading is completed - data: { frag : fragment object, keyInfo : KeyLoaderInfo } Events["KEY_LOADED"] = "hlsKeyLoaded"; // deprecated; please use BACK_BUFFER_REACHED - data : { bufferEnd: number } Events["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached"; // fired when the back buffer is reached as defined by the backBufferLength config option - data : { bufferEnd: number } Events["BACK_BUFFER_REACHED"] = "hlsBackBufferReached"; // fired after steering manifest has been loaded - data: { steeringManifest: SteeringManifest object, url: steering manifest URL } Events["STEERING_MANIFEST_LOADED"] = "hlsSteeringManifestLoaded"; // fired when asset list has begun loading Events["ASSET_LIST_LOADING"] = "hlsAssetListLoading"; // fired when a valid asset list is loaded Events["ASSET_LIST_LOADED"] = "hlsAssetListLoaded"; // fired when the list of Interstitial Events and Interstitial Schedule is updated Events["INTERSTITIALS_UPDATED"] = "hlsInterstitialsUpdated"; // fired when the buffer reaches an Interstitial Schedule boundary (both Primary segments and Interstitial Assets) Events["INTERSTITIALS_BUFFERED_TO_BOUNDARY"] = "hlsInterstitialsBufferedToBoundary"; // fired when a player instance for an Interstitial Asset has been created Events["INTERSTITIAL_ASSET_PLAYER_CREATED"] = "hlsInterstitialAssetPlayerCreated"; // Interstitial playback started Events["INTERSTITIAL_STARTED"] = "hlsInterstitialStarted"; // InterstitialAsset playback started Events["INTERSTITIAL_ASSET_STARTED"] = "hlsInterstitialAssetStarted"; // InterstitialAsset playback ended Events["INTERSTITIAL_ASSET_ENDED"] = "hlsInterstitialAssetEnded"; // InterstitialAsset playback errored Events["INTERSTITIAL_ASSET_ERROR"] = "hlsInterstitialAssetError"; // Interstitial playback ended Events["INTERSTITIAL_ENDED"] = "hlsInterstitialEnded"; // Interstitial schedule resumed primary playback Events["INTERSTITIALS_PRIMARY_RESUMED"] = "hlsInterstitialsPrimaryResumed"; // Interstitial players dispatch this event when playout limit is reached Events["PLAYOUT_LIMIT_REACHED"] = "hlsPlayoutLimitReached"; // Event DateRange cue "enter" event dispatched Events["EVENT_CUE_ENTER"] = "hlsEventCueEnter"; return Events; }({}); /** * Defines each Event type and payload by Event name. Used in {@link hls.js#HlsEventEmitter} to strongly type the event listener API. */ var PlaylistContextType = { MANIFEST: "manifest", LEVEL: "level", AUDIO_TRACK: "audioTrack", SUBTITLE_TRACK: "subtitleTrack" }; var PlaylistLevelType = { MAIN: "main", AUDIO: "audio", SUBTITLE: "subtitle" }; /* * compute an Exponential Weighted moving average * - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average * - heavily inspired from shaka-player */ class EWMA { // About half of the estimated value will be from the last |halfLife| samples by weight. constructor(halfLife, estimate = 0, weight = 0) { this.halfLife = void 0; this.alpha_ = void 0; this.estimate_ = void 0; this.totalWeight_ = void 0; this.halfLife = halfLife; // Larger values of alpha expire historical data more slowly. this.alpha_ = halfLife ? Math.exp(Math.log(0.5) / halfLife) : 0; this.estimate_ = estimate; this.totalWeight_ = weight; } sample(weight, value) { const adjAlpha = Math.pow(this.alpha_, weight); this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_; this.totalWeight_ += weight; } getTotalWeight() { return this.totalWeight_; } getEstimate() { if (this.alpha_) { const zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_); if (zeroFactor) { return this.estimate_ / zeroFactor; } } return this.estimate_; } } /* * EWMA Bandwidth Estimator * - heavily inspired from shaka-player * Tracks bandwidth samples and estimates available bandwidth. * Based on the minimum of two exponentially-weighted moving averages with * different half-lives. */ class EwmaBandWidthEstimator { constructor(slow, fast, defaultEstimate, defaultTTFB = 100) { this.defaultEstimate_ = void 0; this.minWeight_ = void 0; this.minDelayMs_ = void 0; this.slow_ = void 0; this.fast_ = void 0; this.defaultTTFB_ = void 0; this.ttfb_ = void 0; this.defaultEstimate_ = defaultEstimate; this.minWeight_ = 0.001; this.minDelayMs_ = 50; this.slow_ = new EWMA(slow); this.fast_ = new EWMA(fast); this.defaultTTFB_ = defaultTTFB; this.ttfb_ = new EWMA(slow); } update(slow, fast) { const { slow_, fast_, ttfb_ } = this; if (slow_.halfLife !== slow) { this.slow_ = new EWMA(slow, slow_.getEstimate(), slow_.getTotalWeight()); } if (fast_.halfLife !== fast) { this.fast_ = new EWMA(fast, fast_.getEstimate(), fast_.getTotalWeight()); } if (ttfb_.halfLife !== slow) { this.ttfb_ = new EWMA(slow, ttfb_.getEstimate(), ttfb_.getTotalWeight()); } } sample(durationMs, numBytes) { durationMs = Math.max(durationMs, this.minDelayMs_); const numBits = 8 * numBytes; // weight is duration in seconds const durationS = durationMs / 1000; // value is bandwidth in bits/s const bandwidthInBps = numBits / durationS; this.fast_.sample(durationS, bandwidthInBps); this.slow_.sample(durationS, bandwidthInBps); } sampleTTFB(ttfb) { // weight is frequency curve applied to TTFB in seconds // (longer times have less weight with expected input under 1 second) const seconds = ttfb / 1000; const weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2); this.ttfb_.sample(weight, Math.max(ttfb, 5)); } canEstimate() { return this.fast_.getTotalWeight() >= this.minWeight_; } getEstimate() { if (this.canEstimate()) { // console.log('slow estimate:'+ Math.round(this.slow_.getEstimate())); // console.log('fast estimate:'+ Math.round(this.fast_.getEstimate())); // Take the minimum of these two estimates. This should have the effect of // adapting down quickly, but up more slowly. return Math.min(this.fast_.getEstimate(), this.slow_.getEstimate()); } else { return this.defaultEstimate_; } } getEstimateTTFB() { if (this.ttfb_.getTotalWeight() >= this.minWeight_) { return this.ttfb_.getEstimate(); } else { return this.defaultTTFB_; } } get defaultEstimate() { return this.defaultEstimate_; } destroy() {} } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: true, configurable: true, writable: true }) : e[r] = t, e; } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } class Logger { constructor(label, logger) { this.trace = void 0; this.debug = void 0; this.log = void 0; this.warn = void 0; this.info = void 0; this.error = void 0; const lb = `[${label}]:`; this.trace = noop; this.debug = logger.debug.bind(null, lb); this.log = logger.log.bind(null, lb); this.warn = logger.warn.bind(null, lb); this.info = logger.info.bind(null, lb); this.error = logger.error.bind(null, lb); } } const noop = function noop() {}; const fakeLogger = { trace: noop, debug: noop, log: noop, warn: noop, info: noop, error: noop }; function createLogger() { return _extends({}, fakeLogger); } // let lastCallTime; // function formatMsgWithTimeInfo(type, msg) { // const now = Date.now(); // const diff = lastCallTime ? '+' + (now - lastCallTime) : '0'; // lastCallTime = now; // msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )'; // return msg; // } function consolePrintFn(type, id) { const func = self.console[type]; return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop; } function getLoggerFn(key, debugConfig, id) { return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id); } const exportedLogger = createLogger(); function enableLogs(debugConfig, context, id) { // check that console is available const newLogger = createLogger(); if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') { const keys = [ // Remove out from list here to hard-disable a log-level // 'trace', 'debug', 'log', 'info', 'warn', 'error']; keys.forEach(key => { newLogger[key] = getLoggerFn(key, debugConfig, id); }); // Some browsers don't allow to use bind on console object anyway // fallback to default if needed try { newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.6.16"}`); } catch (e) { /* log fn threw an exception. All logger methods are no-ops. */ return createLogger(); } // global exported logger uses the same functions as new logger without `id` keys.forEach(key => { exportedLogger[key] = getLoggerFn(key, debugConfig); }); } else { // Reset global exported logger _extends(exportedLogger, newLogger); } return newLogger; } const logger = exportedLogger; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var emptyEs; var hasRequiredEmptyEs; function requireEmptyEs() { if (hasRequiredEmptyEs) return emptyEs; hasRequiredEmptyEs = 1; // This file is inserted as a shim for modules which we do not want to include into the distro. // This replacement is done in the "alias" plugin of the rollup config. // Use a ES dedicated file as Rollup assigns an object in the output // For example: "var KeySystemFormats = emptyEs.KeySystemFormats;" emptyEs = {}; return emptyEs; } var emptyEsExports = requireEmptyEs(); var Cues = /*@__PURE__*/getDefaultExportFromCjs(emptyEsExports); function getMediaSource(preferManagedMediaSource = true) { if (typeof self === 'undefined') return undefined; const mms = (preferManagedMediaSource || !self.MediaSource) && self.ManagedMediaSource; return mms || self.MediaSource || self.WebKitMediaSource; } function isManagedMediaSource(source) { return typeof self !== 'undefined' && source === self.ManagedMediaSource; } function isCompatibleTrackChange(currentTracks, requiredTracks) { const trackNames = Object.keys(currentTracks); const requiredTrackNames = Object.keys(requiredTracks); const trackCount = trackNames.length; const requiredTrackCount = requiredTrackNames.length; return !trackCount || !requiredTrackCount || trackCount === requiredTrackCount && !trackNames.some(name => requiredTrackNames.indexOf(name) === -1); } // http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197 // http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt /* utf.js - UTF-8 <=> UTF-16 convertion * * Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp> * Version: 1.0 * LastModified: Dec 25 1999 * This library is free. You can redistribute it and/or modify it. */ /** * Converts a UTF-8 array to a string. * * @param array - The UTF-8 array to convert * * @returns The string * * @group Utils * * @beta */ function utf8ArrayToStr(array, exitOnNull = false) { if (typeof TextDecoder !== 'undefined') { const decoder = new TextDecoder('utf-8'); const decoded = decoder.decode(array); if (exitOnNull) { // grab up to the first null const idx = decoded.indexOf('\0'); return idx !== -1 ? decoded.substring(0, idx) : decoded; } // remove any null characters return decoded.replace(/\0/g, ''); } const len = array.length; let c; let char2; let char3; let out = ''; let i = 0; while (i < len) { c = array[i++]; if (c === 0x00 && exitOnNull) { return out; } else if (c === 0x00 || c === 0x03) { // If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it continue; } switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: // 0xxxxxxx out += String.fromCharCode(c); break; case 12: case 13: // 110x xxxx 10xx xxxx char2 = array[i++]; out += String.fromCharCode((c & 0x1f) << 6 | char2 & 0x3f); break; case 14: // 1110 xxxx 10xx xxxx 10xx xxxx char2 = array[i++]; char3 = array[i++]; out += String.fromCharCode((c & 0x0f) << 12 | (char2 & 0x3f) << 6 | (char3 & 0x3f) << 0); break; } } return out; } /** * hex dump helper class */ function arrayToHex(array) { let str = ''; for (let i = 0; i < array.length; i++) { let h = array[i].toString(16); if (h.length < 2) { h = '0' + h; } str += h; } return str; } function hexToArrayBuffer(str) { return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer; } var urlToolkit = {exports: {}}; var hasRequiredUrlToolkit; function requireUrlToolkit () { if (hasRequiredUrlToolkit) return urlToolkit.exports; hasRequiredUrlToolkit = 1; (function (module, exports) { // see https://tools.ietf.org/html/rfc1808 (function (root) { var URL_REGEX = /^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/; var FIRST_SEGMENT_REGEX = /^(?=([^\/?#]*))\1([^]*)$/; var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g; var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g; var URLToolkit = { // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or // // E.g // With opts.alwaysNormalize = false (default, spec compliant) // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g // With opts.alwaysNormalize = true (not spec compliant) // http://a.com/b/cd + /e/f/../g => http://a.com/e/g buildAbsoluteURL: function (baseURL, relativeURL, opts) { opts = opts || {}; // remove any remaining space and CRLF baseURL = baseURL.trim(); relativeURL = relativeURL.trim(); if (!relativeURL) { // 2a) If the embedded URL is entirely empty, it inherits the // entire base URL (i.e., is set equal to the base URL) // and we are done. if (!opts.alwaysNormalize) { return baseURL; } var basePartsForNormalise = URLToolkit.parseURL(baseURL); if (!basePartsForNormalise) { throw new Error('Error trying to parse base URL.'); } basePartsForNormalise.path = URLToolkit.normalizePath( basePartsForNormalise.path ); return URLToolkit.buildURLFromParts(basePartsForNormalise); } var relativeParts = URLToolkit.parseURL(relativeURL); if (!relativeParts) { throw new Error('Error trying to parse relative URL.'); } if (relativeParts.scheme) { // 2b) If the embedded URL starts with a scheme name, it is // interpreted as an absolute URL and we are done. if (!opts.alwaysNormalize) { return relativeURL; } relativeParts.path = URLToolkit.normalizePath(relativeParts.path); return URLToolkit.buildURLFromParts(relativeParts); } var baseParts = URLToolkit.parseURL(baseURL); if (!baseParts) { throw new Error('Error trying to parse base URL.'); } if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') { // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a' var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path); baseParts.netLoc = pathParts[1]; baseParts.path = pathParts[2]; } if (baseParts.netLoc && !baseParts.path) { baseParts.path = '/'; } var builtParts = { // 2c) Otherwise, the embedded URL inherits the scheme of // the base URL. scheme: baseParts.scheme, netLoc: relativeParts.netLoc, path: null, params: relativeParts.params, query: relativeParts.query, fragment: relativeParts.fragment, }; if (!relativeParts.netLoc) { // 3) If the embedded URL's <net_loc> is non-empty, we skip to // Step 7. Otherwise, the embedded URL inherits the <net_loc> // (if any) of the base URL. builtParts.netLoc = baseParts.netLoc; // 4) If the embedded URL path is preceded by a slash "/", the // path is not relative and we skip to Step 7. if (relativeParts.path[0] !== '/') { if (!relativeParts.path) { // 5) If the embedded URL path is empty (and not preceded by a // slash), then the embedded URL inherits the base URL path builtParts.path = baseParts.path; // 5a) if the embedded URL's <params> is non-empty, we skip to // step 7; otherwise, it inherits the <params> of the base // URL (if any) and if (!relativeParts.params) { builtParts.params = baseParts.params; // 5b) if the embedded URL's <query> is non-empty, we skip to // step 7; otherwise, it inherits the <query> of the base // URL (if any) and we skip to step 7. if (!relativeParts.query) { builtParts.query = baseParts.query; } } } else { // 6) The last segment of the base URL's path (anything // following the rightmost slash "/", or the entire path if no // slash is present) is removed and the embedded URL's path is // appended in its place. var baseURLPath = baseParts.path; var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path; builtParts.path = URLToolkit.normalizePath(newPath); } } } if (builtParts.path === null) { builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path; } return URLToolkit.buildURLFromParts(builtParts); }, parseURL: function (url) { var parts = URL_REGEX.exec(url); if (!parts) { return null; } return { scheme: parts[1] || '', netLoc: parts[2] || '', path: parts[3] || '', params: parts[4] || '', query: parts[5] || '', fragment: parts[6] || '', }; }, normalizePath: function (path) { // The following operations are // then applied, in order, to the new path: // 6a) All occurrences of "./", where "." is a complete path // segment, are removed. // 6b) If the path ends with "." as a complete path segment, // that "." is removed. path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, ''); // 6c) All occurrences of "<segment>/../", where <segment> is a // complete path segment not equal to "..", are removed. // Removal of these path segments is performed iteratively, // removing the leftmost matching pattern on each iteration, // until no matching pattern remains. // 6d) If the path ends with "<segment>/..", where <segment> is a // complete path segment not equal to "..", that // "<segment>/.." is removed. while ( path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length ) {} return path.split('').reverse().join(''); }, buildURLFromParts: function (parts) { return ( parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment ); }, }; module.exports = URLToolkit; })(); } (urlToolkit)); return urlToolkit.exports; } var urlToolkitExports = requireUrlToolkit(); class LoadStats { constructor() { this.aborted = false; this.loaded = 0; this.retry = 0; this.total = 0; this.chunkCount = 0; this.bwEstimate = 0; this.loading = { start: 0, first: 0, end: 0 }; this.parsing = { start: 0, end: 0 }; this.buffering = { start: 0, first: 0, end: 0 }; } } var ElementaryStreamTypes = { AUDIO: "audio", VIDEO: "video", AUDIOVIDEO: "audiovideo" }; class BaseSegment { constructor(base) { this._byteRange = null; this._url = null; this._stats = null; this._streams = null; // baseurl is the URL to the playlist this.base = void 0; // relurl is the portion of the URL that comes from inside the playlist. this.relurl = void 0; if (typeof base === 'string') { base = { url: base }; } this.base = base; makeEnumerable(this, 'stats'); } // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array setByteRange(value, previous) { const params = value.split('@', 2); let start; if (params.length === 1) { start = (previous == null ? void 0 : previous.byteRangeEndOffset) || 0; } else { start = parseInt(params[1]); } this._byteRange = [start, parseInt(params[0]) + start]; } get baseurl() { return this.base.url; } get byteRange() { if (this._byteRange === null) { return []; } return this._byteRange; } get byteRangeStartOffset() { return this.byteRange[0]; } get byteRangeEndOffset() { return this.byteRange[1]; } get elementaryStreams() { if (this._streams === null) { this._streams = { [ElementaryStreamTypes.AUDIO]: null, [ElementaryStreamTypes.VIDEO]: null, [ElementaryStreamTypes.AUDIOVIDEO]: null }; } return this._streams; } set elementaryStreams(value) { this._streams = value; } get hasStats() { return this._stats !== null; } get hasStreams() { return this._streams !== null; } get stats() { if (this._stats === null) { this._stats = new LoadStats(); } return this._stats; } set stats(value) { this._stats = value; } get url() { if (!this._url && this.baseurl && this.relurl) { this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, { alwaysNormalize: true }); } return this._url || ''; } set url(value) { this._url = value; } clearElementaryStreamInfo() { const { elementaryStreams } = this; elementaryStreams[ElementaryStreamTypes.AUDIO] = null; elementaryStreams[ElementaryStreamTypes.VIDEO] = null; elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null; } } function isMediaFragment(frag) { return frag.sn !== 'initSegment'; } /** * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}. */ class Fragment extends BaseSegment { constructor(type, base) { super(base); this._decryptdata = null; this._programDateTime = null; this._ref = null; // Approximate bit rate of the fragment expressed in bits per second (bps) as indicated by the last EXT-X-BITRATE (kbps) tag this._bitrate = void 0; this.rawProgramDateTime = null; this.tagList = []; // EXTINF has to be present for a m3u8 to be considered valid this.duration = 0; // sn notates the sequence number for a segment, and if set to a string can be 'initSegment' this.sn = 0; // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption // core difference from the private field _decryptdata is the lack of the initialized IV // _decryptdata will set the IV for this segment based on the segment number in the fragment this.levelkeys = void 0; // A string representing the fragment type this.type = void 0; // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading this.loader = null; // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading this.keyLoader = null; // The level/track index to which the fragment belongs this.level = -1; // The continuity counter of the fragment this.cc = 0; // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. this.startPTS = void 0; // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. this.endPTS = void 0; // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete. this.startDTS = void 0; // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete. this.endDTS = void 0; // The start time of the fragment, as listed in the manifest. Updated after transmux complete. this.start = 0; // The offset time (seconds) of the fragment from the start of the Playlist this.playlistOffset = 0; // Set by `updateFragPTSDTS` in level-helper this.deltaPTS = void 0; // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. this.maxStartPTS = void 0; // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. this.minEndPTS = void 0; // Init Segment bytes (unset for media segments) this.data = void 0; // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered this.bitrateTest = false; // #EXTINF segment title this.title = null; // The Media Initialization Section for this segment this.initSegment = null; // Fragment is the last fragment in the media playlist this.endList = void 0; // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded this.gap = void 0; // Deprecated this.urlId = 0; this.type = type; } get byteLength() { if (this.hasStats) { const total = this.stats.total; if (total) { return total; } } if (this.byteRange.length) { const start = this.byteRange[0]; const end = this.byteRange[1]; if (isFiniteNumber(start) && isFiniteNumber(end)) { return end - start; } } return null; } get bitrate() { if (this.byteLength) { return this.byteLength * 8 / this.duration; } if (this._bitrate) { return this._bitrate; } return null; } set bitrate(value) { this._bitrate = value; } get decryptdata() { var _this$_decryptdata; const { levelkeys } = this; if (!levelkeys || levelkeys.NONE) { return null; } if (levelkeys.identity) { if (!this._decryptdata) { this._decryptdata = levelkeys.identity.getDecryptData(this.sn); } } else if (!((_this$_decryptdata = this._decryptdata) != null && _this$_decryptdata.keyId)) { const keyFormats = Object.keys(levelkeys); if (keyFormats.length === 1) { const levelKey = this._decryptdata = levelkeys[keyFormats[0]] || null; if (levelKey) { this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys); } } } return this._decryptdata; } get end() { return this.start + this.duration; } get endProgramDateTime() { if (this.programDateTime === null) { return null; } const duration = !isFiniteNumber(this.duration) ? 0 : this.duration; return this.programDateTime + duration * 1000; } get encrypted() { var _this$_decryptdata2; // At the m3u8-parser level we need to add support for manifest signalled keyformats // when we want the fragment to start reporting that it is encrypted. // Currently, keyFormat will only be set for identity keys if ((_this$_decryptdata2 = this._decryptdata) != null && _this$_decryptdata2.encrypted) { return true; } else if (this.levelkeys) { var _this$levelkeys$keyFo; const keyFormats = Object.keys(this.levelkeys); const len = keyFormats.length; if (len > 1 || len === 1 && (_this$levelkeys$keyFo = this.levelkeys[keyFormats[0]]) != null && _this$levelkeys$keyFo.encrypted) { return true; } } return false; } get programDateTime() { if (this._programDateTime === null && this.rawProgramDateTime) { this.programDateTime = Date.parse(this.rawProgramDateTime); } return this._programDateTime; } set programDateTime(value) { if (!isFiniteNumber(value)) { this._programDateTime = this.rawProgramDateTime = null; return; } this._programDateTime = value; } get ref() { if (!isMediaFragment(this)) { return null; } if (!this._ref) { this._ref = { base: this.base, start: this.start, duration: this.duration, sn: this.sn, programDateTime: this.programDateTime }; } return this._ref; } addStart(value) { this.setStart(this.start + value);