UNPKG

@tarojs/components

Version:

Taro 组件库

1,502 lines (1,450 loc) • 885 kB
function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var urlToolkit = {exports: {}}; (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)); var urlToolkitExports = urlToolkit.exports; function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } const isFiniteNumber = Number.isFinite || function (value) { return typeof value === 'number' && isFinite(value); }; let Events = /*#__PURE__*/function (Events) { Events["MEDIA_ATTACHING"] = "hlsMediaAttaching"; Events["MEDIA_ATTACHED"] = "hlsMediaAttached"; Events["MEDIA_DETACHING"] = "hlsMediaDetaching"; Events["MEDIA_DETACHED"] = "hlsMediaDetached"; Events["BUFFER_RESET"] = "hlsBufferReset"; Events["BUFFER_CODECS"] = "hlsBufferCodecs"; Events["BUFFER_CREATED"] = "hlsBufferCreated"; Events["BUFFER_APPENDING"] = "hlsBufferAppending"; Events["BUFFER_APPENDED"] = "hlsBufferAppended"; Events["BUFFER_EOS"] = "hlsBufferEos"; Events["BUFFER_FLUSHING"] = "hlsBufferFlushing"; Events["BUFFER_FLUSHED"] = "hlsBufferFlushed"; Events["MANIFEST_LOADING"] = "hlsManifestLoading"; Events["MANIFEST_LOADED"] = "hlsManifestLoaded"; Events["MANIFEST_PARSED"] = "hlsManifestParsed"; Events["LEVEL_SWITCHING"] = "hlsLevelSwitching"; Events["LEVEL_SWITCHED"] = "hlsLevelSwitched"; Events["LEVEL_LOADING"] = "hlsLevelLoading"; Events["LEVEL_LOADED"] = "hlsLevelLoaded"; Events["LEVEL_UPDATED"] = "hlsLevelUpdated"; Events["LEVEL_PTS_UPDATED"] = "hlsLevelPtsUpdated"; Events["LEVELS_UPDATED"] = "hlsLevelsUpdated"; Events["AUDIO_TRACKS_UPDATED"] = "hlsAudioTracksUpdated"; Events["AUDIO_TRACK_SWITCHING"] = "hlsAudioTrackSwitching"; Events["AUDIO_TRACK_SWITCHED"] = "hlsAudioTrackSwitched"; Events["AUDIO_TRACK_LOADING"] = "hlsAudioTrackLoading"; Events["AUDIO_TRACK_LOADED"] = "hlsAudioTrackLoaded"; Events["SUBTITLE_TRACKS_UPDATED"] = "hlsSubtitleTracksUpdated"; Events["SUBTITLE_TRACKS_CLEARED"] = "hlsSubtitleTracksCleared"; Events["SUBTITLE_TRACK_SWITCH"] = "hlsSubtitleTrackSwitch"; Events["SUBTITLE_TRACK_LOADING"] = "hlsSubtitleTrackLoading"; Events["SUBTITLE_TRACK_LOADED"] = "hlsSubtitleTrackLoaded"; Events["SUBTITLE_FRAG_PROCESSED"] = "hlsSubtitleFragProcessed"; Events["CUES_PARSED"] = "hlsCuesParsed"; Events["NON_NATIVE_TEXT_TRACKS_FOUND"] = "hlsNonNativeTextTracksFound"; Events["INIT_PTS_FOUND"] = "hlsInitPtsFound"; Events["FRAG_LOADING"] = "hlsFragLoading"; Events["FRAG_LOAD_EMERGENCY_ABORTED"] = "hlsFragLoadEmergencyAborted"; Events["FRAG_LOADED"] = "hlsFragLoaded"; Events["FRAG_DECRYPTED"] = "hlsFragDecrypted"; Events["FRAG_PARSING_INIT_SEGMENT"] = "hlsFragParsingInitSegment"; Events["FRAG_PARSING_USERDATA"] = "hlsFragParsingUserdata"; Events["FRAG_PARSING_METADATA"] = "hlsFragParsingMetadata"; Events["FRAG_PARSED"] = "hlsFragParsed"; Events["FRAG_BUFFERED"] = "hlsFragBuffered"; Events["FRAG_CHANGED"] = "hlsFragChanged"; Events["FPS_DROP"] = "hlsFpsDrop"; Events["FPS_DROP_LEVEL_CAPPING"] = "hlsFpsDropLevelCapping"; Events["ERROR"] = "hlsError"; Events["DESTROYING"] = "hlsDestroying"; Events["KEY_LOADING"] = "hlsKeyLoading"; Events["KEY_LOADED"] = "hlsKeyLoaded"; Events["LIVE_BACK_BUFFER_REACHED"] = "hlsLiveBackBufferReached"; Events["BACK_BUFFER_REACHED"] = "hlsBackBufferReached"; return Events; }({}); /** * Defines each Event type and payload by Event name. Used in {@link hls.js#HlsEventEmitter} to strongly type the event listener API. */ let ErrorTypes = /*#__PURE__*/function (ErrorTypes) { ErrorTypes["NETWORK_ERROR"] = "networkError"; ErrorTypes["MEDIA_ERROR"] = "mediaError"; ErrorTypes["KEY_SYSTEM_ERROR"] = "keySystemError"; ErrorTypes["MUX_ERROR"] = "muxError"; 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["MANIFEST_LOAD_ERROR"] = "manifestLoadError"; ErrorDetails["MANIFEST_LOAD_TIMEOUT"] = "manifestLoadTimeOut"; ErrorDetails["MANIFEST_PARSING_ERROR"] = "manifestParsingError"; ErrorDetails["MANIFEST_INCOMPATIBLE_CODECS_ERROR"] = "manifestIncompatibleCodecsError"; ErrorDetails["LEVEL_EMPTY_ERROR"] = "levelEmptyError"; ErrorDetails["LEVEL_LOAD_ERROR"] = "levelLoadError"; ErrorDetails["LEVEL_LOAD_TIMEOUT"] = "levelLoadTimeOut"; ErrorDetails["LEVEL_PARSING_ERROR"] = "levelParsingError"; ErrorDetails["LEVEL_SWITCH_ERROR"] = "levelSwitchError"; ErrorDetails["AUDIO_TRACK_LOAD_ERROR"] = "audioTrackLoadError"; ErrorDetails["AUDIO_TRACK_LOAD_TIMEOUT"] = "audioTrackLoadTimeOut"; ErrorDetails["SUBTITLE_LOAD_ERROR"] = "subtitleTrackLoadError"; ErrorDetails["SUBTITLE_TRACK_LOAD_TIMEOUT"] = "subtitleTrackLoadTimeOut"; ErrorDetails["FRAG_LOAD_ERROR"] = "fragLoadError"; ErrorDetails["FRAG_LOAD_TIMEOUT"] = "fragLoadTimeOut"; ErrorDetails["FRAG_DECRYPT_ERROR"] = "fragDecryptError"; ErrorDetails["FRAG_PARSING_ERROR"] = "fragParsingError"; ErrorDetails["FRAG_GAP"] = "fragGap"; ErrorDetails["REMUX_ALLOC_ERROR"] = "remuxAllocError"; ErrorDetails["KEY_LOAD_ERROR"] = "keyLoadError"; ErrorDetails["KEY_LOAD_TIMEOUT"] = "keyLoadTimeOut"; ErrorDetails["BUFFER_ADD_CODEC_ERROR"] = "bufferAddCodecError"; ErrorDetails["BUFFER_INCOMPATIBLE_CODECS_ERROR"] = "bufferIncompatibleCodecsError"; ErrorDetails["BUFFER_APPEND_ERROR"] = "bufferAppendError"; ErrorDetails["BUFFER_APPENDING_ERROR"] = "bufferAppendingError"; ErrorDetails["BUFFER_STALLED_ERROR"] = "bufferStalledError"; ErrorDetails["BUFFER_FULL_ERROR"] = "bufferFullError"; ErrorDetails["BUFFER_SEEK_OVER_HOLE"] = "bufferSeekOverHole"; ErrorDetails["BUFFER_NUDGE_ON_STALL"] = "bufferNudgeOnStall"; ErrorDetails["INTERNAL_EXCEPTION"] = "internalException"; ErrorDetails["INTERNAL_ABORTED"] = "aborted"; ErrorDetails["UNKNOWN"] = "unknown"; return ErrorDetails; }({}); const noop = function noop() {}; const fakeLogger = { trace: noop, debug: noop, log: noop, warn: noop, info: noop, error: noop }; let exportedLogger = 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) { const func = self.console[type]; if (func) { return func.bind(self.console, `[${type}] >`); } return noop; } function exportLoggerFunctions(debugConfig, ...functions) { functions.forEach(function (type) { exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type); }); } function enableLogs(debugConfig, id) { // check that console is available if (self.console && debugConfig === true || typeof debugConfig === 'object') { exportLoggerFunctions(debugConfig, // Remove out from list here to hard-disable a log-level // 'trace', 'debug', 'log', 'info', 'warn', 'error'); // Some browsers don't allow to use bind on console object anyway // fallback to default if needed try { exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.4.1"}`); } catch (e) { exportedLogger = fakeLogger; } } else { exportedLogger = fakeLogger; } } const logger = exportedLogger; const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/; const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g; // adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js class AttrList { constructor(attrs) { if (typeof attrs === 'string') { attrs = AttrList.parseAttrList(attrs); } for (const attr in attrs) { if (attrs.hasOwnProperty(attr)) { if (attr.substring(0, 2) === 'X-') { this.clientAttrs = this.clientAttrs || []; this.clientAttrs.push(attr); } this[attr] = attrs[attr]; } } } decimalInteger(attrName) { const intValue = parseInt(this[attrName], 10); if (intValue > Number.MAX_SAFE_INTEGER) { return Infinity; } return intValue; } hexadecimalInteger(attrName) { if (this[attrName]) { let stringValue = (this[attrName] || '0x').slice(2); stringValue = (stringValue.length & 1 ? '0' : '') + stringValue; const value = new Uint8Array(stringValue.length / 2); for (let i = 0; i < stringValue.length / 2; i++) { value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16); } return value; } else { return null; } } hexadecimalIntegerAsNumber(attrName) { const intValue = parseInt(this[attrName], 16); if (intValue > Number.MAX_SAFE_INTEGER) { return Infinity; } return intValue; } decimalFloatingPoint(attrName) { return parseFloat(this[attrName]); } optionalFloat(attrName, defaultValue) { const value = this[attrName]; return value ? parseFloat(value) : defaultValue; } enumeratedString(attrName) { return this[attrName]; } bool(attrName) { return this[attrName] === 'YES'; } decimalResolution(attrName) { const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]); if (res === null) { return undefined; } return { width: parseInt(res[1], 10), height: parseInt(res[2], 10) }; } static parseAttrList(input) { let match; const attrs = {}; const quote = '"'; ATTR_LIST_REGEX.lastIndex = 0; while ((match = ATTR_LIST_REGEX.exec(input)) !== null) { let value = match[2]; if (value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1) { value = value.slice(1, -1); } const name = match[1].trim(); attrs[name] = value; } return attrs; } } // Avoid exporting const enum so that these values can be inlined function isDateRangeCueAttribute(attrName) { return attrName !== "ID" && attrName !== "CLASS" && attrName !== "START-DATE" && attrName !== "DURATION" && attrName !== "END-DATE" && attrName !== "END-ON-NEXT"; } function isSCTE35Attribute(attrName) { return attrName === "SCTE35-OUT" || attrName === "SCTE35-IN"; } class DateRange { constructor(dateRangeAttr, dateRangeWithSameId) { this.attr = void 0; this._startDate = void 0; this._endDate = void 0; this._badValueForSameId = void 0; if (dateRangeWithSameId) { const previousAttr = dateRangeWithSameId.attr; for (const key in previousAttr) { if (Object.prototype.hasOwnProperty.call(dateRangeAttr, key) && dateRangeAttr[key] !== previousAttr[key]) { logger.warn(`DATERANGE tag attribute: "${key}" does not match for tags with ID: "${dateRangeAttr.ID}"`); this._badValueForSameId = key; break; } } // Merge DateRange tags with the same ID dateRangeAttr = _extends(new AttrList({}), previousAttr, dateRangeAttr); } this.attr = dateRangeAttr; this._startDate = new Date(dateRangeAttr["START-DATE"]); if ("END-DATE" in this.attr) { const endDate = new Date(this.attr["END-DATE"]); if (isFiniteNumber(endDate.getTime())) { this._endDate = endDate; } } } get id() { return this.attr.ID; } get class() { return this.attr.CLASS; } get startDate() { return this._startDate; } get endDate() { if (this._endDate) { return this._endDate; } const duration = this.duration; if (duration !== null) { return new Date(this._startDate.getTime() + duration * 1000); } return null; } get duration() { if ("DURATION" in this.attr) { const duration = this.attr.decimalFloatingPoint("DURATION"); if (isFiniteNumber(duration)) { return duration; } } else if (this._endDate) { return (this._endDate.getTime() - this._startDate.getTime()) / 1000; } return null; } get plannedDuration() { if ("PLANNED-DURATION" in this.attr) { return this.attr.decimalFloatingPoint("PLANNED-DURATION"); } return null; } get endOnNext() { return this.attr.bool("END-ON-NEXT"); } get isValid() { return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class); } } 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 { // baseurl is the URL to the playlist // relurl is the portion of the URL that comes from inside the playlist. // Holds the types of data this fragment supports constructor(baseurl) { this._byteRange = null; this._url = null; this.baseurl = void 0; this.relurl = void 0; this.elementaryStreams = { [ElementaryStreamTypes.AUDIO]: null, [ElementaryStreamTypes.VIDEO]: null, [ElementaryStreamTypes.AUDIOVIDEO]: null }; this.baseurl = baseurl; } // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array setByteRange(value, previous) { const params = value.split('@', 2); const byteRange = []; if (params.length === 1) { byteRange[0] = previous ? previous.byteRangeEndOffset : 0; } else { byteRange[0] = parseInt(params[1]); } byteRange[1] = parseInt(params[0]) + byteRange[0]; this._byteRange = byteRange; } get byteRange() { if (!this._byteRange) { return []; } return this._byteRange; } get byteRangeStartOffset() { return this.byteRange[0]; } get byteRangeEndOffset() { return this.byteRange[1]; } 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; } } /** * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}. */ class Fragment extends BaseSegment { // EXTINF has to be present for a m3u8 to be considered valid // sn notates the sequence number for a segment, and if set to a string can be 'initSegment' // 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 // A string representing the fragment type // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading // A reference to the key loader. Set while the key is loading, and removed afterwards. Used to abort key loading // The level/track index to which the fragment belongs // The continuity counter of the fragment // The starting Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete. // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete. // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete. // The start time of the fragment, as listed in the manifest. Updated after transmux complete. // Set by `updateFragPTSDTS` in level-helper // The maximum starting Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. // The minimum ending Presentation Time Stamp (audio/video PTS) of the fragment. Set after transmux complete. // Load/parse timing information // A flag indicating whether the segment was downloaded in order to test bitrate, and was not buffered // #EXTINF segment title // The Media Initialization Section for this segment // Fragment is the last fragment in the media playlist // Fragment is marked by an EXT-X-GAP tag indicating that it does not contain media data and should not be loaded constructor(type, baseurl) { super(baseurl); this._decryptdata = null; this.rawProgramDateTime = null; this.programDateTime = null; this.tagList = []; this.duration = 0; this.sn = 0; this.levelkeys = void 0; this.type = void 0; this.loader = null; this.keyLoader = null; this.level = -1; this.cc = 0; this.startPTS = void 0; this.endPTS = void 0; this.startDTS = void 0; this.endDTS = void 0; this.start = 0; this.deltaPTS = void 0; this.maxStartPTS = void 0; this.minEndPTS = void 0; this.stats = new LoadStats(); this.urlId = 0; this.data = void 0; this.bitrateTest = false; this.title = null; this.initSegment = null; this.endList = void 0; this.gap = void 0; this.type = type; } get decryptdata() { const { levelkeys } = this; if (!levelkeys && !this._decryptdata) { return null; } if (!this._decryptdata && this.levelkeys && !this.levelkeys.NONE) { const key = this.levelkeys.identity; if (key) { this._decryptdata = key.getDecryptData(this.sn); } else { const keyFormats = Object.keys(this.levelkeys); if (keyFormats.length === 1) { return this._decryptdata = this.levelkeys[keyFormats[0]].getDecryptData(this.sn); } } } return this._decryptdata; } get end() { return this.start + this.duration; } get endProgramDateTime() { if (this.programDateTime === null) { return null; } if (!isFiniteNumber(this.programDateTime)) { return null; } const duration = !isFiniteNumber(this.duration) ? 0 : this.duration; return this.programDateTime + duration * 1000; } get encrypted() { var _this$_decryptdata; // 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$_decryptdata = this._decryptdata) != null && _this$_decryptdata.encrypted) { return true; } else if (this.levelkeys) { const keyFormats = Object.keys(this.levelkeys); const len = keyFormats.length; if (len > 1 || len === 1 && this.levelkeys[keyFormats[0]].encrypted) { return true; } } return false; } setKeyFormat(keyFormat) { if (this.levelkeys) { const key = this.levelkeys[keyFormat]; if (key && !this._decryptdata) { this._decryptdata = key.getDecryptData(this.sn); } } } abortRequests() { var _this$loader, _this$keyLoader; (_this$loader = this.loader) == null ? void 0 : _this$loader.abort(); (_this$keyLoader = this.keyLoader) == null ? void 0 : _this$keyLoader.abort(); } setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial = false) { const { elementaryStreams } = this; const info = elementaryStreams[type]; if (!info) { elementaryStreams[type] = { startPTS, endPTS, startDTS, endDTS, partial }; return; } info.startPTS = Math.min(info.startPTS, startPTS); info.endPTS = Math.max(info.endPTS, endPTS); info.startDTS = Math.min(info.startDTS, startDTS); info.endDTS = Math.max(info.endDTS, endDTS); } clearElementaryStreamInfo() { const { elementaryStreams } = this; elementaryStreams[ElementaryStreamTypes.AUDIO] = null; elementaryStreams[ElementaryStreamTypes.VIDEO] = null; elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null; } } /** * Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}. */ class Part extends BaseSegment { constructor(partAttrs, frag, baseurl, index, previous) { super(baseurl); this.fragOffset = 0; this.duration = 0; this.gap = false; this.independent = false; this.relurl = void 0; this.fragment = void 0; this.index = void 0; this.stats = new LoadStats(); this.duration = partAttrs.decimalFloatingPoint('DURATION'); this.gap = partAttrs.bool('GAP'); this.independent = partAttrs.bool('INDEPENDENT'); this.relurl = partAttrs.enumeratedString('URI'); this.fragment = frag; this.index = index; const byteRange = partAttrs.enumeratedString('BYTERANGE'); if (byteRange) { this.setByteRange(byteRange, previous); } if (previous) { this.fragOffset = previous.fragOffset + previous.duration; } } get start() { return this.fragment.start + this.fragOffset; } get end() { return this.start + this.duration; } get loaded() { const { elementaryStreams } = this; return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo); } } const DEFAULT_TARGET_DURATION = 10; /** * Object representing parsed data from an HLS Media Playlist. Found in {@link hls.js#Level.details}. */ class LevelDetails { // Manifest reload synchronization constructor(baseUrl) { this.PTSKnown = false; this.alignedSliding = false; this.averagetargetduration = void 0; this.endCC = 0; this.endSN = 0; this.fragments = void 0; this.fragmentHint = void 0; this.partList = null; this.dateRanges = void 0; this.live = true; this.ageHeader = 0; this.advancedDateTime = void 0; this.updated = true; this.advanced = true; this.availabilityDelay = void 0; this.misses = 0; this.startCC = 0; this.startSN = 0; this.startTimeOffset = null; this.targetduration = 0; this.totalduration = 0; this.type = null; this.url = void 0; this.m3u8 = ''; this.version = null; this.canBlockReload = false; this.canSkipUntil = 0; this.canSkipDateRanges = false; this.skippedSegments = 0; this.recentlyRemovedDateranges = void 0; this.partHoldBack = 0; this.holdBack = 0; this.partTarget = 0; this.preloadHint = void 0; this.renditionReports = void 0; this.tuneInGoal = 0; this.deltaUpdateFailed = void 0; this.driftStartTime = 0; this.driftEndTime = 0; this.driftStart = 0; this.driftEnd = 0; this.encryptedFragments = void 0; this.playlistParsingError = null; this.variableList = null; this.hasVariableRefs = false; this.fragments = []; this.encryptedFragments = []; this.dateRanges = {}; this.url = baseUrl; } reloaded(previous) { if (!previous) { this.advanced = true; this.updated = true; return; } const partSnDiff = this.lastPartSn - previous.lastPartSn; const partIndexDiff = this.lastPartIndex - previous.lastPartIndex; this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff; this.advanced = this.endSN > previous.endSN || partSnDiff > 0 || partSnDiff === 0 && partIndexDiff > 0; if (this.updated || this.advanced) { this.misses = Math.floor(previous.misses * 0.6); } else { this.misses = previous.misses + 1; } this.availabilityDelay = previous.availabilityDelay; } get hasProgramDateTime() { if (this.fragments.length) { return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime); } return false; } get levelTargetDuration() { return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION; } get drift() { const runTime = this.driftEndTime - this.driftStartTime; if (runTime > 0) { const runDuration = this.driftEnd - this.driftStart; return runDuration * 1000 / runTime; } return 1; } get edge() { return this.partEnd || this.fragmentEnd; } get partEnd() { var _this$partList; if ((_this$partList = this.partList) != null && _this$partList.length) { return this.partList[this.partList.length - 1].end; } return this.fragmentEnd; } get fragmentEnd() { var _this$fragments; if ((_this$fragments = this.fragments) != null && _this$fragments.length) { return this.fragments[this.fragments.length - 1].end; } return 0; } get age() { if (this.advancedDateTime) { return Math.max(Date.now() - this.advancedDateTime, 0) / 1000; } return 0; } get lastPartIndex() { var _this$partList2; if ((_this$partList2 = this.partList) != null && _this$partList2.length) { return this.partList[this.partList.length - 1].index; } return -1; } get lastPartSn() { var _this$partList3; if ((_this$partList3 = this.partList) != null && _this$partList3.length) { return this.partList[this.partList.length - 1].fragment.sn; } return this.endSN; } } function base64Decode(base64encodedStr) { return Uint8Array.from(atob(base64encodedStr), c => c.charCodeAt(0)); } function getKeyIdBytes(str) { const keyIdbytes = strToUtf8array(str).subarray(0, 16); const paddedkeyIdbytes = new Uint8Array(16); paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length); return paddedkeyIdbytes; } function changeEndianness(keyId) { const swap = function swap(array, from, to) { const cur = array[from]; array[from] = array[to]; array[to] = cur; }; swap(keyId, 0, 3); swap(keyId, 1, 2); swap(keyId, 4, 5); swap(keyId, 6, 7); } function convertDataUriToArrayBytes(uri) { // data:[<media type][;attribute=value][;base64],<data> const colonsplit = uri.split(':'); let keydata = null; if (colonsplit[0] === 'data' && colonsplit.length === 2) { const semicolonsplit = colonsplit[1].split(';'); const commasplit = semicolonsplit[semicolonsplit.length - 1].split(','); if (commasplit.length === 2) { const isbase64 = commasplit[0] === 'base64'; const data = commasplit[1]; if (isbase64) { semicolonsplit.splice(-1, 1); // remove from processing keydata = base64Decode(data); } else { keydata = getKeyIdBytes(data); } } } return keydata; } function strToUtf8array(str) { return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0)); } /** * @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/requestMediaKeySystemAccess */ var KeySystems = { CLEARKEY: "org.w3.clearkey", FAIRPLAY: "com.apple.fps", PLAYREADY: "com.microsoft.playready", WIDEVINE: "com.widevine.alpha" }; // Playlist #EXT-X-KEY KEYFORMAT values var KeySystemFormats = { CLEARKEY: "org.w3.clearkey", FAIRPLAY: "com.apple.streamingkeydelivery", PLAYREADY: "com.microsoft.playready", WIDEVINE: "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" }; function keySystemFormatToKeySystemDomain(format) { switch (format) { case KeySystemFormats.FAIRPLAY: return KeySystems.FAIRPLAY; case KeySystemFormats.PLAYREADY: return KeySystems.PLAYREADY; case KeySystemFormats.WIDEVINE: return KeySystems.WIDEVINE; case KeySystemFormats.CLEARKEY: return KeySystems.CLEARKEY; } } // System IDs for which we can extract a key ID from "encrypted" event PSSH var KeySystemIds = { WIDEVINE: "edef8ba979d64acea3c827dcd51d21ed" }; function keySystemIdToKeySystemDomain(systemId) { if (systemId === KeySystemIds.WIDEVINE) { return KeySystems.WIDEVINE; // } else if (systemId === KeySystemIds.PLAYREADY) { // return KeySystems.PLAYREADY; // } else if (systemId === KeySystemIds.CENC || systemId === KeySystemIds.CLEARKEY) { // return KeySystems.CLEARKEY; } } function keySystemDomainToKeySystemFormat(keySystem) { switch (keySystem) { case KeySystems.FAIRPLAY: return KeySystemFormats.FAIRPLAY; case KeySystems.PLAYREADY: return KeySystemFormats.PLAYREADY; case KeySystems.WIDEVINE: return KeySystemFormats.WIDEVINE; case KeySystems.CLEARKEY: return KeySystemFormats.CLEARKEY; } } function getKeySystemsForConfig(config) { const { drmSystems, widevineLicenseUrl } = config; const keySystemsToAttempt = drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter(keySystem => !!drmSystems[keySystem]) : []; if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) { keySystemsToAttempt.push(KeySystems.WIDEVINE); } return keySystemsToAttempt; } const requestMediaKeySystemAccess = function () { if (typeof self !== 'undefined' && self.navigator && self.navigator.requestMediaKeySystemAccess) { return self.navigator.requestMediaKeySystemAccess.bind(self.navigator); } else { return null; } }(); /** * @see https://developer.mozilla.org/en-US/docs/Web/API/MediaKeySystemConfiguration */ function getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, drmSystemOptions) { let initDataTypes; switch (keySystem) { case KeySystems.FAIRPLAY: initDataTypes = ['cenc', 'sinf']; break; case KeySystems.WIDEVINE: case KeySystems.PLAYREADY: initDataTypes = ['cenc']; break; case KeySystems.CLEARKEY: initDataTypes = ['cenc', 'keyids']; break; default: throw new Error(`Unknown key-system: ${keySystem}`); } return createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions); } function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCodecs, drmSystemOptions) { const baseConfig = { initDataTypes: initDataTypes, persistentState: drmSystemOptions.persistentState || 'not-allowed', distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'not-allowed', sessionTypes: drmSystemOptions.sessionTypes || [drmSystemOptions.sessionType || 'temporary'], audioCapabilities: audioCodecs.map(codec => ({ contentType: `audio/mp4; codecs="${codec}"`, robustness: drmSystemOptions.audioRobustness || '', encryptionScheme: drmSystemOptions.audioEncryptionScheme || null })), videoCapabilities: videoCodecs.map(codec => ({ contentType: `video/mp4; codecs="${codec}"`, robustness: drmSystemOptions.videoRobustness || '', encryptionScheme: drmSystemOptions.videoEncryptionScheme || null })) }; return [baseConfig]; } function sliceUint8(array, start, end) { // @ts-expect-error This polyfills IE11 usage of Uint8Array slice. // It always exists in the TypeScript definition so fails, but it fails at runtime on IE11. return Uint8Array.prototype.slice ? array.slice(start, end) : new Uint8Array(Array.prototype.slice.call(array, start, end)); } // breaking up those two types in order to clarify what is happening in the decoding path. /** * Returns true if an ID3 header can be found at offset in data * @param data - The data to search * @param offset - The offset at which to start searching */ const isHeader$2 = (data, offset) => { /* * http://id3.org/id3v2.3.0 * [0] = 'I' * [1] = 'D' * [2] = '3' * [3,4] = {Version} * [5] = {Flags} * [6-9] = {ID3 Size} * * An ID3v2 tag can be detected with the following pattern: * $49 44 33 yy yy xx zz zz zz zz * Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80 */ if (offset + 10 <= data.length) { // look for 'ID3' identifier if (data[offset] === 0x49 && data[offset + 1] === 0x44 && data[offset + 2] === 0x33) { // check version is within range if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { // check size is within range if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) { return true; } } } } return false; }; /** * Returns true if an ID3 footer can be found at offset in data * @param data - The data to search * @param offset - The offset at which to start searching */ const isFooter = (data, offset) => { /* * The footer is a copy of the header, but with a different identifier */ if (offset + 10 <= data.length) { // look for '3DI' identifier if (data[offset] === 0x33 && data[offset + 1] === 0x44 && data[offset + 2] === 0x49) { // check version is within range if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) { // check size is within range if (data[offset + 6] < 0x80 && data[offset + 7] < 0x80 && data[offset + 8] < 0x80 && data[offset + 9] < 0x80) { return true; } } } } return false; }; /** * Returns any adjacent ID3 tags found in data starting at offset, as one block of data * @param data - The data to search in * @param offset - The offset at which to start searching * @returns the block of data containing any ID3 tags found * or *undefined* if no header is found at the starting offset */ const getID3Data = (data, offset) => { const front = offset; let length = 0; while (isHeader$2(data, offset)) { // ID3 header is 10 bytes length += 10; const size = readSize(data, offset + 6); length += size; if (isFooter(data, offset + 10)) { // ID3 footer is 10 bytes length += 10; } offset += length; } if (length > 0) { return data.subarray(front, front + length); } return undefined; }; const readSize = (data, offset) => { let size = 0; size = (data[offset] & 0x7f) << 21; size |= (data[offset + 1] & 0x7f) << 14; size |= (data[offset + 2] & 0x7f) << 7; size |= data[offset + 3] & 0x7f; return size; }; const canParse$2 = (data, offset) => { return isHeader$2(data, offset) && readSize(data, offset + 6) + 10 <= data.length - offset; }; /** * Searches for the Elementary Stream timestamp found in the ID3 data chunk * @param data - Block of data containing one or more ID3 tags */ const getTimeStamp = data => { const frames = getID3Frames(data); for (let i = 0; i < frames.length; i++) { const frame = frames[i]; if (isTimeStampFrame(frame)) { return readTimeStamp(frame); } } return undefined; }; /** * Returns true if the ID3 frame is an Elementary Stream timestamp frame */ const isTimeStampFrame = frame => { return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp'; }; const getFrameData = data => { /* Frame ID $xx xx xx xx (four characters) Size $xx xx xx xx Flags $xx xx */ const type = String.fromCharCode(data[0], data[1], data[2], data[3]); const size = readSize(data, 4); // skip frame id, size, and flags const offset = 10; return { type, size, data: data.subarray(offset, offset + size) }; }; /** * Returns an array of ID3 frames found in all the ID3 tags in the id3Data * @param id3Data - The ID3 data containing one or more ID3 tags */ const getID3Frames = id3Data => { let offset = 0; const frames = []; while (isHeader$2(id3Data, offset)) { const size = readSize(id3Data, offset + 6); // skip past ID3 header offset += 10; const end = offset + size; // loop through frames in the ID3 tag while (offset + 8 < end) { const frameData = getFrameData(id3Data.subarray(offset)); const frame = decodeFrame(frameData); if (frame) { frames.push(frame); } // skip frame header and frame data offset += frameData.size + 10; } if (isFooter(id3Data, offset)) { offset += 10; } } return frames; }; const decodeFrame = frame => { if (frame.type === 'PRIV') { return decodePrivFrame(frame); } else if (frame.type[0] === 'W') { return decodeURLFrame(frame); } return decodeTextFrame(frame); }; const decodePrivFrame = frame => { /* Format: <text string>\0<binary data> */ if (frame.size < 2) { return undefined; } const owner = utf8ArrayToStr(frame.data, true); const privateData = new Uint8Array(frame.data.subarray(owner.length + 1)); return { key: frame.type, info: owner, data: privateData.buffer }; }; const decodeTextFrame = frame => { if (frame.size < 2) { return undefined; } if (frame.type === 'TXXX') { /* Format: [0] = {Text Encoding} [1-?] = {Description}\0{Value} */ let index = 1; const description = utf8ArrayToStr(frame.data.subarray(index), true); index += description.length + 1; const value = utf8ArrayToStr(frame.data.subarray(index)); return { key: frame.type, info: description, data: value }; } /* Format: [0] = {Text Encoding} [1-?] = {Value} */ const text = utf8ArrayToStr(frame.data.subarray(1)); return { key: frame.type, data: text }; }; const decodeURLFrame = frame => { if (frame.type === 'WXXX') { /* Format: [0] = {Text Encoding} [1-?] = {Description}\0{URL} */ if (frame.size < 2) { return undefined; } let index = 1; const description = utf8ArrayToStr(frame.data.subarray(index), true); index += description.length + 1; const value = utf8ArrayToStr(frame.data.subarray(index)); return { key: frame.type, info: description, data: value }; } /* Format: [0-?] = {URL} */ const url = utf8ArrayToStr(frame.data); return { key: frame.type, data: url }; }; const readTimeStamp = timeStampFrame => { if (timeStampFrame.data.byteLength === 8) { const data = new Uint8Array(timeStampFrame.data); // timestamp is 33 bit expressed as a big-endian eight-octet number, // with the upper 31 bits set to zero. const pts33Bit = data[3] & 0x1; let timestamp = (data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7]; timestamp /= 45; if (pts33Bit) { timestamp += 47721858.84; } // 2^32 / 90 return Math.round(timestamp); } return undefined; }; // 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. */ const utf8ArrayToStr = (array, exitOnNull = false) => { const decoder = getTextDecoder(); if (decoder) { 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; }; let decoder; function getTextDecoder() { if (!decoder && typeof self.TextDecoder !== 'undefined') { decoder = new self.TextDecoder('utf-8'); } return decoder; } /** * hex du