hls.js
Version:
JavaScript HLS client using MediaSourceExtension
1,154 lines (1,122 loc) • 868 kB
JavaScript
// 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);