From 5eeb6088f77d44c1ffb5a097de7923e380526b43 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 1 Oct 2024 21:25:11 +0800 Subject: [PATCH] HLS video improvements --- .../AccountContext/Sources/MediaManager.swift | 1 + .../Sources/UniversalVideoNode.swift | 29 +- .../Items/UniversalVideoGalleryItem.swift | 112 +- .../Resources/WebEmbed/HLSVideoPlayer.html | 32 +- .../TelegramUI/Resources/WebEmbed/hls.js | 29292 +--------------- .../Sources/HLSVideoAVContentNode.swift | 549 + .../Sources/HLSVideoContent.swift | 1363 +- .../Sources/HLSVideoJSContentNode.swift | 777 + .../Sources/NativeVideoContent.swift | 11 + .../Sources/PlatformVideoContent.swift | 11 + .../Sources/SystemVideoContent.swift | 11 + .../UniversalVideoContentManager.swift | 57 +- .../Sources/WebEmbedPlayerNode.swift | 7 + .../Sources/WebEmbedVideoContent.swift | 11 + 14 files changed, 1676 insertions(+), 30587 deletions(-) create mode 100644 submodules/TelegramUniversalVideoContent/Sources/HLSVideoAVContentNode.swift create mode 100644 submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSContentNode.swift diff --git a/submodules/AccountContext/Sources/MediaManager.swift b/submodules/AccountContext/Sources/MediaManager.swift index 092fc4c52b..a0e4ecc34a 100644 --- a/submodules/AccountContext/Sources/MediaManager.swift +++ b/submodules/AccountContext/Sources/MediaManager.swift @@ -202,6 +202,7 @@ public protocol UniversalVideoManager: AnyObject { func removePlaybackCompleted(id: AnyHashable, index: Int) func statusSignal(content: UniversalVideoContent) -> Signal func bufferingStatusSignal(content: UniversalVideoContent) -> Signal<(RangeSet, Int64)?, NoError> + func isNativePictureInPictureActiveSignal(content: UniversalVideoContent) -> Signal } public enum AudioRecordingState: Equatable { diff --git a/submodules/AccountContext/Sources/UniversalVideoNode.swift b/submodules/AccountContext/Sources/UniversalVideoNode.swift index 6eae9e3949..8ff0bf498f 100644 --- a/submodules/AccountContext/Sources/UniversalVideoNode.swift +++ b/submodules/AccountContext/Sources/UniversalVideoNode.swift @@ -19,6 +19,7 @@ public protocol UniversalVideoContentNode: AnyObject { var ready: Signal { get } var status: Signal { get } var bufferingStatus: Signal<(RangeSet, Int64)?, NoError> { get } + var isNativePictureInPictureActive: Signal { get } func updateLayout(size: CGSize, actualSize: CGSize, transition: ContainedViewLayoutTransition) @@ -41,6 +42,8 @@ public protocol UniversalVideoContentNode: AnyObject { func fetchControl(_ control: UniversalVideoNodeFetchControl) func notifyPlaybackControlsHidden(_ hidden: Bool) func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) + func enterNativePictureInPicture() -> Bool + func exitNativePictureInPicture() } public protocol UniversalVideoContent { @@ -100,7 +103,7 @@ public final class UniversalVideoNode: ASDisplayNode { private let autoplay: Bool private let snapshotContentWhenGone: Bool - private var contentNode: (UniversalVideoContentNode & ASDisplayNode)? + private(set) var contentNode: (UniversalVideoContentNode & ASDisplayNode)? private var contentNodeId: Int32? private var playbackCompletedIndex: Int? @@ -125,6 +128,11 @@ public final class UniversalVideoNode: ASDisplayNode { return self._bufferingStatus.get() } + private let _isNativePictureInPictureActive = Promise() + public var isNativePictureInPictureActive: Signal { + return self._isNativePictureInPictureActive.get() + } + private let _ready = Promise() public var ready: Signal { return self._ready.get() @@ -181,6 +189,7 @@ public final class UniversalVideoNode: ASDisplayNode { self._status.set(self.manager.statusSignal(content: self.content)) self._bufferingStatus.set(self.manager.bufferingStatusSignal(content: self.content)) + self._isNativePictureInPictureActive.set(self.manager.isNativePictureInPictureActiveSignal(content: self.content)) self.decoration.setStatus(self.status) @@ -418,4 +427,22 @@ public final class UniversalVideoNode: ASDisplayNode { } }) } + + public func enterNativePictureInPicture() -> Bool { + var result = false + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + result = contentNode.enterNativePictureInPicture() + } + }) + return result + } + + public func exitNativePictureInPicture() { + self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in + if let contentNode = contentNode { + contentNode.exitNativePictureInPicture() + } + }) + } } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index c30a72c7a8..9f95f5e933 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -604,6 +604,8 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte private var hiddenMediaManagerIndex: Int? private var messageRemovedDisposable: Disposable? + + private var isNativePictureInPictureActiveDisposable: Disposable? init(context: AccountContext, overlayController: OverlayMediaController, mediaManager: MediaManager, accountId: AccountRecordId, hiddenMedia: (MessageId, Media)?, videoNode: UniversalVideoNode, canSkip: Bool, willBegin: @escaping (PictureInPictureContentImpl) -> Void, didEnd: @escaping (PictureInPictureContentImpl) -> Void, expand: @escaping (@escaping () -> Void) -> Void) { self.overlayController = overlayController @@ -617,30 +619,84 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte super.init() - let contentDelegate = PlaybackDelegate(node: self.node) - self.contentDelegate = contentDelegate + if let videoLayer = videoNode.getVideoLayer() { + let contentDelegate = PlaybackDelegate(node: self.node) + self.contentDelegate = contentDelegate + + let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoLayer, playbackDelegate: contentDelegate)) + self.pictureInPictureController = pictureInPictureController + contentDelegate.pictureInPictureController = pictureInPictureController + + pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = false + pictureInPictureController.requiresLinearPlayback = !canSkip + pictureInPictureController.delegate = self + self.pictureInPictureController = pictureInPictureController + let timer = SwiftSignalKit.Timer(timeout: 0.005, repeat: true, completion: { [weak self] in + guard let strongSelf = self, let pictureInPictureController = strongSelf.pictureInPictureController else { + return + } + if pictureInPictureController.isPictureInPicturePossible { + strongSelf.pictureInPictureTimer?.invalidate() + strongSelf.pictureInPictureTimer = nil + + pictureInPictureController.startPictureInPicture() + } + }, queue: .mainQueue()) + self.pictureInPictureTimer = timer + timer.start() + } else { + var currentIsNativePictureInPictureActive = false + self.isNativePictureInPictureActiveDisposable = (videoNode.isNativePictureInPictureActive + |> deliverOnMainQueue).startStrict(next: { [weak self] isNativePictureInPictureActive in + guard let self else { + return + } + + if currentIsNativePictureInPictureActive == isNativePictureInPictureActive { + return + } + currentIsNativePictureInPictureActive = isNativePictureInPictureActive + + if isNativePictureInPictureActive { + Queue.mainQueue().after(0.0, { [weak self] in + guard let self else { + return + } + self.willBegin(self) + + if let overlayController = self.overlayController { + overlayController.setPictureInPictureContentHidden(content: self, isHidden: true) + } + + self.didEnd(self) + }) + } else { + self.expand { [weak self] in + guard let self else { + return + } - let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoNode.getVideoLayer()!, playbackDelegate: contentDelegate)) - self.pictureInPictureController = pictureInPictureController - contentDelegate.pictureInPictureController = pictureInPictureController - - pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = false - pictureInPictureController.requiresLinearPlayback = !canSkip - pictureInPictureController.delegate = self - self.pictureInPictureController = pictureInPictureController - let timer = SwiftSignalKit.Timer(timeout: 0.005, repeat: true, completion: { [weak self] in - guard let strongSelf = self, let pictureInPictureController = strongSelf.pictureInPictureController else { - return - } - if pictureInPictureController.isPictureInPicturePossible { - strongSelf.pictureInPictureTimer?.invalidate() - strongSelf.pictureInPictureTimer = nil + self.didExpand = true - pictureInPictureController.startPictureInPicture() - } - }, queue: .mainQueue()) - self.pictureInPictureTimer = timer - timer.start() + if let overlayController = self.overlayController { + overlayController.setPictureInPictureContentHidden(content: self, isHidden: false) + self.node.alpha = 0.02 + } + + guard let overlayController = self.overlayController else { + return + } + overlayController.removePictureInPictureContent(content: self) + self.node.canAttachContent = false + if self.didExpand { + return + } + self.node.continuePlayingWithoutSound() + } + } + }) + let _ = videoNode.enterNativePictureInPicture() + } if let hiddenMedia = hiddenMedia { self.hiddenMediaManagerIndex = mediaManager.galleryHiddenMediaManager.addSource(Signal<(MessageId, Media)?, NoError>.single(hiddenMedia) @@ -676,6 +732,7 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte deinit { self.messageRemovedDisposable?.dispose() + self.isNativePictureInPictureActiveDisposable?.dispose() self.pictureInPictureTimer?.invalidate() self.node.setCanPlaybackWithoutHierarchy(false) @@ -743,10 +800,6 @@ private final class PictureInPictureContentImpl: NSObject, PictureInPictureConte } completionHandler(true) - - /*Queue.mainQueue().after(0.2, { - self?.node.canAttachContent = false - })*/ } } } @@ -1295,7 +1348,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - self.moreButtonStateDisposable.set(combineLatest(queue: .mainQueue(), + /*self.moreButtonStateDisposable.set(combineLatest(queue: .mainQueue(), self.playbackRatePromise.get(), self.isShowingContextMenuPromise.get() ).start(next: { [weak self] playbackRate, isShowingContextMenu in @@ -1334,7 +1387,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { strongSelf.moreBarButtonRateTimestamp = CFAbsoluteTimeGetCurrent() } } - })) + }))*/ self.statusDisposable.set((combineLatest(queue: .mainQueue(), videoNode.status, mediaFileStatus) |> deliverOnMainQueue).start(next: { [weak self] value, fetchStatus in @@ -2306,6 +2359,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { let playbackRate = self.playbackRate if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported(), isNativePictureInPictureSupported { + self.disablePictureInPicturePlaceholder = true let overlayVideoNode = UniversalVideoNode(accountId: self.context.account.id, postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .overlay) @@ -2838,7 +2892,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } - if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() && !item.peerIsCopyProtected && message.paidContent == nil { + if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() && !item.peerIsCopyProtected && message.paidContent == nil && !(item.content is HLSVideoContent) { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in f(.default) diff --git a/submodules/TelegramUI/Resources/WebEmbed/HLSVideoPlayer.html b/submodules/TelegramUI/Resources/WebEmbed/HLSVideoPlayer.html index b7700a02bd..26a5e4e76c 100755 --- a/submodules/TelegramUI/Resources/WebEmbed/HLSVideoPlayer.html +++ b/submodules/TelegramUI/Resources/WebEmbed/HLSVideoPlayer.html @@ -28,6 +28,7 @@ var isManifestParsed = false; var isFirstFrameReady = false; + var isPictureInPictureActive = false; var currentTimeUpdateTimeout = null; @@ -52,12 +53,22 @@ video.addEventListener("waiting", function() { refreshPlayerStatus(); }); + video.addEventListener("enterpictureinpicture", function() { + isPictureInPictureActive = true; + refreshPlayerStatus(); + }, false); + video.addEventListener("leavepictureinpicture", function() { + isPictureInPictureActive = false; + refreshPlayerStatus(); + }, false); + hls = new Hls({ startLevel: 0, testBandwidth: false, debug: params['debug'], - autoStartLoad: false + autoStartLoad: false, + abrEwmaDefaultEstimate: params['bandwidthEstimate'] }); hls.on(Hls.Events.MANIFEST_PARSED, function() { isManifestParsed = true; @@ -109,6 +120,19 @@ video.muted = value; } + function playerRequestPictureInPicture() { + if (video !== document.pictureInPictureElement) { + video.requestPictureInPicture().then(function() { + isPictureInPictureActive = true; + refreshPlayerStatus(); + }); + } + } + + function playerStopPictureInPicture() { + document.exitPictureInPicture(); + } + function getLevels() { var levels = []; for (var i = 0; i < hls.levels.length; i++) { @@ -136,7 +160,8 @@ 'rate': isPlaying ? video.playbackRate : 0.0, 'defaultRate': video.playbackRate, 'levels': getLevels(), - 'currentLevel': hls.currentLevel + 'currentLevel': hls.currentLevel, + 'isPictureInPictureActive': isPictureInPictureActive }); refreshPlayerCurrentTime(); @@ -157,7 +182,8 @@ function refreshPlayerCurrentTime() { postPlayerEvent('playerCurrentTime', { - 'value': video.currentTime + 'value': video.currentTime, + 'bandwidthEstimate': hls.bandwidthEstimate }); currentTimeUpdateTimeout = setTimeout(() => { refreshPlayerCurrentTime() diff --git a/submodules/TelegramUI/Resources/WebEmbed/hls.js b/submodules/TelegramUI/Resources/WebEmbed/hls.js index 7fa8484e65..b3ac6dd055 100644 --- a/submodules/TelegramUI/Resources/WebEmbed/hls.js +++ b/submodules/TelegramUI/Resources/WebEmbed/hls.js @@ -1,29290 +1,2 @@ -(function __HLS_WORKER_BUNDLE__(__IN_WORKER__){ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Hls = factory()); -})(this, (function () { 'use strict'; - - 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), !0).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 || "default"); - 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 : String(i); - } - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); - } - } - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - Object.defineProperty(Constructor, "prototype", { - writable: false - }); - return Constructor; - } - 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 _inheritsLoose(subClass, superClass) { - subClass.prototype = Object.create(superClass.prototype); - subClass.prototype.constructor = subClass; - _setPrototypeOf(subClass, superClass); - } - function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); - }; - return _getPrototypeOf(o); - } - function _setPrototypeOf(o, p) { - _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); - } - function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - try { - Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); - return true; - } catch (e) { - return false; - } - } - function _construct(Parent, args, Class) { - if (_isNativeReflectConstruct()) { - _construct = Reflect.construct.bind(); - } else { - _construct = function _construct(Parent, args, Class) { - var a = [null]; - a.push.apply(a, args); - var Constructor = Function.bind.apply(Parent, a); - var instance = new Constructor(); - if (Class) _setPrototypeOf(instance, Class.prototype); - return instance; - }; - } - return _construct.apply(null, arguments); - } - function _isNativeFunction(fn) { - try { - return Function.toString.call(fn).indexOf("[native code]") !== -1; - } catch (e) { - return typeof fn === "function"; - } - } - function _wrapNativeSuper(Class) { - var _cache = typeof Map === "function" ? new Map() : undefined; - _wrapNativeSuper = function _wrapNativeSuper(Class) { - if (Class === null || !_isNativeFunction(Class)) return Class; - if (typeof Class !== "function") { - throw new TypeError("Super expression must either be null or a function"); - } - if (typeof _cache !== "undefined") { - if (_cache.has(Class)) return _cache.get(Class); - _cache.set(Class, Wrapper); - } - function Wrapper() { - return _construct(Class, arguments, _getPrototypeOf(this).constructor); - } - Wrapper.prototype = Object.create(Class.prototype, { - constructor: { - value: Wrapper, - enumerable: false, - writable: true, - configurable: true - } - }); - return _setPrototypeOf(Wrapper, Class); - }; - return _wrapNativeSuper(Class); - } - function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - return self; - } - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); - } - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; - } - function _createForOfIteratorHelperLoose(o, allowArrayLike) { - var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; - if (it) return (it = it.call(o)).next.bind(it); - if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { - if (it) o = it; - var i = 0; - return function () { - if (i >= o.length) return { - done: true - }; - return { - done: false, - value: o[i++] - }; - }; - } - throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); - } - - 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 is non-empty, we skip to - // Step 7. Otherwise, the embedded URL inherits the - // (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 is non-empty, we skip to - // step 7; otherwise, it inherits the of the base - // URL (if any) and - if (!relativeParts.params) { - builtParts.params = baseParts.params; - // 5b) if the embedded URL's is non-empty, we skip to - // step 7; otherwise, it inherits the 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 "/../", where 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 "/..", where is a - // complete path segment not equal to "..", that - // "/.." 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; - - // https://caniuse.com/mdn-javascript_builtins_number_isfinite - var isFiniteNumber = Number.isFinite || function (value) { - return typeof value === 'number' && isFinite(value); - }; - - // https://caniuse.com/mdn-javascript_builtins_number_issafeinteger - var isSafeInteger = Number.isSafeInteger || function (value) { - return typeof value === 'number' && Math.abs(value) <= MAX_SAFE_INTEGER; - }; - var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; - - var 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["MAX_AUTO_LEVEL_UPDATED"] = "hlsMaxAutoLevelUpdated"; - 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"; - Events["STEERING_MANIFEST_LOADED"] = "hlsSteeringManifestLoaded"; - 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 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; - }({}); - var 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; - }({}); - - var noop = function noop() {}; - var fakeLogger = { - trace: noop, - debug: noop, - log: noop, - warn: noop, - info: noop, - error: noop - }; - var 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) { - var func = self.console[type]; - if (func) { - return func.bind(self.console, "[" + type + "] >"); - } - return noop; - } - function exportLoggerFunctions(debugConfig) { - for (var _len = arguments.length, functions = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - functions[_key - 1] = arguments[_key]; - } - functions.forEach(function (type) { - exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type); - }); - } - function enableLogs(debugConfig, id) { - // check that console is available - if (typeof console === 'object' && 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.5.14"); - } catch (e) { - exportedLogger = fakeLogger; - } - } else { - exportedLogger = fakeLogger; - } - } - var logger = exportedLogger; - - var DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/; - var ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g; - - // adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js - var AttrList = /*#__PURE__*/function () { - function AttrList(attrs) { - if (typeof attrs === 'string') { - attrs = AttrList.parseAttrList(attrs); - } - _extends(this, attrs); - } - var _proto = AttrList.prototype; - _proto.decimalInteger = function decimalInteger(attrName) { - var intValue = parseInt(this[attrName], 10); - if (intValue > Number.MAX_SAFE_INTEGER) { - return Infinity; - } - return intValue; - }; - _proto.hexadecimalInteger = function hexadecimalInteger(attrName) { - if (this[attrName]) { - var stringValue = (this[attrName] || '0x').slice(2); - stringValue = (stringValue.length & 1 ? '0' : '') + stringValue; - var value = new Uint8Array(stringValue.length / 2); - for (var i = 0; i < stringValue.length / 2; i++) { - value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16); - } - return value; - } else { - return null; - } - }; - _proto.hexadecimalIntegerAsNumber = function hexadecimalIntegerAsNumber(attrName) { - var intValue = parseInt(this[attrName], 16); - if (intValue > Number.MAX_SAFE_INTEGER) { - return Infinity; - } - return intValue; - }; - _proto.decimalFloatingPoint = function decimalFloatingPoint(attrName) { - return parseFloat(this[attrName]); - }; - _proto.optionalFloat = function optionalFloat(attrName, defaultValue) { - var value = this[attrName]; - return value ? parseFloat(value) : defaultValue; - }; - _proto.enumeratedString = function enumeratedString(attrName) { - return this[attrName]; - }; - _proto.bool = function bool(attrName) { - return this[attrName] === 'YES'; - }; - _proto.decimalResolution = function decimalResolution(attrName) { - var res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]); - if (res === null) { - return undefined; - } - return { - width: parseInt(res[1], 10), - height: parseInt(res[2], 10) - }; - }; - AttrList.parseAttrList = function parseAttrList(input) { - var match; - var attrs = {}; - var quote = '"'; - ATTR_LIST_REGEX.lastIndex = 0; - while ((match = ATTR_LIST_REGEX.exec(input)) !== null) { - var value = match[2]; - if (value.indexOf(quote) === 0 && value.lastIndexOf(quote) === value.length - 1) { - value = value.slice(1, -1); - } - var name = match[1].trim(); - attrs[name] = value; - } - return attrs; - }; - _createClass(AttrList, [{ - key: "clientAttrs", - get: function get() { - return Object.keys(this).filter(function (attr) { - return attr.substring(0, 2) === 'X-'; - }); - } - }]); - return AttrList; - }(); - - // 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"; - } - var DateRange = /*#__PURE__*/function () { - function DateRange(dateRangeAttr, dateRangeWithSameId) { - this.attr = void 0; - this._startDate = void 0; - this._endDate = void 0; - this._badValueForSameId = void 0; - if (dateRangeWithSameId) { - var previousAttr = dateRangeWithSameId.attr; - for (var 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) { - var endDate = new Date(this.attr["END-DATE"]); - if (isFiniteNumber(endDate.getTime())) { - this._endDate = endDate; - } - } - } - _createClass(DateRange, [{ - key: "id", - get: function get() { - return this.attr.ID; - } - }, { - key: "class", - get: function get() { - return this.attr.CLASS; - } - }, { - key: "startDate", - get: function get() { - return this._startDate; - } - }, { - key: "endDate", - get: function get() { - if (this._endDate) { - return this._endDate; - } - var duration = this.duration; - if (duration !== null) { - return new Date(this._startDate.getTime() + duration * 1000); - } - return null; - } - }, { - key: "duration", - get: function get() { - if ("DURATION" in this.attr) { - var duration = this.attr.decimalFloatingPoint("DURATION"); - if (isFiniteNumber(duration)) { - return duration; - } - } else if (this._endDate) { - return (this._endDate.getTime() - this._startDate.getTime()) / 1000; - } - return null; - } - }, { - key: "plannedDuration", - get: function get() { - if ("PLANNED-DURATION" in this.attr) { - return this.attr.decimalFloatingPoint("PLANNED-DURATION"); - } - return null; - } - }, { - key: "endOnNext", - get: function get() { - return this.attr.bool("END-ON-NEXT"); - } - }, { - key: "isValid", - get: function get() { - return !!this.id && !this._badValueForSameId && isFiniteNumber(this.startDate.getTime()) && (this.duration === null || this.duration >= 0) && (!this.endOnNext || !!this.class); - } - }]); - return DateRange; - }(); - - var LoadStats = function LoadStats() { - 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" - }; - var BaseSegment = /*#__PURE__*/function () { - function BaseSegment(baseurl) { - var _this$elementaryStrea; - this._byteRange = null; - this._url = null; - // baseurl is the URL to the playlist - this.baseurl = void 0; - // relurl is the portion of the URL that comes from inside the playlist. - this.relurl = void 0; - // Holds the types of data this fragment supports - this.elementaryStreams = (_this$elementaryStrea = {}, _this$elementaryStrea[ElementaryStreamTypes.AUDIO] = null, _this$elementaryStrea[ElementaryStreamTypes.VIDEO] = null, _this$elementaryStrea[ElementaryStreamTypes.AUDIOVIDEO] = null, _this$elementaryStrea); - this.baseurl = baseurl; - } - - // setByteRange converts a EXT-X-BYTERANGE attribute into a two element array - var _proto = BaseSegment.prototype; - _proto.setByteRange = function setByteRange(value, previous) { - var params = value.split('@', 2); - var 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]; - }; - _createClass(BaseSegment, [{ - key: "byteRange", - get: function get() { - if (!this._byteRange) { - return []; - } - return this._byteRange; - } - }, { - key: "byteRangeStartOffset", - get: function get() { - return this.byteRange[0]; - } - }, { - key: "byteRangeEndOffset", - get: function get() { - return this.byteRange[1]; - } - }, { - key: "url", - get: function get() { - if (!this._url && this.baseurl && this.relurl) { - this._url = urlToolkitExports.buildAbsoluteURL(this.baseurl, this.relurl, { - alwaysNormalize: true - }); - } - return this._url || ''; - }, - set: function set(value) { - this._url = value; - } - }]); - return BaseSegment; - }(); - - /** - * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}. - */ - var Fragment = /*#__PURE__*/function (_BaseSegment) { - _inheritsLoose(Fragment, _BaseSegment); - function Fragment(type, baseurl) { - var _this; - _this = _BaseSegment.call(this, baseurl) || this; - _this._decryptdata = null; - _this.rawProgramDateTime = null; - _this.programDateTime = 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; - // 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; - // Load/parse timing information - _this.stats = new LoadStats(); - // 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; - return _this; - } - var _proto2 = Fragment.prototype; - _proto2.setKeyFormat = function setKeyFormat(keyFormat) { - if (this.levelkeys) { - var _key = this.levelkeys[keyFormat]; - if (_key && !this._decryptdata) { - this._decryptdata = _key.getDecryptData(this.sn); - } - } - }; - _proto2.abortRequests = function 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(); - }; - _proto2.setElementaryStreamInfo = function setElementaryStreamInfo(type, startPTS, endPTS, startDTS, endDTS, partial) { - if (partial === void 0) { - partial = false; - } - var elementaryStreams = this.elementaryStreams; - var info = elementaryStreams[type]; - if (!info) { - elementaryStreams[type] = { - startPTS: startPTS, - endPTS: endPTS, - startDTS: startDTS, - endDTS: endDTS, - partial: 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); - }; - _proto2.clearElementaryStreamInfo = function clearElementaryStreamInfo() { - var elementaryStreams = this.elementaryStreams; - elementaryStreams[ElementaryStreamTypes.AUDIO] = null; - elementaryStreams[ElementaryStreamTypes.VIDEO] = null; - elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO] = null; - }; - _createClass(Fragment, [{ - key: "decryptdata", - get: function get() { - var levelkeys = this.levelkeys; - if (!levelkeys && !this._decryptdata) { - return null; - } - if (!this._decryptdata && this.levelkeys && !this.levelkeys.NONE) { - var _key2 = this.levelkeys.identity; - if (_key2) { - this._decryptdata = _key2.getDecryptData(this.sn); - } else { - var keyFormats = Object.keys(this.levelkeys); - if (keyFormats.length === 1) { - return this._decryptdata = this.levelkeys[keyFormats[0]].getDecryptData(this.sn); - } - } - } - return this._decryptdata; - } - }, { - key: "end", - get: function get() { - return this.start + this.duration; - } - }, { - key: "endProgramDateTime", - get: function get() { - if (this.programDateTime === null) { - return null; - } - if (!isFiniteNumber(this.programDateTime)) { - return null; - } - var duration = !isFiniteNumber(this.duration) ? 0 : this.duration; - return this.programDateTime + duration * 1000; - } - }, { - key: "encrypted", - get: function get() { - 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) { - var keyFormats = Object.keys(this.levelkeys); - var len = keyFormats.length; - if (len > 1 || len === 1 && this.levelkeys[keyFormats[0]].encrypted) { - return true; - } - } - return false; - } - }]); - return Fragment; - }(BaseSegment); - - /** - * Object representing parsed data from an HLS Partial Segment. Found in {@link hls.js#LevelDetails.partList}. - */ - var Part = /*#__PURE__*/function (_BaseSegment2) { - _inheritsLoose(Part, _BaseSegment2); - function Part(partAttrs, frag, baseurl, index, previous) { - var _this2; - _this2 = _BaseSegment2.call(this, baseurl) || this; - _this2.fragOffset = 0; - _this2.duration = 0; - _this2.gap = false; - _this2.independent = false; - _this2.relurl = void 0; - _this2.fragment = void 0; - _this2.index = void 0; - _this2.stats = new LoadStats(); - _this2.duration = partAttrs.decimalFloatingPoint('DURATION'); - _this2.gap = partAttrs.bool('GAP'); - _this2.independent = partAttrs.bool('INDEPENDENT'); - _this2.relurl = partAttrs.enumeratedString('URI'); - _this2.fragment = frag; - _this2.index = index; - var byteRange = partAttrs.enumeratedString('BYTERANGE'); - if (byteRange) { - _this2.setByteRange(byteRange, previous); - } - if (previous) { - _this2.fragOffset = previous.fragOffset + previous.duration; - } - return _this2; - } - _createClass(Part, [{ - key: "start", - get: function get() { - return this.fragment.start + this.fragOffset; - } - }, { - key: "end", - get: function get() { - return this.start + this.duration; - } - }, { - key: "loaded", - get: function get() { - var elementaryStreams = this.elementaryStreams; - return !!(elementaryStreams.audio || elementaryStreams.video || elementaryStreams.audiovideo); - } - }]); - return Part; - }(BaseSegment); - - var DEFAULT_TARGET_DURATION = 10; - - /** - * Object representing parsed data from an HLS Media Playlist. Found in {@link hls.js#Level.details}. - */ - var LevelDetails = /*#__PURE__*/function () { - function LevelDetails(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; - // Manifest reload synchronization - 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; - } - var _proto = LevelDetails.prototype; - _proto.reloaded = function reloaded(previous) { - if (!previous) { - this.advanced = true; - this.updated = true; - return; - } - var partSnDiff = this.lastPartSn - previous.lastPartSn; - var partIndexDiff = this.lastPartIndex - previous.lastPartIndex; - this.updated = this.endSN !== previous.endSN || !!partIndexDiff || !!partSnDiff || !this.live; - 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; - }; - _createClass(LevelDetails, [{ - key: "hasProgramDateTime", - get: function get() { - if (this.fragments.length) { - return isFiniteNumber(this.fragments[this.fragments.length - 1].programDateTime); - } - return false; - } - }, { - key: "levelTargetDuration", - get: function get() { - return this.averagetargetduration || this.targetduration || DEFAULT_TARGET_DURATION; - } - }, { - key: "drift", - get: function get() { - var runTime = this.driftEndTime - this.driftStartTime; - if (runTime > 0) { - var runDuration = this.driftEnd - this.driftStart; - return runDuration * 1000 / runTime; - } - return 1; - } - }, { - key: "edge", - get: function get() { - return this.partEnd || this.fragmentEnd; - } - }, { - key: "partEnd", - get: function get() { - var _this$partList; - if ((_this$partList = this.partList) != null && _this$partList.length) { - return this.partList[this.partList.length - 1].end; - } - return this.fragmentEnd; - } - }, { - key: "fragmentEnd", - get: function get() { - var _this$fragments; - if ((_this$fragments = this.fragments) != null && _this$fragments.length) { - return this.fragments[this.fragments.length - 1].end; - } - return 0; - } - }, { - key: "age", - get: function get() { - if (this.advancedDateTime) { - return Math.max(Date.now() - this.advancedDateTime, 0) / 1000; - } - return 0; - } - }, { - key: "lastPartIndex", - get: function get() { - var _this$partList2; - if ((_this$partList2 = this.partList) != null && _this$partList2.length) { - return this.partList[this.partList.length - 1].index; - } - return -1; - } - }, { - key: "lastPartSn", - get: function get() { - var _this$partList3; - if ((_this$partList3 = this.partList) != null && _this$partList3.length) { - return this.partList[this.partList.length - 1].fragment.sn; - } - return this.endSN; - } - }]); - return LevelDetails; - }(); - - function base64Decode(base64encodedStr) { - return Uint8Array.from(atob(base64encodedStr), function (c) { - return c.charCodeAt(0); - }); - } - - function getKeyIdBytes(str) { - var keyIdbytes = strToUtf8array(str).subarray(0, 16); - var paddedkeyIdbytes = new Uint8Array(16); - paddedkeyIdbytes.set(keyIdbytes, 16 - keyIdbytes.length); - return paddedkeyIdbytes; - } - function changeEndianness(keyId) { - var swap = function swap(array, from, to) { - var 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:[ - var colonsplit = uri.split(':'); - var keydata = null; - if (colonsplit[0] === 'data' && colonsplit.length === 2) { - var semicolonsplit = colonsplit[1].split(';'); - var commasplit = semicolonsplit[semicolonsplit.length - 1].split(','); - if (commasplit.length === 2) { - var isbase64 = commasplit[0] === 'base64'; - var 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)), function (c) { - return c.charCodeAt(0); - }); - } - - /** returns `undefined` is `self` is missing, e.g. in node */ - var optionalSelf = typeof self !== 'undefined' ? self : undefined; - - /** - * @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) { - var drmSystems = config.drmSystems, - widevineLicenseUrl = config.widevineLicenseUrl; - var keySystemsToAttempt = drmSystems ? [KeySystems.FAIRPLAY, KeySystems.WIDEVINE, KeySystems.PLAYREADY, KeySystems.CLEARKEY].filter(function (keySystem) { - return !!drmSystems[keySystem]; - }) : []; - if (!keySystemsToAttempt[KeySystems.WIDEVINE] && widevineLicenseUrl) { - keySystemsToAttempt.push(KeySystems.WIDEVINE); - } - return keySystemsToAttempt; - } - var requestMediaKeySystemAccess = function (_optionalSelf$navigat) { - if (optionalSelf != null && (_optionalSelf$navigat = optionalSelf.navigator) != null && _optionalSelf$navigat.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) { - var 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) { - var baseConfig = { - initDataTypes: initDataTypes, - persistentState: drmSystemOptions.persistentState || 'optional', - distinctiveIdentifier: drmSystemOptions.distinctiveIdentifier || 'optional', - sessionTypes: drmSystemOptions.sessionTypes || [drmSystemOptions.sessionType || 'temporary'], - audioCapabilities: audioCodecs.map(function (codec) { - return { - contentType: "audio/mp4; codecs=\"" + codec + "\"", - robustness: drmSystemOptions.audioRobustness || '', - encryptionScheme: drmSystemOptions.audioEncryptionScheme || null - }; - }), - videoCapabilities: videoCodecs.map(function (codec) { - return { - 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 - */ - var isHeader$2 = function isHeader(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 - */ - var isFooter = function 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 - */ - var getID3Data = function getID3Data(data, offset) { - var front = offset; - var length = 0; - while (isHeader$2(data, offset)) { - // ID3 header is 10 bytes - length += 10; - var 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; - }; - var readSize = function readSize(data, offset) { - var 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; - }; - var canParse$2 = function canParse(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 - */ - var getTimeStamp = function getTimeStamp(data) { - var frames = getID3Frames(data); - for (var i = 0; i < frames.length; i++) { - var frame = frames[i]; - if (isTimeStampFrame(frame)) { - return readTimeStamp(frame); - } - } - return undefined; - }; - - /** - * Returns true if the ID3 frame is an Elementary Stream timestamp frame - */ - var isTimeStampFrame = function isTimeStampFrame(frame) { - return frame && frame.key === 'PRIV' && frame.info === 'com.apple.streaming.transportStreamTimestamp'; - }; - var getFrameData = function getFrameData(data) { - /* - Frame ID $xx xx xx xx (four characters) - Size $xx xx xx xx - Flags $xx xx - */ - var type = String.fromCharCode(data[0], data[1], data[2], data[3]); - var size = readSize(data, 4); - - // skip frame id, size, and flags - var offset = 10; - return { - type: type, - size: 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 - */ - var getID3Frames = function getID3Frames(id3Data) { - var offset = 0; - var frames = []; - while (isHeader$2(id3Data, offset)) { - var size = readSize(id3Data, offset + 6); - // skip past ID3 header - offset += 10; - var end = offset + size; - // loop through frames in the ID3 tag - while (offset + 8 < end) { - var frameData = getFrameData(id3Data.subarray(offset)); - var 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; - }; - var decodeFrame = function decodeFrame(frame) { - if (frame.type === 'PRIV') { - return decodePrivFrame(frame); - } else if (frame.type[0] === 'W') { - return decodeURLFrame(frame); - } - return decodeTextFrame(frame); - }; - var decodePrivFrame = function decodePrivFrame(frame) { - /* - Format: \0 - */ - if (frame.size < 2) { - return undefined; - } - var owner = utf8ArrayToStr(frame.data, true); - var privateData = new Uint8Array(frame.data.subarray(owner.length + 1)); - return { - key: frame.type, - info: owner, - data: privateData.buffer - }; - }; - var decodeTextFrame = function decodeTextFrame(frame) { - if (frame.size < 2) { - return undefined; - } - if (frame.type === 'TXXX') { - /* - Format: - [0] = {Text Encoding} - [1-?] = {Description}\0{Value} - */ - var index = 1; - var description = utf8ArrayToStr(frame.data.subarray(index), true); - index += description.length + 1; - var value = utf8ArrayToStr(frame.data.subarray(index)); - return { - key: frame.type, - info: description, - data: value - }; - } - /* - Format: - [0] = {Text Encoding} - [1-?] = {Value} - */ - var text = utf8ArrayToStr(frame.data.subarray(1)); - return { - key: frame.type, - data: text - }; - }; - var decodeURLFrame = function decodeURLFrame(frame) { - if (frame.type === 'WXXX') { - /* - Format: - [0] = {Text Encoding} - [1-?] = {Description}\0{URL} - */ - if (frame.size < 2) { - return undefined; - } - var index = 1; - var description = utf8ArrayToStr(frame.data.subarray(index), true); - index += description.length + 1; - var value = utf8ArrayToStr(frame.data.subarray(index)); - return { - key: frame.type, - info: description, - data: value - }; - } - /* - Format: - [0-?] = {URL} - */ - var url = utf8ArrayToStr(frame.data); - return { - key: frame.type, - data: url - }; - }; - var readTimeStamp = function readTimeStamp(timeStampFrame) { - if (timeStampFrame.data.byteLength === 8) { - var 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. - var pts33Bit = data[3] & 0x1; - var 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 - * Version: 1.0 - * LastModified: Dec 25 1999 - * This library is free. You can redistribute it and/or modify it. - */ - var utf8ArrayToStr = function utf8ArrayToStr(array, exitOnNull) { - if (exitOnNull === void 0) { - exitOnNull = false; - } - var decoder = getTextDecoder(); - if (decoder) { - var decoded = decoder.decode(array); - if (exitOnNull) { - // grab up to the first null - var idx = decoded.indexOf('\0'); - return idx !== -1 ? decoded.substring(0, idx) : decoded; - } - - // remove any null characters - return decoded.replace(/\0/g, ''); - } - var len = array.length; - var c; - var char2; - var char3; - var out = ''; - var 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; - }; - var decoder; - function getTextDecoder() { - // On Play Station 4, TextDecoder is defined but partially implemented. - // Manual decoding option is preferable - if (navigator.userAgent.includes('PlayStation 4')) { - return; - } - if (!decoder && typeof self.TextDecoder !== 'undefined') { - decoder = new self.TextDecoder('utf-8'); - } - return decoder; - } - - /** - * hex dump helper class - */ - - var Hex = { - hexDump: function hexDump(array) { - var str = ''; - for (var i = 0; i < array.length; i++) { - var h = array[i].toString(16); - if (h.length < 2) { - h = '0' + h; - } - str += h; - } - return str; - } - }; - - var UINT32_MAX$1 = Math.pow(2, 32) - 1; - var push = [].push; - - // We are using fixed track IDs for driving the MP4 remuxer - // instead of following the TS PIDs. - // There is no reason not to do this and some browsers/SourceBuffer-demuxers - // may not like if there are TrackID "switches" - // See https://github.com/video-dev/hls.js/issues/1331 - // Here we are mapping our internal track types to constant MP4 track IDs - // With MSE currently one can only have one track of each, and we are muxing - // whatever video/audio rendition in them. - var RemuxerTrackIdConfig = { - video: 1, - audio: 2, - id3: 3, - text: 4 - }; - function bin2str(data) { - return String.fromCharCode.apply(null, data); - } - function readUint16(buffer, offset) { - var val = buffer[offset] << 8 | buffer[offset + 1]; - return val < 0 ? 65536 + val : val; - } - function readUint32(buffer, offset) { - var val = readSint32(buffer, offset); - return val < 0 ? 4294967296 + val : val; - } - function readUint64(buffer, offset) { - var result = readUint32(buffer, offset); - result *= Math.pow(2, 32); - result += readUint32(buffer, offset + 4); - return result; - } - function readSint32(buffer, offset) { - return buffer[offset] << 24 | buffer[offset + 1] << 16 | buffer[offset + 2] << 8 | buffer[offset + 3]; - } - function writeUint32(buffer, offset, value) { - buffer[offset] = value >> 24; - buffer[offset + 1] = value >> 16 & 0xff; - buffer[offset + 2] = value >> 8 & 0xff; - buffer[offset + 3] = value & 0xff; - } - - // Find "moof" box - function hasMoofData(data) { - var end = data.byteLength; - for (var i = 0; i < end;) { - var size = readUint32(data, i); - if (size > 8 && data[i + 4] === 0x6d && data[i + 5] === 0x6f && data[i + 6] === 0x6f && data[i + 7] === 0x66) { - return true; - } - i = size > 1 ? i + size : end; - } - return false; - } - - // Find the data for a box specified by its path - function findBox(data, path) { - var results = []; - if (!path.length) { - // short-circuit the search for empty paths - return results; - } - var end = data.byteLength; - for (var i = 0; i < end;) { - var size = readUint32(data, i); - var type = bin2str(data.subarray(i + 4, i + 8)); - var endbox = size > 1 ? i + size : end; - if (type === path[0]) { - if (path.length === 1) { - // this is the end of the path and we've found the box we were - // looking for - results.push(data.subarray(i + 8, endbox)); - } else { - // recursively search for the next box along the path - var subresults = findBox(data.subarray(i + 8, endbox), path.slice(1)); - if (subresults.length) { - push.apply(results, subresults); - } - } - } - i = endbox; - } - - // we've finished searching all of data - return results; - } - function parseSegmentIndex(sidx) { - var references = []; - var version = sidx[0]; - - // set initial offset, we skip the reference ID (not needed) - var index = 8; - var timescale = readUint32(sidx, index); - index += 4; - var earliestPresentationTime = 0; - var firstOffset = 0; - if (version === 0) { - earliestPresentationTime = readUint32(sidx, index); - firstOffset = readUint32(sidx, index + 4); - index += 8; - } else { - earliestPresentationTime = readUint64(sidx, index); - firstOffset = readUint64(sidx, index + 8); - index += 16; - } - - // skip reserved - index += 2; - var startByte = sidx.length + firstOffset; - var referencesCount = readUint16(sidx, index); - index += 2; - for (var i = 0; i < referencesCount; i++) { - var referenceIndex = index; - var referenceInfo = readUint32(sidx, referenceIndex); - referenceIndex += 4; - var referenceSize = referenceInfo & 0x7fffffff; - var referenceType = (referenceInfo & 0x80000000) >>> 31; - if (referenceType === 1) { - logger.warn('SIDX has hierarchical references (not supported)'); - return null; - } - var subsegmentDuration = readUint32(sidx, referenceIndex); - referenceIndex += 4; - references.push({ - referenceSize: referenceSize, - subsegmentDuration: subsegmentDuration, - // unscaled - info: { - duration: subsegmentDuration / timescale, - start: startByte, - end: startByte + referenceSize - 1 - } - }); - startByte += referenceSize; - - // Skipping 1 bit for |startsWithSap|, 3 bits for |sapType|, and 28 bits - // for |sapDelta|. - referenceIndex += 4; - - // skip to next ref - index = referenceIndex; - } - return { - earliestPresentationTime: earliestPresentationTime, - timescale: timescale, - version: version, - referencesCount: referencesCount, - references: references - }; - } - - /** - * Parses an MP4 initialization segment and extracts stream type and - * timescale values for any declared tracks. Timescale values indicate the - * number of clock ticks per second to assume for time-based values - * elsewhere in the MP4. - * - * To determine the start time of an MP4, you need two pieces of - * information: the timescale unit and the earliest base media decode - * time. Multiple timescales can be specified within an MP4 but the - * base media decode time is always expressed in the timescale from - * the media header box for the track: - * ``` - * moov > trak > mdia > mdhd.timescale - * moov > trak > mdia > hdlr - * ``` - * @param initSegment the bytes of the init segment - * @returns a hash of track type to timescale values or null if - * the init segment is malformed. - */ - - function parseInitSegment(initSegment) { - var result = []; - var traks = findBox(initSegment, ['moov', 'trak']); - for (var i = 0; i < traks.length; i++) { - var trak = traks[i]; - var tkhd = findBox(trak, ['tkhd'])[0]; - if (tkhd) { - var version = tkhd[0]; - var trackId = readUint32(tkhd, version === 0 ? 12 : 20); - var mdhd = findBox(trak, ['mdia', 'mdhd'])[0]; - if (mdhd) { - version = mdhd[0]; - var timescale = readUint32(mdhd, version === 0 ? 12 : 20); - var hdlr = findBox(trak, ['mdia', 'hdlr'])[0]; - if (hdlr) { - var hdlrType = bin2str(hdlr.subarray(8, 12)); - var type = { - soun: ElementaryStreamTypes.AUDIO, - vide: ElementaryStreamTypes.VIDEO - }[hdlrType]; - if (type) { - // Parse codec details - var stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; - var stsdData = parseStsd(stsd); - result[trackId] = { - timescale: timescale, - type: type - }; - result[type] = _objectSpread2({ - timescale: timescale, - id: trackId - }, stsdData); - } - } - } - } - } - var trex = findBox(initSegment, ['moov', 'mvex', 'trex']); - trex.forEach(function (trex) { - var trackId = readUint32(trex, 4); - var track = result[trackId]; - if (track) { - track.default = { - duration: readUint32(trex, 12), - flags: readUint32(trex, 20) - }; - } - }); - return result; - } - function parseStsd(stsd) { - var sampleEntries = stsd.subarray(8); - var sampleEntriesEnd = sampleEntries.subarray(8 + 78); - var fourCC = bin2str(sampleEntries.subarray(4, 8)); - var codec = fourCC; - var encrypted = fourCC === 'enca' || fourCC === 'encv'; - if (encrypted) { - var encBox = findBox(sampleEntries, [fourCC])[0]; - var encBoxChildren = encBox.subarray(fourCC === 'enca' ? 28 : 78); - var sinfs = findBox(encBoxChildren, ['sinf']); - sinfs.forEach(function (sinf) { - var schm = findBox(sinf, ['schm'])[0]; - if (schm) { - var scheme = bin2str(schm.subarray(4, 8)); - if (scheme === 'cbcs' || scheme === 'cenc') { - var frma = findBox(sinf, ['frma'])[0]; - if (frma) { - // for encrypted content codec fourCC will be in frma - codec = bin2str(frma); - } - } - } - }); - } - switch (codec) { - case 'avc1': - case 'avc2': - case 'avc3': - case 'avc4': - { - // extract profile + compatibility + level out of avcC box - var avcCBox = findBox(sampleEntriesEnd, ['avcC'])[0]; - codec += '.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]); - break; - } - case 'mp4a': - { - var codecBox = findBox(sampleEntries, [fourCC])[0]; - var esdsBox = findBox(codecBox.subarray(28), ['esds'])[0]; - if (esdsBox && esdsBox.length > 12) { - var i = 4; - // ES Descriptor tag - if (esdsBox[i++] !== 0x03) { - break; - } - i = skipBERInteger(esdsBox, i); - i += 2; // skip es_id; - var flags = esdsBox[i++]; - if (flags & 0x80) { - i += 2; // skip dependency es_id - } - if (flags & 0x40) { - i += esdsBox[i++]; // skip URL - } - // Decoder config descriptor - if (esdsBox[i++] !== 0x04) { - break; - } - i = skipBERInteger(esdsBox, i); - var objectType = esdsBox[i++]; - if (objectType === 0x40) { - codec += '.' + toHex(objectType); - } else { - break; - } - i += 12; - // Decoder specific info - if (esdsBox[i++] !== 0x05) { - break; - } - i = skipBERInteger(esdsBox, i); - var firstByte = esdsBox[i++]; - var audioObjectType = (firstByte & 0xf8) >> 3; - if (audioObjectType === 31) { - audioObjectType += 1 + ((firstByte & 0x7) << 3) + ((esdsBox[i] & 0xe0) >> 5); - } - codec += '.' + audioObjectType; - } - break; - } - case 'hvc1': - case 'hev1': - { - var hvcCBox = findBox(sampleEntriesEnd, ['hvcC'])[0]; - var profileByte = hvcCBox[1]; - var profileSpace = ['', 'A', 'B', 'C'][profileByte >> 6]; - var generalProfileIdc = profileByte & 0x1f; - var profileCompat = readUint32(hvcCBox, 2); - var tierFlag = (profileByte & 0x20) >> 5 ? 'H' : 'L'; - var levelIDC = hvcCBox[12]; - var constraintIndicator = hvcCBox.subarray(6, 12); - codec += '.' + profileSpace + generalProfileIdc; - codec += '.' + profileCompat.toString(16).toUpperCase(); - codec += '.' + tierFlag + levelIDC; - var constraintString = ''; - for (var _i = constraintIndicator.length; _i--;) { - var _byte = constraintIndicator[_i]; - if (_byte || constraintString) { - var encodedByte = _byte.toString(16).toUpperCase(); - constraintString = '.' + encodedByte + constraintString; - } - } - codec += constraintString; - break; - } - case 'dvh1': - case 'dvhe': - { - var dvcCBox = findBox(sampleEntriesEnd, ['dvcC'])[0]; - var profile = dvcCBox[2] >> 1 & 0x7f; - var level = dvcCBox[2] << 5 & 0x20 | dvcCBox[3] >> 3 & 0x1f; - codec += '.' + addLeadingZero(profile) + '.' + addLeadingZero(level); - break; - } - case 'vp09': - { - var vpcCBox = findBox(sampleEntriesEnd, ['vpcC'])[0]; - var _profile = vpcCBox[4]; - var _level = vpcCBox[5]; - var bitDepth = vpcCBox[6] >> 4 & 0x0f; - codec += '.' + addLeadingZero(_profile) + '.' + addLeadingZero(_level) + '.' + addLeadingZero(bitDepth); - break; - } - case 'av01': - { - var av1CBox = findBox(sampleEntriesEnd, ['av1C'])[0]; - var _profile2 = av1CBox[1] >>> 5; - var _level2 = av1CBox[1] & 0x1f; - var _tierFlag = av1CBox[2] >>> 7 ? 'H' : 'M'; - var highBitDepth = (av1CBox[2] & 0x40) >> 6; - var twelveBit = (av1CBox[2] & 0x20) >> 5; - var _bitDepth = _profile2 === 2 && highBitDepth ? twelveBit ? 12 : 10 : highBitDepth ? 10 : 8; - var monochrome = (av1CBox[2] & 0x10) >> 4; - var chromaSubsamplingX = (av1CBox[2] & 0x08) >> 3; - var chromaSubsamplingY = (av1CBox[2] & 0x04) >> 2; - var chromaSamplePosition = av1CBox[2] & 0x03; - // TODO: parse color_description_present_flag - // default it to BT.709/limited range for now - // more info https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax - var colorPrimaries = 1; - var transferCharacteristics = 1; - var matrixCoefficients = 1; - var videoFullRangeFlag = 0; - codec += '.' + _profile2 + '.' + addLeadingZero(_level2) + _tierFlag + '.' + addLeadingZero(_bitDepth) + '.' + monochrome + '.' + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition + '.' + addLeadingZero(colorPrimaries) + '.' + addLeadingZero(transferCharacteristics) + '.' + addLeadingZero(matrixCoefficients) + '.' + videoFullRangeFlag; - break; - } - } - return { - codec: codec, - encrypted: encrypted - }; - } - function skipBERInteger(bytes, i) { - var limit = i + 5; - while (bytes[i++] & 0x80 && i < limit) {} - return i; - } - function toHex(x) { - return ('0' + x.toString(16).toUpperCase()).slice(-2); - } - function addLeadingZero(num) { - return (num < 10 ? '0' : '') + num; - } - function patchEncyptionData(initSegment, decryptdata) { - if (!initSegment || !decryptdata) { - return initSegment; - } - var keyId = decryptdata.keyId; - if (keyId && decryptdata.isCommonEncryption) { - var traks = findBox(initSegment, ['moov', 'trak']); - traks.forEach(function (trak) { - var stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; - - // skip the sample entry count - var sampleEntries = stsd.subarray(8); - var encBoxes = findBox(sampleEntries, ['enca']); - var isAudio = encBoxes.length > 0; - if (!isAudio) { - encBoxes = findBox(sampleEntries, ['encv']); - } - encBoxes.forEach(function (enc) { - var encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78); - var sinfBoxes = findBox(encBoxChildren, ['sinf']); - sinfBoxes.forEach(function (sinf) { - var tenc = parseSinf(sinf); - if (tenc) { - // Look for default key id (keyID offset is always 8 within the tenc box): - var tencKeyId = tenc.subarray(8, 24); - if (!tencKeyId.some(function (b) { - return b !== 0; - })) { - logger.log("[eme] Patching keyId in 'enc" + (isAudio ? 'a' : 'v') + ">sinf>>tenc' box: " + Hex.hexDump(tencKeyId) + " -> " + Hex.hexDump(keyId)); - tenc.set(keyId, 8); - } - } - }); - }); - }); - } - return initSegment; - } - function parseSinf(sinf) { - var schm = findBox(sinf, ['schm'])[0]; - if (schm) { - var scheme = bin2str(schm.subarray(4, 8)); - if (scheme === 'cbcs' || scheme === 'cenc') { - return findBox(sinf, ['schi', 'tenc'])[0]; - } - } - logger.error("[eme] missing 'schm' box"); - return null; - } - - /** - * Determine the base media decode start time, in seconds, for an MP4 - * fragment. If multiple fragments are specified, the earliest time is - * returned. - * - * The base media decode time can be parsed from track fragment - * metadata: - * ``` - * moof > traf > tfdt.baseMediaDecodeTime - * ``` - * It requires the timescale value from the mdhd to interpret. - * - * @param initData - a hash of track type to timescale values - * @param fmp4 - the bytes of the mp4 fragment - * @returns the earliest base media decode start time for the - * fragment, in seconds - */ - function getStartDTS(initData, fmp4) { - // we need info from two children of each track fragment box - return findBox(fmp4, ['moof', 'traf']).reduce(function (result, traf) { - var tfdt = findBox(traf, ['tfdt'])[0]; - var version = tfdt[0]; - var start = findBox(traf, ['tfhd']).reduce(function (result, tfhd) { - // get the track id from the tfhd - var id = readUint32(tfhd, 4); - var track = initData[id]; - if (track) { - var baseTime = readUint32(tfdt, 4); - if (version === 1) { - // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild. - // This prevents large values from being used for initPTS, which can cause playlist sync issues. - // https://github.com/video-dev/hls.js/issues/5303 - if (baseTime === UINT32_MAX$1) { - logger.warn("[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time"); - return result; - } - baseTime *= UINT32_MAX$1 + 1; - baseTime += readUint32(tfdt, 8); - } - // assume a 90kHz clock if no timescale was specified - var scale = track.timescale || 90e3; - // convert base time to seconds - var startTime = baseTime / scale; - if (isFiniteNumber(startTime) && (result === null || startTime < result)) { - return startTime; - } - } - return result; - }, null); - if (start !== null && isFiniteNumber(start) && (result === null || start < result)) { - return start; - } - return result; - }, null); - } - - /* - For Reference: - aligned(8) class TrackFragmentHeaderBox - extends FullBox(‘tfhd’, 0, tf_flags){ - unsigned int(32) track_ID; - // all the following are optional fields - unsigned int(64) base_data_offset; - unsigned int(32) sample_description_index; - unsigned int(32) default_sample_duration; - unsigned int(32) default_sample_size; - unsigned int(32) default_sample_flags - } - */ - function getDuration(data, initData) { - var rawDuration = 0; - var videoDuration = 0; - var audioDuration = 0; - var trafs = findBox(data, ['moof', 'traf']); - for (var i = 0; i < trafs.length; i++) { - var traf = trafs[i]; - // There is only one tfhd & trun per traf - // This is true for CMAF style content, and we should perhaps check the ftyp - // and only look for a single trun then, but for ISOBMFF we should check - // for multiple track runs. - var tfhd = findBox(traf, ['tfhd'])[0]; - // get the track id from the tfhd - var id = readUint32(tfhd, 4); - var track = initData[id]; - if (!track) { - continue; - } - var trackDefault = track.default; - var tfhdFlags = readUint32(tfhd, 0) | (trackDefault == null ? void 0 : trackDefault.flags); - var sampleDuration = trackDefault == null ? void 0 : trackDefault.duration; - if (tfhdFlags & 0x000008) { - // 0x000008 indicates the presence of the default_sample_duration field - if (tfhdFlags & 0x000002) { - // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration - // If present, the default_sample_duration exists at byte offset 12 - sampleDuration = readUint32(tfhd, 12); - } else { - // Otherwise, the duration is at byte offset 8 - sampleDuration = readUint32(tfhd, 8); - } - } - // assume a 90kHz clock if no timescale was specified - var timescale = track.timescale || 90e3; - var truns = findBox(traf, ['trun']); - for (var j = 0; j < truns.length; j++) { - rawDuration = computeRawDurationFromSamples(truns[j]); - if (!rawDuration && sampleDuration) { - var sampleCount = readUint32(truns[j], 4); - rawDuration = sampleDuration * sampleCount; - } - if (track.type === ElementaryStreamTypes.VIDEO) { - videoDuration += rawDuration / timescale; - } else if (track.type === ElementaryStreamTypes.AUDIO) { - audioDuration += rawDuration / timescale; - } - } - } - if (videoDuration === 0 && audioDuration === 0) { - // If duration samples are not available in the traf use sidx subsegment_duration - var sidxMinStart = Infinity; - var sidxMaxEnd = 0; - var sidxDuration = 0; - var sidxs = findBox(data, ['sidx']); - for (var _i2 = 0; _i2 < sidxs.length; _i2++) { - var sidx = parseSegmentIndex(sidxs[_i2]); - if (sidx != null && sidx.references) { - sidxMinStart = Math.min(sidxMinStart, sidx.earliestPresentationTime / sidx.timescale); - var subSegmentDuration = sidx.references.reduce(function (dur, ref) { - return dur + ref.info.duration || 0; - }, 0); - sidxMaxEnd = Math.max(sidxMaxEnd, subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale); - sidxDuration = sidxMaxEnd - sidxMinStart; - } - } - if (sidxDuration && isFiniteNumber(sidxDuration)) { - return sidxDuration; - } - } - if (videoDuration) { - return videoDuration; - } - return audioDuration; - } - - /* - For Reference: - aligned(8) class TrackRunBox - extends FullBox(‘trun’, version, tr_flags) { - unsigned int(32) sample_count; - // the following are optional fields - signed int(32) data_offset; - unsigned int(32) first_sample_flags; - // all fields in the following array are optional - { - unsigned int(32) sample_duration; - unsigned int(32) sample_size; - unsigned int(32) sample_flags - if (version == 0) - { unsigned int(32) - else - { signed int(32) - }[ sample_count ] - } - */ - function computeRawDurationFromSamples(trun) { - var flags = readUint32(trun, 0); - // Flags are at offset 0, non-optional sample_count is at offset 4. Therefore we start 8 bytes in. - // Each field is an int32, which is 4 bytes - var offset = 8; - // data-offset-present flag - if (flags & 0x000001) { - offset += 4; - } - // first-sample-flags-present flag - if (flags & 0x000004) { - offset += 4; - } - var duration = 0; - var sampleCount = readUint32(trun, 4); - for (var i = 0; i < sampleCount; i++) { - // sample-duration-present flag - if (flags & 0x000100) { - var sampleDuration = readUint32(trun, offset); - duration += sampleDuration; - offset += 4; - } - // sample-size-present flag - if (flags & 0x000200) { - offset += 4; - } - // sample-flags-present flag - if (flags & 0x000400) { - offset += 4; - } - // sample-composition-time-offsets-present flag - if (flags & 0x000800) { - offset += 4; - } - } - return duration; - } - function offsetStartDTS(initData, fmp4, timeOffset) { - findBox(fmp4, ['moof', 'traf']).forEach(function (traf) { - findBox(traf, ['tfhd']).forEach(function (tfhd) { - // get the track id from the tfhd - var id = readUint32(tfhd, 4); - var track = initData[id]; - if (!track) { - return; - } - // assume a 90kHz clock if no timescale was specified - var timescale = track.timescale || 90e3; - // get the base media decode time from the tfdt - findBox(traf, ['tfdt']).forEach(function (tfdt) { - var version = tfdt[0]; - var offset = timeOffset * timescale; - if (offset) { - var baseMediaDecodeTime = readUint32(tfdt, 4); - if (version === 0) { - baseMediaDecodeTime -= offset; - baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0); - writeUint32(tfdt, 4, baseMediaDecodeTime); - } else { - baseMediaDecodeTime *= Math.pow(2, 32); - baseMediaDecodeTime += readUint32(tfdt, 8); - baseMediaDecodeTime -= offset; - baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0); - var upper = Math.floor(baseMediaDecodeTime / (UINT32_MAX$1 + 1)); - var lower = Math.floor(baseMediaDecodeTime % (UINT32_MAX$1 + 1)); - writeUint32(tfdt, 4, upper); - writeUint32(tfdt, 8, lower); - } - } - }); - }); - }); - } - - // TODO: Check if the last moof+mdat pair is part of the valid range - function segmentValidRange(data) { - var segmentedRange = { - valid: null, - remainder: null - }; - var moofs = findBox(data, ['moof']); - if (moofs.length < 2) { - segmentedRange.remainder = data; - return segmentedRange; - } - var last = moofs[moofs.length - 1]; - // Offset by 8 bytes; findBox offsets the start by as much - segmentedRange.valid = sliceUint8(data, 0, last.byteOffset - 8); - segmentedRange.remainder = sliceUint8(data, last.byteOffset - 8); - return segmentedRange; - } - function appendUint8Array(data1, data2) { - var temp = new Uint8Array(data1.length + data2.length); - temp.set(data1); - temp.set(data2, data1.length); - return temp; - } - function parseSamples(timeOffset, track) { - var seiSamples = []; - var videoData = track.samples; - var timescale = track.timescale; - var trackId = track.id; - var isHEVCFlavor = false; - var moofs = findBox(videoData, ['moof']); - moofs.map(function (moof) { - var moofOffset = moof.byteOffset - 8; - var trafs = findBox(moof, ['traf']); - trafs.map(function (traf) { - // get the base media decode time from the tfdt - var baseTime = findBox(traf, ['tfdt']).map(function (tfdt) { - var version = tfdt[0]; - var result = readUint32(tfdt, 4); - if (version === 1) { - result *= Math.pow(2, 32); - result += readUint32(tfdt, 8); - } - return result / timescale; - })[0]; - if (baseTime !== undefined) { - timeOffset = baseTime; - } - return findBox(traf, ['tfhd']).map(function (tfhd) { - var id = readUint32(tfhd, 4); - var tfhdFlags = readUint32(tfhd, 0) & 0xffffff; - var baseDataOffsetPresent = (tfhdFlags & 0x000001) !== 0; - var sampleDescriptionIndexPresent = (tfhdFlags & 0x000002) !== 0; - var defaultSampleDurationPresent = (tfhdFlags & 0x000008) !== 0; - var defaultSampleDuration = 0; - var defaultSampleSizePresent = (tfhdFlags & 0x000010) !== 0; - var defaultSampleSize = 0; - var defaultSampleFlagsPresent = (tfhdFlags & 0x000020) !== 0; - var tfhdOffset = 8; - if (id === trackId) { - if (baseDataOffsetPresent) { - tfhdOffset += 8; - } - if (sampleDescriptionIndexPresent) { - tfhdOffset += 4; - } - if (defaultSampleDurationPresent) { - defaultSampleDuration = readUint32(tfhd, tfhdOffset); - tfhdOffset += 4; - } - if (defaultSampleSizePresent) { - defaultSampleSize = readUint32(tfhd, tfhdOffset); - tfhdOffset += 4; - } - if (defaultSampleFlagsPresent) { - tfhdOffset += 4; - } - if (track.type === 'video') { - isHEVCFlavor = isHEVC(track.codec); - } - findBox(traf, ['trun']).map(function (trun) { - var version = trun[0]; - var flags = readUint32(trun, 0) & 0xffffff; - var dataOffsetPresent = (flags & 0x000001) !== 0; - var dataOffset = 0; - var firstSampleFlagsPresent = (flags & 0x000004) !== 0; - var sampleDurationPresent = (flags & 0x000100) !== 0; - var sampleDuration = 0; - var sampleSizePresent = (flags & 0x000200) !== 0; - var sampleSize = 0; - var sampleFlagsPresent = (flags & 0x000400) !== 0; - var sampleCompositionOffsetsPresent = (flags & 0x000800) !== 0; - var compositionOffset = 0; - var sampleCount = readUint32(trun, 4); - var trunOffset = 8; // past version, flags, and sample count - - if (dataOffsetPresent) { - dataOffset = readUint32(trun, trunOffset); - trunOffset += 4; - } - if (firstSampleFlagsPresent) { - trunOffset += 4; - } - var sampleOffset = dataOffset + moofOffset; - for (var ix = 0; ix < sampleCount; ix++) { - if (sampleDurationPresent) { - sampleDuration = readUint32(trun, trunOffset); - trunOffset += 4; - } else { - sampleDuration = defaultSampleDuration; - } - if (sampleSizePresent) { - sampleSize = readUint32(trun, trunOffset); - trunOffset += 4; - } else { - sampleSize = defaultSampleSize; - } - if (sampleFlagsPresent) { - trunOffset += 4; - } - if (sampleCompositionOffsetsPresent) { - if (version === 0) { - compositionOffset = readUint32(trun, trunOffset); - } else { - compositionOffset = readSint32(trun, trunOffset); - } - trunOffset += 4; - } - if (track.type === ElementaryStreamTypes.VIDEO) { - var naluTotalSize = 0; - while (naluTotalSize < sampleSize) { - var naluSize = readUint32(videoData, sampleOffset); - sampleOffset += 4; - if (isSEIMessage(isHEVCFlavor, videoData[sampleOffset])) { - var data = videoData.subarray(sampleOffset, sampleOffset + naluSize); - parseSEIMessageFromNALu(data, isHEVCFlavor ? 2 : 1, timeOffset + compositionOffset / timescale, seiSamples); - } - sampleOffset += naluSize; - naluTotalSize += naluSize + 4; - } - } - timeOffset += sampleDuration / timescale; - } - }); - } - }); - }); - }); - return seiSamples; - } - function isHEVC(codec) { - if (!codec) { - return false; - } - var delimit = codec.indexOf('.'); - var baseCodec = delimit < 0 ? codec : codec.substring(0, delimit); - return baseCodec === 'hvc1' || baseCodec === 'hev1' || - // Dolby Vision - baseCodec === 'dvh1' || baseCodec === 'dvhe'; - } - function isSEIMessage(isHEVCFlavor, naluHeader) { - if (isHEVCFlavor) { - var naluType = naluHeader >> 1 & 0x3f; - return naluType === 39 || naluType === 40; - } else { - var _naluType = naluHeader & 0x1f; - return _naluType === 6; - } - } - function parseSEIMessageFromNALu(unescapedData, headerSize, pts, samples) { - var data = discardEPB(unescapedData); - var seiPtr = 0; - // skip nal header - seiPtr += headerSize; - var payloadType = 0; - var payloadSize = 0; - var b = 0; - while (seiPtr < data.length) { - payloadType = 0; - do { - if (seiPtr >= data.length) { - break; - } - b = data[seiPtr++]; - payloadType += b; - } while (b === 0xff); - - // Parse payload size. - payloadSize = 0; - do { - if (seiPtr >= data.length) { - break; - } - b = data[seiPtr++]; - payloadSize += b; - } while (b === 0xff); - var leftOver = data.length - seiPtr; - // Create a variable to process the payload - var payPtr = seiPtr; - - // Increment the seiPtr to the end of the payload - if (payloadSize < leftOver) { - seiPtr += payloadSize; - } else if (payloadSize > leftOver) { - // Some type of corruption has happened? - logger.error("Malformed SEI payload. " + payloadSize + " is too small, only " + leftOver + " bytes left to parse."); - // We might be able to parse some data, but let's be safe and ignore it. - break; - } - if (payloadType === 4) { - var countryCode = data[payPtr++]; - if (countryCode === 181) { - var providerCode = readUint16(data, payPtr); - payPtr += 2; - if (providerCode === 49) { - var userStructure = readUint32(data, payPtr); - payPtr += 4; - if (userStructure === 0x47413934) { - var userDataType = data[payPtr++]; - - // Raw CEA-608 bytes wrapped in CEA-708 packet - if (userDataType === 3) { - var firstByte = data[payPtr++]; - var totalCCs = 0x1f & firstByte; - var enabled = 0x40 & firstByte; - var totalBytes = enabled ? 2 + totalCCs * 3 : 0; - var byteArray = new Uint8Array(totalBytes); - if (enabled) { - byteArray[0] = firstByte; - for (var i = 1; i < totalBytes; i++) { - byteArray[i] = data[payPtr++]; - } - } - samples.push({ - type: userDataType, - payloadType: payloadType, - pts: pts, - bytes: byteArray - }); - } - } - } - } - } else if (payloadType === 5) { - if (payloadSize > 16) { - var uuidStrArray = []; - for (var _i3 = 0; _i3 < 16; _i3++) { - var _b = data[payPtr++].toString(16); - uuidStrArray.push(_b.length == 1 ? '0' + _b : _b); - if (_i3 === 3 || _i3 === 5 || _i3 === 7 || _i3 === 9) { - uuidStrArray.push('-'); - } - } - var length = payloadSize - 16; - var userDataBytes = new Uint8Array(length); - for (var _i4 = 0; _i4 < length; _i4++) { - userDataBytes[_i4] = data[payPtr++]; - } - samples.push({ - payloadType: payloadType, - pts: pts, - uuid: uuidStrArray.join(''), - userData: utf8ArrayToStr(userDataBytes), - userDataBytes: userDataBytes - }); - } - } - } - } - - /** - * remove Emulation Prevention bytes from a RBSP - */ - function discardEPB(data) { - var length = data.byteLength; - var EPBPositions = []; - var i = 1; - - // Find all `Emulation Prevention Bytes` - while (i < length - 2) { - if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) { - EPBPositions.push(i + 2); - i += 2; - } else { - i++; - } - } - - // If no Emulation Prevention Bytes were found just return the original - // array - if (EPBPositions.length === 0) { - return data; - } - - // Create a new array to hold the NAL unit data - var newLength = length - EPBPositions.length; - var newData = new Uint8Array(newLength); - var sourceIndex = 0; - for (i = 0; i < newLength; sourceIndex++, i++) { - if (sourceIndex === EPBPositions[0]) { - // Skip this byte - sourceIndex++; - // Remove this position index - EPBPositions.shift(); - } - newData[i] = data[sourceIndex]; - } - return newData; - } - function parseEmsg(data) { - var version = data[0]; - var schemeIdUri = ''; - var value = ''; - var timeScale = 0; - var presentationTimeDelta = 0; - var presentationTime = 0; - var eventDuration = 0; - var id = 0; - var offset = 0; - if (version === 0) { - while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { - schemeIdUri += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - } - schemeIdUri += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { - value += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - } - value += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - timeScale = readUint32(data, 12); - presentationTimeDelta = readUint32(data, 16); - eventDuration = readUint32(data, 20); - id = readUint32(data, 24); - offset = 28; - } else if (version === 1) { - offset += 4; - timeScale = readUint32(data, offset); - offset += 4; - var leftPresentationTime = readUint32(data, offset); - offset += 4; - var rightPresentationTime = readUint32(data, offset); - offset += 4; - presentationTime = Math.pow(2, 32) * leftPresentationTime + rightPresentationTime; - if (!isSafeInteger(presentationTime)) { - presentationTime = Number.MAX_SAFE_INTEGER; - logger.warn('Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box'); - } - eventDuration = readUint32(data, offset); - offset += 4; - id = readUint32(data, offset); - offset += 4; - while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { - schemeIdUri += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - } - schemeIdUri += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - while (bin2str(data.subarray(offset, offset + 1)) !== '\0') { - value += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - } - value += bin2str(data.subarray(offset, offset + 1)); - offset += 1; - } - var payload = data.subarray(offset, data.byteLength); - return { - schemeIdUri: schemeIdUri, - value: value, - timeScale: timeScale, - presentationTime: presentationTime, - presentationTimeDelta: presentationTimeDelta, - eventDuration: eventDuration, - id: id, - payload: payload - }; - } - function mp4Box(type) { - for (var _len = arguments.length, payload = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - payload[_key - 1] = arguments[_key]; - } - var len = payload.length; - var size = 8; - var i = len; - while (i--) { - size += payload[i].byteLength; - } - var result = new Uint8Array(size); - result[0] = size >> 24 & 0xff; - result[1] = size >> 16 & 0xff; - result[2] = size >> 8 & 0xff; - result[3] = size & 0xff; - result.set(type, 4); - for (i = 0, size = 8; i < len; i++) { - result.set(payload[i], size); - size += payload[i].byteLength; - } - return result; - } - function mp4pssh(systemId, keyids, data) { - if (systemId.byteLength !== 16) { - throw new RangeError('Invalid system id'); - } - var version; - var kids; - if (keyids) { - version = 1; - kids = new Uint8Array(keyids.length * 16); - for (var ix = 0; ix < keyids.length; ix++) { - var k = keyids[ix]; // uint8array - if (k.byteLength !== 16) { - throw new RangeError('Invalid key'); - } - kids.set(k, ix * 16); - } - } else { - version = 0; - kids = new Uint8Array(); - } - var kidCount; - if (version > 0) { - kidCount = new Uint8Array(4); - if (keyids.length > 0) { - new DataView(kidCount.buffer).setUint32(0, keyids.length, false); - } - } else { - kidCount = new Uint8Array(); - } - var dataSize = new Uint8Array(4); - if (data && data.byteLength > 0) { - new DataView(dataSize.buffer).setUint32(0, data.byteLength, false); - } - return mp4Box([112, 115, 115, 104], new Uint8Array([version, 0x00, 0x00, 0x00 // Flags - ]), systemId, - // 16 bytes - kidCount, kids, dataSize, data || new Uint8Array()); - } - function parsePssh(initData) { - if (!(initData instanceof ArrayBuffer) || initData.byteLength < 32) { - return null; - } - var result = { - version: 0, - systemId: '', - kids: null, - data: null - }; - var view = new DataView(initData); - var boxSize = view.getUint32(0); - if (initData.byteLength !== boxSize && boxSize > 44) { - return null; - } - var type = view.getUint32(4); - if (type !== 0x70737368) { - return null; - } - result.version = view.getUint32(8) >>> 24; - if (result.version > 1) { - return null; - } - result.systemId = Hex.hexDump(new Uint8Array(initData, 12, 16)); - var dataSizeOrKidCount = view.getUint32(28); - if (result.version === 0) { - if (boxSize - 32 < dataSizeOrKidCount) { - return null; - } - result.data = new Uint8Array(initData, 32, dataSizeOrKidCount); - } else if (result.version === 1) { - result.kids = []; - for (var i = 0; i < dataSizeOrKidCount; i++) { - result.kids.push(new Uint8Array(initData, 32 + i * 16, 16)); - } - } - return result; - } - - var keyUriToKeyIdMap = {}; - var LevelKey = /*#__PURE__*/function () { - LevelKey.clearKeyUriToKeyIdMap = function clearKeyUriToKeyIdMap() { - keyUriToKeyIdMap = {}; - }; - function LevelKey(method, uri, format, formatversions, iv) { - if (formatversions === void 0) { - formatversions = [1]; - } - if (iv === void 0) { - iv = null; - } - this.uri = void 0; - this.method = void 0; - this.keyFormat = void 0; - this.keyFormatVersions = void 0; - this.encrypted = void 0; - this.isCommonEncryption = void 0; - this.iv = null; - this.key = null; - this.keyId = null; - this.pssh = null; - this.method = method; - this.uri = uri; - this.keyFormat = format; - this.keyFormatVersions = formatversions; - this.iv = iv; - this.encrypted = method ? method !== 'NONE' : false; - this.isCommonEncryption = this.encrypted && method !== 'AES-128'; - } - var _proto = LevelKey.prototype; - _proto.isSupported = function isSupported() { - // If it's Segment encryption or No encryption, just select that key system - if (this.method) { - if (this.method === 'AES-128' || this.method === 'NONE') { - return true; - } - if (this.keyFormat === 'identity') { - // Maintain support for clear SAMPLE-AES with MPEG-3 TS - return this.method === 'SAMPLE-AES'; - } else { - switch (this.keyFormat) { - case KeySystemFormats.FAIRPLAY: - case KeySystemFormats.WIDEVINE: - case KeySystemFormats.PLAYREADY: - case KeySystemFormats.CLEARKEY: - return ['ISO-23001-7', 'SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf(this.method) !== -1; - } - } - } - return false; - }; - _proto.getDecryptData = function getDecryptData(sn) { - if (!this.encrypted || !this.uri) { - return null; - } - if (this.method === 'AES-128' && this.uri && !this.iv) { - if (typeof sn !== 'number') { - // We are fetching decryption data for a initialization segment - // If the segment was encrypted with AES-128 - // It must have an IV defined. We cannot substitute the Segment Number in. - if (this.method === 'AES-128' && !this.iv) { - logger.warn("missing IV for initialization segment with method=\"" + this.method + "\" - compliance issue"); - } - // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation. - sn = 0; - } - var iv = createInitializationVector(sn); - var decryptdata = new LevelKey(this.method, this.uri, 'identity', this.keyFormatVersions, iv); - return decryptdata; - } - - // Initialize keyId if possible - var keyBytes = convertDataUriToArrayBytes(this.uri); - if (keyBytes) { - switch (this.keyFormat) { - case KeySystemFormats.WIDEVINE: - this.pssh = keyBytes; - // In case of widevine keyID is embedded in PSSH box. Read Key ID. - if (keyBytes.length >= 22) { - this.keyId = keyBytes.subarray(keyBytes.length - 22, keyBytes.length - 6); - } - break; - case KeySystemFormats.PLAYREADY: - { - var PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]); - this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes); - var keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2); - var keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16)); - - // Parse Playready WRMHeader XML - var xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length); - var parser = new DOMParser(); - var xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml'); - var keyData = xmlDoc.getElementsByTagName('KID')[0]; - if (keyData) { - var keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE'); - if (keyId) { - var keyIdArray = base64Decode(keyId).subarray(0, 16); - // KID value in PRO is a base64-encoded little endian GUID interpretation of UUID - // KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID - changeEndianness(keyIdArray); - this.keyId = keyIdArray; - } - } - break; - } - default: - { - var keydata = keyBytes.subarray(0, 16); - if (keydata.length !== 16) { - var padded = new Uint8Array(16); - padded.set(keydata, 16 - keydata.length); - keydata = padded; - } - this.keyId = keydata; - break; - } - } - } - - // Default behavior: assign a new keyId for each uri - if (!this.keyId || this.keyId.byteLength !== 16) { - var _keyId = keyUriToKeyIdMap[this.uri]; - if (!_keyId) { - var val = Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER; - _keyId = new Uint8Array(16); - var dv = new DataView(_keyId.buffer, 12, 4); // Just set the last 4 bytes - dv.setUint32(0, val); - keyUriToKeyIdMap[this.uri] = _keyId; - } - this.keyId = _keyId; - } - return this; - }; - return LevelKey; - }(); - function createInitializationVector(segmentNumber) { - var uint8View = new Uint8Array(16); - for (var i = 12; i < 16; i++) { - uint8View[i] = segmentNumber >> 8 * (15 - i) & 0xff; - } - return uint8View; - } - - var VARIABLE_REPLACEMENT_REGEX = /\{\$([a-zA-Z0-9-_]+)\}/g; - function hasVariableReferences(str) { - return VARIABLE_REPLACEMENT_REGEX.test(str); - } - function substituteVariablesInAttributes(parsed, attr, attributeNames) { - if (parsed.variableList !== null || parsed.hasVariableRefs) { - for (var i = attributeNames.length; i--;) { - var name = attributeNames[i]; - var value = attr[name]; - if (value) { - attr[name] = substituteVariables(parsed, value); - } - } - } - } - function substituteVariables(parsed, value) { - if (parsed.variableList !== null || parsed.hasVariableRefs) { - var variableList = parsed.variableList; - return value.replace(VARIABLE_REPLACEMENT_REGEX, function (variableReference) { - var variableName = variableReference.substring(2, variableReference.length - 1); - var variableValue = variableList == null ? void 0 : variableList[variableName]; - if (variableValue === undefined) { - parsed.playlistParsingError || (parsed.playlistParsingError = new Error("Missing preceding EXT-X-DEFINE tag for Variable Reference: \"" + variableName + "\"")); - return variableReference; - } - return variableValue; - }); - } - return value; - } - function addVariableDefinition(parsed, attr, parentUrl) { - var variableList = parsed.variableList; - if (!variableList) { - parsed.variableList = variableList = {}; - } - var NAME; - var VALUE; - if ('QUERYPARAM' in attr) { - NAME = attr.QUERYPARAM; - try { - var searchParams = new self.URL(parentUrl).searchParams; - if (searchParams.has(NAME)) { - VALUE = searchParams.get(NAME); - } else { - throw new Error("\"" + NAME + "\" does not match any query parameter in URI: \"" + parentUrl + "\""); - } - } catch (error) { - parsed.playlistParsingError || (parsed.playlistParsingError = new Error("EXT-X-DEFINE QUERYPARAM: " + error.message)); - } - } else { - NAME = attr.NAME; - VALUE = attr.VALUE; - } - if (NAME in variableList) { - parsed.playlistParsingError || (parsed.playlistParsingError = new Error("EXT-X-DEFINE duplicate Variable Name declarations: \"" + NAME + "\"")); - } else { - variableList[NAME] = VALUE || ''; - } - } - function importVariableDefinition(parsed, attr, sourceVariableList) { - var IMPORT = attr.IMPORT; - if (sourceVariableList && IMPORT in sourceVariableList) { - var variableList = parsed.variableList; - if (!variableList) { - parsed.variableList = variableList = {}; - } - variableList[IMPORT] = sourceVariableList[IMPORT]; - } else { - parsed.playlistParsingError || (parsed.playlistParsingError = new Error("EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: \"" + IMPORT + "\"")); - } - } - - /** - * MediaSource helper - */ - - function getMediaSource(preferManagedMediaSource) { - if (preferManagedMediaSource === void 0) { - preferManagedMediaSource = true; - } - if (typeof self === 'undefined') return undefined; - var mms = (preferManagedMediaSource || !self.MediaSource) && self.ManagedMediaSource; - return mms || self.MediaSource || self.WebKitMediaSource; - } - function isManagedMediaSource(source) { - return typeof self !== 'undefined' && source === self.ManagedMediaSource; - } - - // from http://mp4ra.org/codecs.html - // values indicate codec selection preference (lower is higher priority) - var sampleEntryCodesISO = { - audio: { - a3ds: 1, - 'ac-3': 0.95, - 'ac-4': 1, - alac: 0.9, - alaw: 1, - dra1: 1, - 'dts+': 1, - 'dts-': 1, - dtsc: 1, - dtse: 1, - dtsh: 1, - 'ec-3': 0.9, - enca: 1, - fLaC: 0.9, - // MP4-RA listed codec entry for FLAC - flac: 0.9, - // legacy browser codec name for FLAC - FLAC: 0.9, - // some manifests may list "FLAC" with Apple's tools - g719: 1, - g726: 1, - m4ae: 1, - mha1: 1, - mha2: 1, - mhm1: 1, - mhm2: 1, - mlpa: 1, - mp4a: 1, - 'raw ': 1, - Opus: 1, - opus: 1, - // browsers expect this to be lowercase despite MP4RA says 'Opus' - samr: 1, - sawb: 1, - sawp: 1, - sevc: 1, - sqcp: 1, - ssmv: 1, - twos: 1, - ulaw: 1 - }, - video: { - avc1: 1, - avc2: 1, - avc3: 1, - avc4: 1, - avcp: 1, - av01: 0.8, - drac: 1, - dva1: 1, - dvav: 1, - dvh1: 0.7, - dvhe: 0.7, - encv: 1, - hev1: 0.75, - hvc1: 0.75, - mjp2: 1, - mp4v: 1, - mvc1: 1, - mvc2: 1, - mvc3: 1, - mvc4: 1, - resv: 1, - rv60: 1, - s263: 1, - svc1: 1, - svc2: 1, - 'vc-1': 1, - vp08: 1, - vp09: 0.9 - }, - text: { - stpp: 1, - wvtt: 1 - } - }; - function isCodecType(codec, type) { - var typeCodes = sampleEntryCodesISO[type]; - return !!typeCodes && !!typeCodes[codec.slice(0, 4)]; - } - function areCodecsMediaSourceSupported(codecs, type, preferManagedMediaSource) { - if (preferManagedMediaSource === void 0) { - preferManagedMediaSource = true; - } - return !codecs.split(',').some(function (codec) { - return !isCodecMediaSourceSupported(codec, type, preferManagedMediaSource); - }); - } - function isCodecMediaSourceSupported(codec, type, preferManagedMediaSource) { - var _MediaSource$isTypeSu; - if (preferManagedMediaSource === void 0) { - preferManagedMediaSource = true; - } - var MediaSource = getMediaSource(preferManagedMediaSource); - return (_MediaSource$isTypeSu = MediaSource == null ? void 0 : MediaSource.isTypeSupported(mimeTypeForCodec(codec, type))) != null ? _MediaSource$isTypeSu : false; - } - function mimeTypeForCodec(codec, type) { - return type + "/mp4;codecs=\"" + codec + "\""; - } - function videoCodecPreferenceValue(videoCodec) { - if (videoCodec) { - var fourCC = videoCodec.substring(0, 4); - return sampleEntryCodesISO.video[fourCC]; - } - return 2; - } - function codecsSetSelectionPreferenceValue(codecSet) { - return codecSet.split(',').reduce(function (num, fourCC) { - var preferenceValue = sampleEntryCodesISO.video[fourCC]; - if (preferenceValue) { - return (preferenceValue * 2 + num) / (num ? 3 : 2); - } - return (sampleEntryCodesISO.audio[fourCC] + num) / (num ? 2 : 1); - }, 0); - } - var CODEC_COMPATIBLE_NAMES = {}; - function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource) { - if (preferManagedMediaSource === void 0) { - preferManagedMediaSource = true; - } - if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) { - return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]; - } - - // Idealy fLaC and Opus would be first (spec-compliant) but - // some browsers will report that fLaC is supported then fail. - // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728 - var codecsToCheck = { - flac: ['flac', 'fLaC', 'FLAC'], - opus: ['opus', 'Opus'] - }[lowerCaseCodec]; - for (var i = 0; i < codecsToCheck.length; i++) { - if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) { - CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i]; - return codecsToCheck[i]; - } - } - return lowerCaseCodec; - } - var AUDIO_CODEC_REGEXP = /flac|opus/i; - function getCodecCompatibleName(codec, preferManagedMediaSource) { - if (preferManagedMediaSource === void 0) { - preferManagedMediaSource = true; - } - return codec.replace(AUDIO_CODEC_REGEXP, function (m) { - return getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource); - }); - } - function pickMostCompleteCodecName(parsedCodec, levelCodec) { - // Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a - // so use level codec is parsed codec is unavailable or incomplete - if (parsedCodec && parsedCodec !== 'mp4a') { - return parsedCodec; - } - return levelCodec ? levelCodec.split(',')[0] : levelCodec; - } - function convertAVC1ToAVCOTI(codec) { - // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported - // Examples: avc1.66.30 to avc1.42001e and avc1.77.30,avc1.66.30 to avc1.4d001e,avc1.42001e. - var codecs = codec.split(','); - for (var i = 0; i < codecs.length; i++) { - var avcdata = codecs[i].split('.'); - if (avcdata.length > 2) { - var result = avcdata.shift() + '.'; - result += parseInt(avcdata.shift()).toString(16); - result += ('000' + parseInt(avcdata.shift()).toString(16)).slice(-4); - codecs[i] = result; - } - } - return codecs.join(','); - } - - var MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g; - var MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g; - var IS_MEDIA_PLAYLIST = /^#EXT(?:INF|-X-TARGETDURATION):/m; // Handle empty Media Playlist (first EXTINF not signaled, but TARGETDURATION present) - - var LEVEL_PLAYLIST_REGEX_FAST = new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source, - // duration (#EXTINF:,), group 1 => duration, group 2 => title - /(?!#) *(\S[^\r\n]*)/.source, - // segment URI, group 3 => the URI (note newline is not eaten) - /#EXT-X-BYTERANGE:*(.+)/.source, - // next segment's byterange, group 4 => range spec (x@y) - /#EXT-X-PROGRAM-DATE-TIME:(.+)/.source, - // next segment's program date/time group 5 => the datetime spec - /#.*/.source // All other non-segment oriented tags will match with all groups empty - ].join('|'), 'g'); - var LEVEL_PLAYLIST_REGEX_SLOW = new RegExp([/#(EXTM3U)/.source, /#EXT-X-(DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source, /#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/.source, /#EXT-X-(DISCONTINUITY|ENDLIST|GAP|INDEPENDENT-SEGMENTS)/.source, /(#)([^:]*):(.*)/.source, /(#)(.*)(?:.*)\r?\n?/.source].join('|')); - var M3U8Parser = /*#__PURE__*/function () { - function M3U8Parser() {} - M3U8Parser.findGroup = function findGroup(groups, mediaGroupId) { - for (var i = 0; i < groups.length; i++) { - var group = groups[i]; - if (group.id === mediaGroupId) { - return group; - } - } - }; - M3U8Parser.resolve = function resolve(url, baseUrl) { - return urlToolkitExports.buildAbsoluteURL(baseUrl, url, { - alwaysNormalize: true - }); - }; - M3U8Parser.isMediaPlaylist = function isMediaPlaylist(str) { - return IS_MEDIA_PLAYLIST.test(str); - }; - M3U8Parser.parseMasterPlaylist = function parseMasterPlaylist(string, baseurl) { - var hasVariableRefs = hasVariableReferences(string) ; - var parsed = { - contentSteering: null, - levels: [], - playlistParsingError: null, - sessionData: null, - sessionKeys: null, - startTimeOffset: null, - variableList: null, - hasVariableRefs: hasVariableRefs - }; - var levelsWithKnownCodecs = []; - MASTER_PLAYLIST_REGEX.lastIndex = 0; - var result; - while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) { - if (result[1]) { - var _level$unknownCodecs; - // '#EXT-X-STREAM-INF' is found, parse level tag in group 1 - var attrs = new AttrList(result[1]); - { - substituteVariablesInAttributes(parsed, attrs, ['CODECS', 'SUPPLEMENTAL-CODECS', 'ALLOWED-CPC', 'PATHWAY-ID', 'STABLE-VARIANT-ID', 'AUDIO', 'VIDEO', 'SUBTITLES', 'CLOSED-CAPTIONS', 'NAME']); - } - var uri = substituteVariables(parsed, result[2]) ; - var level = { - attrs: attrs, - bitrate: attrs.decimalInteger('BANDWIDTH') || attrs.decimalInteger('AVERAGE-BANDWIDTH'), - name: attrs.NAME, - url: M3U8Parser.resolve(uri, baseurl) - }; - var resolution = attrs.decimalResolution('RESOLUTION'); - if (resolution) { - level.width = resolution.width; - level.height = resolution.height; - } - setCodecs(attrs.CODECS, level); - if (!((_level$unknownCodecs = level.unknownCodecs) != null && _level$unknownCodecs.length)) { - levelsWithKnownCodecs.push(level); - } - parsed.levels.push(level); - } else if (result[3]) { - var tag = result[3]; - var attributes = result[4]; - switch (tag) { - case 'SESSION-DATA': - { - // #EXT-X-SESSION-DATA - var sessionAttrs = new AttrList(attributes); - { - substituteVariablesInAttributes(parsed, sessionAttrs, ['DATA-ID', 'LANGUAGE', 'VALUE', 'URI']); - } - var dataId = sessionAttrs['DATA-ID']; - if (dataId) { - if (parsed.sessionData === null) { - parsed.sessionData = {}; - } - parsed.sessionData[dataId] = sessionAttrs; - } - break; - } - case 'SESSION-KEY': - { - // #EXT-X-SESSION-KEY - var sessionKey = parseKey(attributes, baseurl, parsed); - if (sessionKey.encrypted && sessionKey.isSupported()) { - if (parsed.sessionKeys === null) { - parsed.sessionKeys = []; - } - parsed.sessionKeys.push(sessionKey); - } else { - logger.warn("[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: \"" + attributes + "\""); - } - break; - } - case 'DEFINE': - { - // #EXT-X-DEFINE - { - var variableAttributes = new AttrList(attributes); - substituteVariablesInAttributes(parsed, variableAttributes, ['NAME', 'VALUE', 'QUERYPARAM']); - addVariableDefinition(parsed, variableAttributes, baseurl); - } - break; - } - case 'CONTENT-STEERING': - { - // #EXT-X-CONTENT-STEERING - var contentSteeringAttributes = new AttrList(attributes); - { - substituteVariablesInAttributes(parsed, contentSteeringAttributes, ['SERVER-URI', 'PATHWAY-ID']); - } - parsed.contentSteering = { - uri: M3U8Parser.resolve(contentSteeringAttributes['SERVER-URI'], baseurl), - pathwayId: contentSteeringAttributes['PATHWAY-ID'] || '.' - }; - break; - } - case 'START': - { - // #EXT-X-START - parsed.startTimeOffset = parseStartTimeOffset(attributes); - break; - } - } - } - } - // Filter out levels with unknown codecs if it does not remove all levels - var stripUnknownCodecLevels = levelsWithKnownCodecs.length > 0 && levelsWithKnownCodecs.length < parsed.levels.length; - parsed.levels = stripUnknownCodecLevels ? levelsWithKnownCodecs : parsed.levels; - if (parsed.levels.length === 0) { - parsed.playlistParsingError = new Error('no levels found in manifest'); - } - return parsed; - }; - M3U8Parser.parseMasterPlaylistMedia = function parseMasterPlaylistMedia(string, baseurl, parsed) { - var result; - var results = {}; - var levels = parsed.levels; - var groupsByType = { - AUDIO: levels.map(function (level) { - return { - id: level.attrs.AUDIO, - audioCodec: level.audioCodec - }; - }), - SUBTITLES: levels.map(function (level) { - return { - id: level.attrs.SUBTITLES, - textCodec: level.textCodec - }; - }), - 'CLOSED-CAPTIONS': [] - }; - var id = 0; - MASTER_PLAYLIST_MEDIA_REGEX.lastIndex = 0; - while ((result = MASTER_PLAYLIST_MEDIA_REGEX.exec(string)) !== null) { - var attrs = new AttrList(result[1]); - var type = attrs.TYPE; - if (type) { - var groups = groupsByType[type]; - var medias = results[type] || []; - results[type] = medias; - { - substituteVariablesInAttributes(parsed, attrs, ['URI', 'GROUP-ID', 'LANGUAGE', 'ASSOC-LANGUAGE', 'STABLE-RENDITION-ID', 'NAME', 'INSTREAM-ID', 'CHARACTERISTICS', 'CHANNELS']); - } - var lang = attrs.LANGUAGE; - var assocLang = attrs['ASSOC-LANGUAGE']; - var channels = attrs.CHANNELS; - var characteristics = attrs.CHARACTERISTICS; - var instreamId = attrs['INSTREAM-ID']; - var media = { - attrs: attrs, - bitrate: 0, - id: id++, - groupId: attrs['GROUP-ID'] || '', - name: attrs.NAME || lang || '', - type: type, - default: attrs.bool('DEFAULT'), - autoselect: attrs.bool('AUTOSELECT'), - forced: attrs.bool('FORCED'), - lang: lang, - url: attrs.URI ? M3U8Parser.resolve(attrs.URI, baseurl) : '' - }; - if (assocLang) { - media.assocLang = assocLang; - } - if (channels) { - media.channels = channels; - } - if (characteristics) { - media.characteristics = characteristics; - } - if (instreamId) { - media.instreamId = instreamId; - } - if (groups != null && groups.length) { - // If there are audio or text groups signalled in the manifest, let's look for a matching codec string for this track - // If we don't find the track signalled, lets use the first audio groups codec we have - // Acting as a best guess - var groupCodec = M3U8Parser.findGroup(groups, media.groupId) || groups[0]; - assignCodec(media, groupCodec, 'audioCodec'); - assignCodec(media, groupCodec, 'textCodec'); - } - medias.push(media); - } - } - return results; - }; - M3U8Parser.parseLevelPlaylist = function parseLevelPlaylist(string, baseurl, id, type, levelUrlId, multivariantVariableList) { - var level = new LevelDetails(baseurl); - var fragments = level.fragments; - // The most recent init segment seen (applies to all subsequent segments) - var currentInitSegment = null; - var currentSN = 0; - var currentPart = 0; - var totalduration = 0; - var discontinuityCounter = 0; - var prevFrag = null; - var frag = new Fragment(type, baseurl); - var result; - var i; - var levelkeys; - var firstPdtIndex = -1; - var createNextFrag = false; - var nextByteRange = null; - LEVEL_PLAYLIST_REGEX_FAST.lastIndex = 0; - level.m3u8 = string; - level.hasVariableRefs = hasVariableReferences(string) ; - while ((result = LEVEL_PLAYLIST_REGEX_FAST.exec(string)) !== null) { - if (createNextFrag) { - createNextFrag = false; - frag = new Fragment(type, baseurl); - // setup the next fragment for part loading - frag.start = totalduration; - frag.sn = currentSN; - frag.cc = discontinuityCounter; - frag.level = id; - if (currentInitSegment) { - frag.initSegment = currentInitSegment; - frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime; - currentInitSegment.rawProgramDateTime = null; - if (nextByteRange) { - frag.setByteRange(nextByteRange); - nextByteRange = null; - } - } - } - var duration = result[1]; - if (duration) { - // INF - frag.duration = parseFloat(duration); - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - var title = (' ' + result[2]).slice(1); - frag.title = title || null; - frag.tagList.push(title ? ['INF', duration, title] : ['INF', duration]); - } else if (result[3]) { - // url - if (isFiniteNumber(frag.duration)) { - frag.start = totalduration; - if (levelkeys) { - setFragLevelKeys(frag, levelkeys, level); - } - frag.sn = currentSN; - frag.level = id; - frag.cc = discontinuityCounter; - fragments.push(frag); - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - var uri = (' ' + result[3]).slice(1); - frag.relurl = substituteVariables(level, uri) ; - assignProgramDateTime(frag, prevFrag); - prevFrag = frag; - totalduration += frag.duration; - currentSN++; - currentPart = 0; - createNextFrag = true; - } - } else if (result[4]) { - // X-BYTERANGE - var data = (' ' + result[4]).slice(1); - if (prevFrag) { - frag.setByteRange(data, prevFrag); - } else { - frag.setByteRange(data); - } - } else if (result[5]) { - // PROGRAM-DATE-TIME - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - frag.rawProgramDateTime = (' ' + result[5]).slice(1); - frag.tagList.push(['PROGRAM-DATE-TIME', frag.rawProgramDateTime]); - if (firstPdtIndex === -1) { - firstPdtIndex = fragments.length; - } - } else { - result = result[0].match(LEVEL_PLAYLIST_REGEX_SLOW); - if (!result) { - logger.warn('No matches on slow regex match for level playlist!'); - continue; - } - for (i = 1; i < result.length; i++) { - if (typeof result[i] !== 'undefined') { - break; - } - } - - // avoid sliced strings https://github.com/video-dev/hls.js/issues/939 - var tag = (' ' + result[i]).slice(1); - var value1 = (' ' + result[i + 1]).slice(1); - var value2 = result[i + 2] ? (' ' + result[i + 2]).slice(1) : ''; - switch (tag) { - case 'PLAYLIST-TYPE': - level.type = value1.toUpperCase(); - break; - case 'MEDIA-SEQUENCE': - currentSN = level.startSN = parseInt(value1); - break; - case 'SKIP': - { - var skipAttrs = new AttrList(value1); - { - substituteVariablesInAttributes(level, skipAttrs, ['RECENTLY-REMOVED-DATERANGES']); - } - var skippedSegments = skipAttrs.decimalInteger('SKIPPED-SEGMENTS'); - if (isFiniteNumber(skippedSegments)) { - level.skippedSegments = skippedSegments; - // This will result in fragments[] containing undefined values, which we will fill in with `mergeDetails` - for (var _i = skippedSegments; _i--;) { - fragments.unshift(null); - } - currentSN += skippedSegments; - } - var recentlyRemovedDateranges = skipAttrs.enumeratedString('RECENTLY-REMOVED-DATERANGES'); - if (recentlyRemovedDateranges) { - level.recentlyRemovedDateranges = recentlyRemovedDateranges.split('\t'); - } - break; - } - case 'TARGETDURATION': - level.targetduration = Math.max(parseInt(value1), 1); - break; - case 'VERSION': - level.version = parseInt(value1); - break; - case 'INDEPENDENT-SEGMENTS': - case 'EXTM3U': - break; - case 'ENDLIST': - level.live = false; - break; - case '#': - if (value1 || value2) { - frag.tagList.push(value2 ? [value1, value2] : [value1]); - } - break; - case 'DISCONTINUITY': - discontinuityCounter++; - frag.tagList.push(['DIS']); - break; - case 'GAP': - frag.gap = true; - frag.tagList.push([tag]); - break; - case 'BITRATE': - frag.tagList.push([tag, value1]); - break; - case 'DATERANGE': - { - var dateRangeAttr = new AttrList(value1); - { - substituteVariablesInAttributes(level, dateRangeAttr, ['ID', 'CLASS', 'START-DATE', 'END-DATE', 'SCTE35-CMD', 'SCTE35-OUT', 'SCTE35-IN']); - substituteVariablesInAttributes(level, dateRangeAttr, dateRangeAttr.clientAttrs); - } - var dateRange = new DateRange(dateRangeAttr, level.dateRanges[dateRangeAttr.ID]); - if (dateRange.isValid || level.skippedSegments) { - level.dateRanges[dateRange.id] = dateRange; - } else { - logger.warn("Ignoring invalid DATERANGE tag: \"" + value1 + "\""); - } - // Add to fragment tag list for backwards compatibility (< v1.2.0) - frag.tagList.push(['EXT-X-DATERANGE', value1]); - break; - } - case 'DEFINE': - { - { - var variableAttributes = new AttrList(value1); - substituteVariablesInAttributes(level, variableAttributes, ['NAME', 'VALUE', 'IMPORT', 'QUERYPARAM']); - if ('IMPORT' in variableAttributes) { - importVariableDefinition(level, variableAttributes, multivariantVariableList); - } else { - addVariableDefinition(level, variableAttributes, baseurl); - } - } - break; - } - case 'DISCONTINUITY-SEQUENCE': - discontinuityCounter = parseInt(value1); - break; - case 'KEY': - { - var levelKey = parseKey(value1, baseurl, level); - if (levelKey.isSupported()) { - if (levelKey.method === 'NONE') { - levelkeys = undefined; - break; - } - if (!levelkeys) { - levelkeys = {}; - } - if (levelkeys[levelKey.keyFormat]) { - levelkeys = _extends({}, levelkeys); - } - levelkeys[levelKey.keyFormat] = levelKey; - } else { - logger.warn("[Keys] Ignoring invalid EXT-X-KEY tag: \"" + value1 + "\""); - } - break; - } - case 'START': - level.startTimeOffset = parseStartTimeOffset(value1); - break; - case 'MAP': - { - var mapAttrs = new AttrList(value1); - { - substituteVariablesInAttributes(level, mapAttrs, ['BYTERANGE', 'URI']); - } - if (frag.duration) { - // Initial segment tag is after segment duration tag. - // #EXTINF: 6.0 - // #EXT-X-MAP:URI="init.mp4 - var init = new Fragment(type, baseurl); - setInitSegment(init, mapAttrs, id, levelkeys); - currentInitSegment = init; - frag.initSegment = currentInitSegment; - if (currentInitSegment.rawProgramDateTime && !frag.rawProgramDateTime) { - frag.rawProgramDateTime = currentInitSegment.rawProgramDateTime; - } - } else { - // Initial segment tag is before segment duration tag - // Handle case where EXT-X-MAP is declared after EXT-X-BYTERANGE - var end = frag.byteRangeEndOffset; - if (end) { - var start = frag.byteRangeStartOffset; - nextByteRange = end - start + "@" + start; - } else { - nextByteRange = null; - } - setInitSegment(frag, mapAttrs, id, levelkeys); - currentInitSegment = frag; - createNextFrag = true; - } - break; - } - case 'SERVER-CONTROL': - { - var serverControlAttrs = new AttrList(value1); - level.canBlockReload = serverControlAttrs.bool('CAN-BLOCK-RELOAD'); - level.canSkipUntil = serverControlAttrs.optionalFloat('CAN-SKIP-UNTIL', 0); - level.canSkipDateRanges = level.canSkipUntil > 0 && serverControlAttrs.bool('CAN-SKIP-DATERANGES'); - level.partHoldBack = serverControlAttrs.optionalFloat('PART-HOLD-BACK', 0); - level.holdBack = serverControlAttrs.optionalFloat('HOLD-BACK', 0); - break; - } - case 'PART-INF': - { - var partInfAttrs = new AttrList(value1); - level.partTarget = partInfAttrs.decimalFloatingPoint('PART-TARGET'); - break; - } - case 'PART': - { - var partList = level.partList; - if (!partList) { - partList = level.partList = []; - } - var previousFragmentPart = currentPart > 0 ? partList[partList.length - 1] : undefined; - var index = currentPart++; - var partAttrs = new AttrList(value1); - { - substituteVariablesInAttributes(level, partAttrs, ['BYTERANGE', 'URI']); - } - var part = new Part(partAttrs, frag, baseurl, index, previousFragmentPart); - partList.push(part); - frag.duration += part.duration; - break; - } - case 'PRELOAD-HINT': - { - var preloadHintAttrs = new AttrList(value1); - { - substituteVariablesInAttributes(level, preloadHintAttrs, ['URI']); - } - level.preloadHint = preloadHintAttrs; - break; - } - case 'RENDITION-REPORT': - { - var renditionReportAttrs = new AttrList(value1); - { - substituteVariablesInAttributes(level, renditionReportAttrs, ['URI']); - } - level.renditionReports = level.renditionReports || []; - level.renditionReports.push(renditionReportAttrs); - break; - } - default: - logger.warn("line parsed but not handled: " + result); - break; - } - } - } - if (prevFrag && !prevFrag.relurl) { - fragments.pop(); - totalduration -= prevFrag.duration; - if (level.partList) { - level.fragmentHint = prevFrag; - } - } else if (level.partList) { - assignProgramDateTime(frag, prevFrag); - frag.cc = discontinuityCounter; - level.fragmentHint = frag; - if (levelkeys) { - setFragLevelKeys(frag, levelkeys, level); - } - } - var fragmentLength = fragments.length; - var firstFragment = fragments[0]; - var lastFragment = fragments[fragmentLength - 1]; - totalduration += level.skippedSegments * level.targetduration; - if (totalduration > 0 && fragmentLength && lastFragment) { - level.averagetargetduration = totalduration / fragmentLength; - var lastSn = lastFragment.sn; - level.endSN = lastSn !== 'initSegment' ? lastSn : 0; - if (!level.live) { - lastFragment.endList = true; - } - if (firstFragment) { - level.startCC = firstFragment.cc; - } - } else { - level.endSN = 0; - level.startCC = 0; - } - if (level.fragmentHint) { - totalduration += level.fragmentHint.duration; - } - level.totalduration = totalduration; - level.endCC = discontinuityCounter; - - /** - * Backfill any missing PDT values - * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after - * one or more Media Segment URIs, the client SHOULD extrapolate - * backward from that tag (using EXTINF durations and/or media - * timestamps) to associate dates with those segments." - * We have already extrapolated forward, but all fragments up to the first instance of PDT do not have their PDTs - * computed. - */ - if (firstPdtIndex > 0) { - backfillProgramDateTimes(fragments, firstPdtIndex); - } - return level; - }; - return M3U8Parser; - }(); - function parseKey(keyTagAttributes, baseurl, parsed) { - var _keyAttrs$METHOD, _keyAttrs$KEYFORMAT; - // https://tools.ietf.org/html/rfc8216#section-4.3.2.4 - var keyAttrs = new AttrList(keyTagAttributes); - { - substituteVariablesInAttributes(parsed, keyAttrs, ['KEYFORMAT', 'KEYFORMATVERSIONS', 'URI', 'IV', 'URI']); - } - var decryptmethod = (_keyAttrs$METHOD = keyAttrs.METHOD) != null ? _keyAttrs$METHOD : ''; - var decrypturi = keyAttrs.URI; - var decryptiv = keyAttrs.hexadecimalInteger('IV'); - var decryptkeyformatversions = keyAttrs.KEYFORMATVERSIONS; - // From RFC: This attribute is OPTIONAL; its absence indicates an implicit value of "identity". - var decryptkeyformat = (_keyAttrs$KEYFORMAT = keyAttrs.KEYFORMAT) != null ? _keyAttrs$KEYFORMAT : 'identity'; - if (decrypturi && keyAttrs.IV && !decryptiv) { - logger.error("Invalid IV: " + keyAttrs.IV); - } - // If decrypturi is a URI with a scheme, then baseurl will be ignored - // No uri is allowed when METHOD is NONE - var resolvedUri = decrypturi ? M3U8Parser.resolve(decrypturi, baseurl) : ''; - var keyFormatVersions = (decryptkeyformatversions ? decryptkeyformatversions : '1').split('/').map(Number).filter(Number.isFinite); - return new LevelKey(decryptmethod, resolvedUri, decryptkeyformat, keyFormatVersions, decryptiv); - } - function parseStartTimeOffset(startAttributes) { - var startAttrs = new AttrList(startAttributes); - var startTimeOffset = startAttrs.decimalFloatingPoint('TIME-OFFSET'); - if (isFiniteNumber(startTimeOffset)) { - return startTimeOffset; - } - return null; - } - function setCodecs(codecsAttributeValue, level) { - var codecs = (codecsAttributeValue || '').split(/[ ,]+/).filter(function (c) { - return c; - }); - ['video', 'audio', 'text'].forEach(function (type) { - var filtered = codecs.filter(function (codec) { - return isCodecType(codec, type); - }); - if (filtered.length) { - // Comma separated list of all codecs for type - level[type + "Codec"] = filtered.join(','); - // Remove known codecs so that only unknownCodecs are left after iterating through each type - codecs = codecs.filter(function (codec) { - return filtered.indexOf(codec) === -1; - }); - } - }); - level.unknownCodecs = codecs; - } - function assignCodec(media, groupItem, codecProperty) { - var codecValue = groupItem[codecProperty]; - if (codecValue) { - media[codecProperty] = codecValue; - } - } - function backfillProgramDateTimes(fragments, firstPdtIndex) { - var fragPrev = fragments[firstPdtIndex]; - for (var i = firstPdtIndex; i--;) { - var frag = fragments[i]; - // Exit on delta-playlist skipped segments - if (!frag) { - return; - } - frag.programDateTime = fragPrev.programDateTime - frag.duration * 1000; - fragPrev = frag; - } - } - function assignProgramDateTime(frag, prevFrag) { - if (frag.rawProgramDateTime) { - frag.programDateTime = Date.parse(frag.rawProgramDateTime); - } else if (prevFrag != null && prevFrag.programDateTime) { - frag.programDateTime = prevFrag.endProgramDateTime; - } - if (!isFiniteNumber(frag.programDateTime)) { - frag.programDateTime = null; - frag.rawProgramDateTime = null; - } - } - function setInitSegment(frag, mapAttrs, id, levelkeys) { - frag.relurl = mapAttrs.URI; - if (mapAttrs.BYTERANGE) { - frag.setByteRange(mapAttrs.BYTERANGE); - } - frag.level = id; - frag.sn = 'initSegment'; - if (levelkeys) { - frag.levelkeys = levelkeys; - } - frag.initSegment = null; - } - function setFragLevelKeys(frag, levelkeys, level) { - frag.levelkeys = levelkeys; - var encryptedFragments = level.encryptedFragments; - if ((!encryptedFragments.length || encryptedFragments[encryptedFragments.length - 1].levelkeys !== levelkeys) && Object.keys(levelkeys).some(function (format) { - return levelkeys[format].isCommonEncryption; - })) { - encryptedFragments.push(frag); - } - } - - var PlaylistContextType = { - MANIFEST: "manifest", - LEVEL: "level", - AUDIO_TRACK: "audioTrack", - SUBTITLE_TRACK: "subtitleTrack" - }; - var PlaylistLevelType = { - MAIN: "main", - AUDIO: "audio", - SUBTITLE: "subtitle" - }; - - function mapContextToLevelType(context) { - var type = context.type; - switch (type) { - case PlaylistContextType.AUDIO_TRACK: - return PlaylistLevelType.AUDIO; - case PlaylistContextType.SUBTITLE_TRACK: - return PlaylistLevelType.SUBTITLE; - default: - return PlaylistLevelType.MAIN; - } - } - function getResponseUrl(response, context) { - var url = response.url; - // responseURL not supported on some browsers (it is used to detect URL redirection) - // data-uri mode also not supported (but no need to detect redirection) - if (url === undefined || url.indexOf('data:') === 0) { - // fallback to initial URL - url = context.url; - } - return url; - } - var PlaylistLoader = /*#__PURE__*/function () { - function PlaylistLoader(hls) { - this.hls = void 0; - this.loaders = Object.create(null); - this.variableList = null; - this.hls = hls; - this.registerListeners(); - } - var _proto = PlaylistLoader.prototype; - _proto.startLoad = function startLoad(startPosition) {}; - _proto.stopLoad = function stopLoad() { - this.destroyInternalLoaders(); - }; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); - hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this); - hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); - hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this); - hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this); - } - - /** - * Returns defaults or configured loader-type overloads (pLoader and loader config params) - */; - _proto.createInternalLoader = function createInternalLoader(context) { - var config = this.hls.config; - var PLoader = config.pLoader; - var Loader = config.loader; - var InternalLoader = PLoader || Loader; - var loader = new InternalLoader(config); - this.loaders[context.type] = loader; - return loader; - }; - _proto.getInternalLoader = function getInternalLoader(context) { - return this.loaders[context.type]; - }; - _proto.resetInternalLoader = function resetInternalLoader(contextType) { - if (this.loaders[contextType]) { - delete this.loaders[contextType]; - } - } - - /** - * Call `destroy` on all internal loader instances mapped (one per context type) - */; - _proto.destroyInternalLoaders = function destroyInternalLoaders() { - for (var contextType in this.loaders) { - var loader = this.loaders[contextType]; - if (loader) { - loader.destroy(); - } - this.resetInternalLoader(contextType); - } - }; - _proto.destroy = function destroy() { - this.variableList = null; - this.unregisterListeners(); - this.destroyInternalLoaders(); - }; - _proto.onManifestLoading = function onManifestLoading(event, data) { - var url = data.url; - this.variableList = null; - this.load({ - id: null, - level: 0, - responseType: 'text', - type: PlaylistContextType.MANIFEST, - url: url, - deliveryDirectives: null - }); - }; - _proto.onLevelLoading = function onLevelLoading(event, data) { - var id = data.id, - level = data.level, - pathwayId = data.pathwayId, - url = data.url, - deliveryDirectives = data.deliveryDirectives; - this.load({ - id: id, - level: level, - pathwayId: pathwayId, - responseType: 'text', - type: PlaylistContextType.LEVEL, - url: url, - deliveryDirectives: deliveryDirectives - }); - }; - _proto.onAudioTrackLoading = function onAudioTrackLoading(event, data) { - var id = data.id, - groupId = data.groupId, - url = data.url, - deliveryDirectives = data.deliveryDirectives; - this.load({ - id: id, - groupId: groupId, - level: null, - responseType: 'text', - type: PlaylistContextType.AUDIO_TRACK, - url: url, - deliveryDirectives: deliveryDirectives - }); - }; - _proto.onSubtitleTrackLoading = function onSubtitleTrackLoading(event, data) { - var id = data.id, - groupId = data.groupId, - url = data.url, - deliveryDirectives = data.deliveryDirectives; - this.load({ - id: id, - groupId: groupId, - level: null, - responseType: 'text', - type: PlaylistContextType.SUBTITLE_TRACK, - url: url, - deliveryDirectives: deliveryDirectives - }); - }; - _proto.load = function load(context) { - var _context$deliveryDire, - _this = this; - var config = this.hls.config; - - // logger.debug(`[playlist-loader]: Loading playlist of type ${context.type}, level: ${context.level}, id: ${context.id}`); - - // Check if a loader for this context already exists - var loader = this.getInternalLoader(context); - if (loader) { - var loaderContext = loader.context; - if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) { - // same URL can't overlap - logger.trace('[playlist-loader]: playlist request ongoing'); - return; - } - logger.log("[playlist-loader]: aborting previous loader for type: " + context.type); - loader.abort(); - } - - // apply different configs for retries depending on - // context (manifest, level, audio/subs playlist) - var loadPolicy; - if (context.type === PlaylistContextType.MANIFEST) { - loadPolicy = config.manifestLoadPolicy.default; - } else { - loadPolicy = _extends({}, config.playlistLoadPolicy.default, { - timeoutRetry: null, - errorRetry: null - }); - } - loader = this.createInternalLoader(context); - - // Override level/track timeout for LL-HLS requests - // (the default of 10000ms is counter productive to blocking playlist reload requests) - if (isFiniteNumber((_context$deliveryDire = context.deliveryDirectives) == null ? void 0 : _context$deliveryDire.part)) { - var levelDetails; - if (context.type === PlaylistContextType.LEVEL && context.level !== null) { - levelDetails = this.hls.levels[context.level].details; - } else if (context.type === PlaylistContextType.AUDIO_TRACK && context.id !== null) { - levelDetails = this.hls.audioTracks[context.id].details; - } else if (context.type === PlaylistContextType.SUBTITLE_TRACK && context.id !== null) { - levelDetails = this.hls.subtitleTracks[context.id].details; - } - if (levelDetails) { - var partTarget = levelDetails.partTarget; - var targetDuration = levelDetails.targetduration; - if (partTarget && targetDuration) { - var maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1000; - loadPolicy = _extends({}, loadPolicy, { - maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs), - maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs) - }); - } - } - } - var legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {}; - var loaderConfig = { - loadPolicy: loadPolicy, - timeout: loadPolicy.maxLoadTimeMs, - maxRetry: legacyRetryCompatibility.maxNumRetry || 0, - retryDelay: legacyRetryCompatibility.retryDelayMs || 0, - maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0 - }; - var loaderCallbacks = { - onSuccess: function onSuccess(response, stats, context, networkDetails) { - var loader = _this.getInternalLoader(context); - _this.resetInternalLoader(context.type); - var string = response.data; - - // Validate if it is an M3U8 at all - if (string.indexOf('#EXTM3U') !== 0) { - _this.handleManifestParsingError(response, context, new Error('no EXTM3U delimiter'), networkDetails || null, stats); - return; - } - stats.parsing.start = performance.now(); - if (M3U8Parser.isMediaPlaylist(string)) { - _this.handleTrackOrLevelPlaylist(response, stats, context, networkDetails || null, loader); - } else { - _this.handleMasterPlaylist(response, stats, context, networkDetails); - } - }, - onError: function onError(response, context, networkDetails, stats) { - _this.handleNetworkError(context, networkDetails, false, response, stats); - }, - onTimeout: function onTimeout(stats, context, networkDetails) { - _this.handleNetworkError(context, networkDetails, true, undefined, stats); - } - }; - - // logger.debug(`[playlist-loader]: Calling internal loader delegate for URL: ${context.url}`); - - loader.load(context, loaderConfig, loaderCallbacks); - }; - _proto.handleMasterPlaylist = function handleMasterPlaylist(response, stats, context, networkDetails) { - var hls = this.hls; - var string = response.data; - var url = getResponseUrl(response, context); - var parsedResult = M3U8Parser.parseMasterPlaylist(string, url); - if (parsedResult.playlistParsingError) { - this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats); - return; - } - var contentSteering = parsedResult.contentSteering, - levels = parsedResult.levels, - sessionData = parsedResult.sessionData, - sessionKeys = parsedResult.sessionKeys, - startTimeOffset = parsedResult.startTimeOffset, - variableList = parsedResult.variableList; - this.variableList = variableList; - var _M3U8Parser$parseMast = M3U8Parser.parseMasterPlaylistMedia(string, url, parsedResult), - _M3U8Parser$parseMast2 = _M3U8Parser$parseMast.AUDIO, - audioTracks = _M3U8Parser$parseMast2 === void 0 ? [] : _M3U8Parser$parseMast2, - subtitles = _M3U8Parser$parseMast.SUBTITLES, - captions = _M3U8Parser$parseMast['CLOSED-CAPTIONS']; - if (audioTracks.length) { - // check if we have found an audio track embedded in main playlist (audio track without URI attribute) - var embeddedAudioFound = audioTracks.some(function (audioTrack) { - return !audioTrack.url; - }); - - // if no embedded audio track defined, but audio codec signaled in quality level, - // we need to signal this main audio track this could happen with playlists with - // alt audio rendition in which quality levels (main) - // contains both audio+video. but with mixed audio track not signaled - if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) { - logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one'); - audioTracks.unshift({ - type: 'main', - name: 'main', - groupId: 'main', - default: false, - autoselect: false, - forced: false, - id: -1, - attrs: new AttrList({}), - bitrate: 0, - url: '' - }); - } - } - hls.trigger(Events.MANIFEST_LOADED, { - levels: levels, - audioTracks: audioTracks, - subtitles: subtitles, - captions: captions, - contentSteering: contentSteering, - url: url, - stats: stats, - networkDetails: networkDetails, - sessionData: sessionData, - sessionKeys: sessionKeys, - startTimeOffset: startTimeOffset, - variableList: variableList - }); - }; - _proto.handleTrackOrLevelPlaylist = function handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) { - var hls = this.hls; - var id = context.id, - level = context.level, - type = context.type; - var url = getResponseUrl(response, context); - var levelUrlId = 0; - var levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0; - var levelType = mapContextToLevelType(context); - var levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url, levelId, levelType, levelUrlId, this.variableList); - - // We have done our first request (Manifest-type) and receive - // not a master playlist but a chunk-list (track/level) - // We fire the manifest-loaded event anyway with the parsed level-details - // by creating a single-level structure for it. - if (type === PlaylistContextType.MANIFEST) { - var singleLevel = { - attrs: new AttrList({}), - bitrate: 0, - details: levelDetails, - name: '', - url: url - }; - hls.trigger(Events.MANIFEST_LOADED, { - levels: [singleLevel], - audioTracks: [], - url: url, - stats: stats, - networkDetails: networkDetails, - sessionData: null, - sessionKeys: null, - contentSteering: null, - startTimeOffset: null, - variableList: null - }); - } - - // save parsing time - stats.parsing.end = performance.now(); - - // extend the context with the new levelDetails property - context.levelDetails = levelDetails; - this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader); - }; - _proto.handleManifestParsingError = function handleManifestParsingError(response, context, error, networkDetails, stats) { - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.MANIFEST_PARSING_ERROR, - fatal: context.type === PlaylistContextType.MANIFEST, - url: response.url, - err: error, - error: error, - reason: error.message, - response: response, - context: context, - networkDetails: networkDetails, - stats: stats - }); - }; - _proto.handleNetworkError = function handleNetworkError(context, networkDetails, timeout, response, stats) { - if (timeout === void 0) { - timeout = false; - } - var message = "A network " + (timeout ? 'timeout' : 'error' + (response ? ' (status ' + response.code + ')' : '')) + " occurred while loading " + context.type; - if (context.type === PlaylistContextType.LEVEL) { - message += ": " + context.level + " id: " + context.id; - } else if (context.type === PlaylistContextType.AUDIO_TRACK || context.type === PlaylistContextType.SUBTITLE_TRACK) { - message += " id: " + context.id + " group-id: \"" + context.groupId + "\""; - } - var error = new Error(message); - logger.warn("[playlist-loader]: " + message); - var details = ErrorDetails.UNKNOWN; - var fatal = false; - var loader = this.getInternalLoader(context); - switch (context.type) { - case PlaylistContextType.MANIFEST: - details = timeout ? ErrorDetails.MANIFEST_LOAD_TIMEOUT : ErrorDetails.MANIFEST_LOAD_ERROR; - fatal = true; - break; - case PlaylistContextType.LEVEL: - details = timeout ? ErrorDetails.LEVEL_LOAD_TIMEOUT : ErrorDetails.LEVEL_LOAD_ERROR; - fatal = false; - break; - case PlaylistContextType.AUDIO_TRACK: - details = timeout ? ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT : ErrorDetails.AUDIO_TRACK_LOAD_ERROR; - fatal = false; - break; - case PlaylistContextType.SUBTITLE_TRACK: - details = timeout ? ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT : ErrorDetails.SUBTITLE_LOAD_ERROR; - fatal = false; - break; - } - if (loader) { - this.resetInternalLoader(context.type); - } - var errorData = { - type: ErrorTypes.NETWORK_ERROR, - details: details, - fatal: fatal, - url: context.url, - loader: loader, - context: context, - error: error, - networkDetails: networkDetails, - stats: stats - }; - if (response) { - var url = (networkDetails == null ? void 0 : networkDetails.url) || context.url; - errorData.response = _objectSpread2({ - url: url, - data: undefined - }, response); - } - this.hls.trigger(Events.ERROR, errorData); - }; - _proto.handlePlaylistLoaded = function handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) { - var hls = this.hls; - var type = context.type, - level = context.level, - id = context.id, - groupId = context.groupId, - deliveryDirectives = context.deliveryDirectives; - var url = getResponseUrl(response, context); - var parent = mapContextToLevelType(context); - var levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? level : undefined; - if (!levelDetails.fragments.length) { - var _error = new Error('No Segments found in Playlist'); - hls.trigger(Events.ERROR, { - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.LEVEL_EMPTY_ERROR, - fatal: false, - url: url, - error: _error, - reason: _error.message, - response: response, - context: context, - level: levelIndex, - parent: parent, - networkDetails: networkDetails, - stats: stats - }); - return; - } - if (!levelDetails.targetduration) { - levelDetails.playlistParsingError = new Error('Missing Target Duration'); - } - var error = levelDetails.playlistParsingError; - if (error) { - hls.trigger(Events.ERROR, { - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.LEVEL_PARSING_ERROR, - fatal: false, - url: url, - error: error, - reason: error.message, - response: response, - context: context, - level: levelIndex, - parent: parent, - networkDetails: networkDetails, - stats: stats - }); - return; - } - if (levelDetails.live && loader) { - if (loader.getCacheAge) { - levelDetails.ageHeader = loader.getCacheAge() || 0; - } - if (!loader.getCacheAge || isNaN(levelDetails.ageHeader)) { - levelDetails.ageHeader = 0; - } - } - switch (type) { - case PlaylistContextType.MANIFEST: - case PlaylistContextType.LEVEL: - hls.trigger(Events.LEVEL_LOADED, { - details: levelDetails, - level: levelIndex || 0, - id: id || 0, - stats: stats, - networkDetails: networkDetails, - deliveryDirectives: deliveryDirectives - }); - break; - case PlaylistContextType.AUDIO_TRACK: - hls.trigger(Events.AUDIO_TRACK_LOADED, { - details: levelDetails, - id: id || 0, - groupId: groupId || '', - stats: stats, - networkDetails: networkDetails, - deliveryDirectives: deliveryDirectives - }); - break; - case PlaylistContextType.SUBTITLE_TRACK: - hls.trigger(Events.SUBTITLE_TRACK_LOADED, { - details: levelDetails, - id: id || 0, - groupId: groupId || '', - stats: stats, - networkDetails: networkDetails, - deliveryDirectives: deliveryDirectives - }); - break; - } - }; - return PlaylistLoader; - }(); - - function sendAddTrackEvent(track, videoEl) { - var event; - try { - event = new Event('addtrack'); - } catch (err) { - // for IE11 - event = document.createEvent('Event'); - event.initEvent('addtrack', false, false); - } - event.track = track; - videoEl.dispatchEvent(event); - } - function addCueToTrack(track, cue) { - // Sometimes there are cue overlaps on segmented vtts so the same - // cue can appear more than once in different vtt files. - // This avoid showing duplicated cues with same timecode and text. - var mode = track.mode; - if (mode === 'disabled') { - track.mode = 'hidden'; - } - if (track.cues && !track.cues.getCueById(cue.id)) { - try { - track.addCue(cue); - if (!track.cues.getCueById(cue.id)) { - throw new Error("addCue is failed for: " + cue); - } - } catch (err) { - logger.debug("[texttrack-utils]: " + err); - try { - var textTrackCue = new self.TextTrackCue(cue.startTime, cue.endTime, cue.text); - textTrackCue.id = cue.id; - track.addCue(textTrackCue); - } catch (err2) { - logger.debug("[texttrack-utils]: Legacy TextTrackCue fallback failed: " + err2); - } - } - } - if (mode === 'disabled') { - track.mode = mode; - } - } - function clearCurrentCues(track) { - // When track.mode is disabled, track.cues will be null. - // To guarantee the removal of cues, we need to temporarily - // change the mode to hidden - var mode = track.mode; - if (mode === 'disabled') { - track.mode = 'hidden'; - } - if (track.cues) { - for (var i = track.cues.length; i--;) { - track.removeCue(track.cues[i]); - } - } - if (mode === 'disabled') { - track.mode = mode; - } - } - function removeCuesInRange(track, start, end, predicate) { - var mode = track.mode; - if (mode === 'disabled') { - track.mode = 'hidden'; - } - if (track.cues && track.cues.length > 0) { - var cues = getCuesInRange(track.cues, start, end); - for (var i = 0; i < cues.length; i++) { - if (!predicate || predicate(cues[i])) { - track.removeCue(cues[i]); - } - } - } - if (mode === 'disabled') { - track.mode = mode; - } - } - - // Find first cue starting after given time. - // Modified version of binary search O(log(n)). - function getFirstCueIndexAfterTime(cues, time) { - // If first cue starts after time, start there - if (time < cues[0].startTime) { - return 0; - } - // If the last cue ends before time there is no overlap - var len = cues.length - 1; - if (time > cues[len].endTime) { - return -1; - } - var left = 0; - var right = len; - while (left <= right) { - var mid = Math.floor((right + left) / 2); - if (time < cues[mid].startTime) { - right = mid - 1; - } else if (time > cues[mid].startTime && left < len) { - left = mid + 1; - } else { - // If it's not lower or higher, it must be equal. - return mid; - } - } - // At this point, left and right have swapped. - // No direct match was found, left or right element must be the closest. Check which one has the smallest diff. - return cues[left].startTime - time < time - cues[right].startTime ? left : right; - } - function getCuesInRange(cues, start, end) { - var cuesFound = []; - var firstCueInRange = getFirstCueIndexAfterTime(cues, start); - if (firstCueInRange > -1) { - for (var i = firstCueInRange, len = cues.length; i < len; i++) { - var _cue = cues[i]; - if (_cue.startTime >= start && _cue.endTime <= end) { - cuesFound.push(_cue); - } else if (_cue.startTime > end) { - return cuesFound; - } - } - } - return cuesFound; - } - function filterSubtitleTracks(textTrackList) { - var tracks = []; - for (var i = 0; i < textTrackList.length; i++) { - var track = textTrackList[i]; - // Edge adds a track without a label; we don't want to use it - if ((track.kind === 'subtitles' || track.kind === 'captions') && track.label) { - tracks.push(textTrackList[i]); - } - } - return tracks; - } - - var MetadataSchema = { - audioId3: "org.id3", - dateRange: "com.apple.quicktime.HLS", - emsg: "https://aomedia.org/emsg/ID3" - }; - - var MIN_CUE_DURATION = 0.25; - function getCueClass() { - if (typeof self === 'undefined') return undefined; - return self.VTTCue || self.TextTrackCue; - } - function createCueWithDataFields(Cue, startTime, endTime, data, type) { - var cue = new Cue(startTime, endTime, ''); - try { - cue.value = data; - if (type) { - cue.type = type; - } - } catch (e) { - cue = new Cue(startTime, endTime, JSON.stringify(type ? _objectSpread2({ - type: type - }, data) : data)); - } - return cue; - } - - // VTTCue latest draft allows an infinite duration, fallback - // to MAX_VALUE if necessary - var MAX_CUE_ENDTIME = function () { - var Cue = getCueClass(); - try { - Cue && new Cue(0, Number.POSITIVE_INFINITY, ''); - } catch (e) { - return Number.MAX_VALUE; - } - return Number.POSITIVE_INFINITY; - }(); - function dateRangeDateToTimelineSeconds(date, offset) { - return date.getTime() / 1000 - offset; - } - function hexToArrayBuffer(str) { - return Uint8Array.from(str.replace(/^0x/, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ')).buffer; - } - var ID3TrackController = /*#__PURE__*/function () { - function ID3TrackController(hls) { - this.hls = void 0; - this.id3Track = null; - this.media = null; - this.dateRangeCuesAppended = {}; - this.hls = hls; - this._registerListeners(); - } - var _proto = ID3TrackController.prototype; - _proto.destroy = function destroy() { - this._unregisterListeners(); - this.id3Track = null; - this.media = null; - this.dateRangeCuesAppended = {}; - // @ts-ignore - this.hls = null; - }; - _proto._registerListeners = function _registerListeners() { - var hls = this.hls; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); - hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - }; - _proto._unregisterListeners = function _unregisterListeners() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); - hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - } - - // Add ID3 metatadata text track. - ; - _proto.onMediaAttached = function onMediaAttached(event, data) { - this.media = data.media; - }; - _proto.onMediaDetaching = function onMediaDetaching() { - if (!this.id3Track) { - return; - } - clearCurrentCues(this.id3Track); - this.id3Track = null; - this.media = null; - this.dateRangeCuesAppended = {}; - }; - _proto.onManifestLoading = function onManifestLoading() { - this.dateRangeCuesAppended = {}; - }; - _proto.createTrack = function createTrack(media) { - var track = this.getID3Track(media.textTracks); - track.mode = 'hidden'; - return track; - }; - _proto.getID3Track = function getID3Track(textTracks) { - if (!this.media) { - return; - } - for (var i = 0; i < textTracks.length; i++) { - var textTrack = textTracks[i]; - if (textTrack.kind === 'metadata' && textTrack.label === 'id3') { - // send 'addtrack' when reusing the textTrack for metadata, - // same as what we do for captions - sendAddTrackEvent(textTrack, this.media); - return textTrack; - } - } - return this.media.addTextTrack('metadata', 'id3'); - }; - _proto.onFragParsingMetadata = function onFragParsingMetadata(event, data) { - if (!this.media) { - return; - } - var _this$hls$config = this.hls.config, - enableEmsgMetadataCues = _this$hls$config.enableEmsgMetadataCues, - enableID3MetadataCues = _this$hls$config.enableID3MetadataCues; - if (!enableEmsgMetadataCues && !enableID3MetadataCues) { - return; - } - var samples = data.samples; - - // create track dynamically - if (!this.id3Track) { - this.id3Track = this.createTrack(this.media); - } - var Cue = getCueClass(); - if (!Cue) { - return; - } - for (var i = 0; i < samples.length; i++) { - var type = samples[i].type; - if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) { - continue; - } - var frames = getID3Frames(samples[i].data); - if (frames) { - var startTime = samples[i].pts; - var endTime = startTime + samples[i].duration; - if (endTime > MAX_CUE_ENDTIME) { - endTime = MAX_CUE_ENDTIME; - } - var timeDiff = endTime - startTime; - if (timeDiff <= 0) { - endTime = startTime + MIN_CUE_DURATION; - } - for (var j = 0; j < frames.length; j++) { - var frame = frames[j]; - // Safari doesn't put the timestamp frame in the TextTrack - if (!isTimeStampFrame(frame)) { - // add a bounds to any unbounded cues - this.updateId3CueEnds(startTime, type); - var cue = createCueWithDataFields(Cue, startTime, endTime, frame, type); - if (cue) { - this.id3Track.addCue(cue); - } - } - } - } - } - }; - _proto.updateId3CueEnds = function updateId3CueEnds(startTime, type) { - var _this$id3Track; - var cues = (_this$id3Track = this.id3Track) == null ? void 0 : _this$id3Track.cues; - if (cues) { - for (var i = cues.length; i--;) { - var cue = cues[i]; - if (cue.type === type && cue.startTime < startTime && cue.endTime === MAX_CUE_ENDTIME) { - cue.endTime = startTime; - } - } - } - }; - _proto.onBufferFlushing = function onBufferFlushing(event, _ref) { - var startOffset = _ref.startOffset, - endOffset = _ref.endOffset, - type = _ref.type; - var id3Track = this.id3Track, - hls = this.hls; - if (!hls) { - return; - } - var _hls$config = hls.config, - enableEmsgMetadataCues = _hls$config.enableEmsgMetadataCues, - enableID3MetadataCues = _hls$config.enableID3MetadataCues; - if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) { - var predicate; - if (type === 'audio') { - predicate = function predicate(cue) { - return cue.type === MetadataSchema.audioId3 && enableID3MetadataCues; - }; - } else if (type === 'video') { - predicate = function predicate(cue) { - return cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; - }; - } else { - predicate = function predicate(cue) { - return cue.type === MetadataSchema.audioId3 && enableID3MetadataCues || cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; - }; - } - removeCuesInRange(id3Track, startOffset, endOffset, predicate); - } - }; - _proto.onLevelUpdated = function onLevelUpdated(event, _ref2) { - var _this = this; - var details = _ref2.details; - if (!this.media || !details.hasProgramDateTime || !this.hls.config.enableDateRangeMetadataCues) { - return; - } - var dateRangeCuesAppended = this.dateRangeCuesAppended, - id3Track = this.id3Track; - var dateRanges = details.dateRanges; - var ids = Object.keys(dateRanges); - // Remove cues from track not found in details.dateRanges - if (id3Track) { - var idsToRemove = Object.keys(dateRangeCuesAppended).filter(function (id) { - return !ids.includes(id); - }); - var _loop = function _loop() { - var id = idsToRemove[i]; - Object.keys(dateRangeCuesAppended[id].cues).forEach(function (key) { - id3Track.removeCue(dateRangeCuesAppended[id].cues[key]); - }); - delete dateRangeCuesAppended[id]; - }; - for (var i = idsToRemove.length; i--;) { - _loop(); - } - } - // Exit if the playlist does not have Date Ranges or does not have Program Date Time - var lastFragment = details.fragments[details.fragments.length - 1]; - if (ids.length === 0 || !isFiniteNumber(lastFragment == null ? void 0 : lastFragment.programDateTime)) { - return; - } - if (!this.id3Track) { - this.id3Track = this.createTrack(this.media); - } - var dateTimeOffset = lastFragment.programDateTime / 1000 - lastFragment.start; - var Cue = getCueClass(); - var _loop2 = function _loop2() { - var id = ids[_i]; - var dateRange = dateRanges[id]; - var startTime = dateRangeDateToTimelineSeconds(dateRange.startDate, dateTimeOffset); - - // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT) - var appendedDateRangeCues = dateRangeCuesAppended[id]; - var cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {}; - var durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false; - var endTime = MAX_CUE_ENDTIME; - var endDate = dateRange.endDate; - if (endDate) { - endTime = dateRangeDateToTimelineSeconds(endDate, dateTimeOffset); - durationKnown = true; - } else if (dateRange.endOnNext && !durationKnown) { - var nextDateRangeWithSameClass = ids.reduce(function (candidateDateRange, id) { - if (id !== dateRange.id) { - var otherDateRange = dateRanges[id]; - if (otherDateRange.class === dateRange.class && otherDateRange.startDate > dateRange.startDate && (!candidateDateRange || dateRange.startDate < candidateDateRange.startDate)) { - return otherDateRange; - } - } - return candidateDateRange; - }, null); - if (nextDateRangeWithSameClass) { - endTime = dateRangeDateToTimelineSeconds(nextDateRangeWithSameClass.startDate, dateTimeOffset); - durationKnown = true; - } - } - - // Create TextTrack Cues for each MetadataGroup Item (select DateRange attribute) - // This is to emulate Safari HLS playback handling of DateRange tags - var attributes = Object.keys(dateRange.attr); - for (var j = 0; j < attributes.length; j++) { - var key = attributes[j]; - if (!isDateRangeCueAttribute(key)) { - continue; - } - var cue = cues[key]; - if (cue) { - if (durationKnown && !appendedDateRangeCues.durationKnown) { - cue.endTime = endTime; - } - } else if (Cue) { - var data = dateRange.attr[key]; - if (isSCTE35Attribute(key)) { - data = hexToArrayBuffer(data); - } - var _cue = createCueWithDataFields(Cue, startTime, endTime, { - key: key, - data: data - }, MetadataSchema.dateRange); - if (_cue) { - _cue.id = id; - _this.id3Track.addCue(_cue); - cues[key] = _cue; - } - } - } - - // Keep track of processed DateRanges by ID for updating cues with new DateRange tag attributes - dateRangeCuesAppended[id] = { - cues: cues, - dateRange: dateRange, - durationKnown: durationKnown - }; - }; - for (var _i = 0; _i < ids.length; _i++) { - _loop2(); - } - }; - return ID3TrackController; - }(); - - var LatencyController = /*#__PURE__*/function () { - function LatencyController(hls) { - var _this = this; - this.hls = void 0; - this.config = void 0; - this.media = null; - this.levelDetails = null; - this.currentTime = 0; - this.stallCount = 0; - this._latency = null; - this.timeupdateHandler = function () { - return _this.timeupdate(); - }; - this.hls = hls; - this.config = hls.config; - this.registerListeners(); - } - var _proto = LatencyController.prototype; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.onMediaDetaching(); - this.levelDetails = null; - // @ts-ignore - this.hls = this.timeupdateHandler = null; - }; - _proto.registerListeners = function registerListeners() { - this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - this.hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - this.hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - this.hls.on(Events.ERROR, this.onError, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - this.hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - this.hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - this.hls.off(Events.ERROR, this.onError, this); - }; - _proto.onMediaAttached = function onMediaAttached(event, data) { - this.media = data.media; - this.media.addEventListener('timeupdate', this.timeupdateHandler); - }; - _proto.onMediaDetaching = function onMediaDetaching() { - if (this.media) { - this.media.removeEventListener('timeupdate', this.timeupdateHandler); - this.media = null; - } - }; - _proto.onManifestLoading = function onManifestLoading() { - this.levelDetails = null; - this._latency = null; - this.stallCount = 0; - }; - _proto.onLevelUpdated = function onLevelUpdated(event, _ref) { - var details = _ref.details; - this.levelDetails = details; - if (details.advanced) { - this.timeupdate(); - } - if (!details.live && this.media) { - this.media.removeEventListener('timeupdate', this.timeupdateHandler); - } - }; - _proto.onError = function onError(event, data) { - var _this$levelDetails; - if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) { - return; - } - this.stallCount++; - if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) { - logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency'); - } - }; - _proto.timeupdate = function timeupdate() { - var media = this.media, - levelDetails = this.levelDetails; - if (!media || !levelDetails) { - return; - } - this.currentTime = media.currentTime; - var latency = this.computeLatency(); - if (latency === null) { - return; - } - this._latency = latency; - - // Adapt playbackRate to meet target latency in low-latency mode - var _this$config = this.config, - lowLatencyMode = _this$config.lowLatencyMode, - maxLiveSyncPlaybackRate = _this$config.maxLiveSyncPlaybackRate; - if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) { - return; - } - var targetLatency = this.targetLatency; - if (targetLatency === null) { - return; - } - var distanceFromTarget = latency - targetLatency; - // Only adjust playbackRate when within one target duration of targetLatency - // and more than one second from under-buffering. - // Playback further than one target duration from target can be considered DVR playback. - var liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration); - var inLiveRange = distanceFromTarget < liveMinLatencyDuration; - if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) { - var max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate)); - var rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20; - media.playbackRate = Math.min(max, Math.max(1, rate)); - } else if (media.playbackRate !== 1 && media.playbackRate !== 0) { - media.playbackRate = 1; - } - }; - _proto.estimateLiveEdge = function estimateLiveEdge() { - var levelDetails = this.levelDetails; - if (levelDetails === null) { - return null; - } - return levelDetails.edge + levelDetails.age; - }; - _proto.computeLatency = function computeLatency() { - var liveEdge = this.estimateLiveEdge(); - if (liveEdge === null) { - return null; - } - return liveEdge - this.currentTime; - }; - _createClass(LatencyController, [{ - key: "latency", - get: function get() { - return this._latency || 0; - } - }, { - key: "maxLatency", - get: function get() { - var config = this.config, - levelDetails = this.levelDetails; - if (config.liveMaxLatencyDuration !== undefined) { - return config.liveMaxLatencyDuration; - } - return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0; - } - }, { - key: "targetLatency", - get: function get() { - var levelDetails = this.levelDetails; - if (levelDetails === null) { - return null; - } - var holdBack = levelDetails.holdBack, - partHoldBack = levelDetails.partHoldBack, - targetduration = levelDetails.targetduration; - var _this$config2 = this.config, - liveSyncDuration = _this$config2.liveSyncDuration, - liveSyncDurationCount = _this$config2.liveSyncDurationCount, - lowLatencyMode = _this$config2.lowLatencyMode; - var userConfig = this.hls.userConfig; - var targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack; - if (userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) { - targetLatency = liveSyncDuration !== undefined ? liveSyncDuration : liveSyncDurationCount * targetduration; - } - var maxLiveSyncOnStallIncrease = targetduration; - var liveSyncOnStallIncrease = 1.0; - return targetLatency + Math.min(this.stallCount * liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease); - } - }, { - key: "liveSyncPosition", - get: function get() { - var liveEdge = this.estimateLiveEdge(); - var targetLatency = this.targetLatency; - var levelDetails = this.levelDetails; - if (liveEdge === null || targetLatency === null || levelDetails === null) { - return null; - } - var edge = levelDetails.edge; - var syncPosition = liveEdge - targetLatency - this.edgeStalled; - var min = edge - levelDetails.totalduration; - var max = edge - (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration); - return Math.min(Math.max(min, syncPosition), max); - } - }, { - key: "drift", - get: function get() { - var levelDetails = this.levelDetails; - if (levelDetails === null) { - return 1; - } - return levelDetails.drift; - } - }, { - key: "edgeStalled", - get: function get() { - var levelDetails = this.levelDetails; - if (levelDetails === null) { - return 0; - } - var maxLevelUpdateAge = (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3; - return Math.max(levelDetails.age - maxLevelUpdateAge, 0); - } - }, { - key: "forwardBufferLength", - get: function get() { - var media = this.media, - levelDetails = this.levelDetails; - if (!media || !levelDetails) { - return 0; - } - var bufferedRanges = media.buffered.length; - return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime; - } - }]); - return LatencyController; - }(); - - var HdcpLevels = ['NONE', 'TYPE-0', 'TYPE-1', null]; - function isHdcpLevel(value) { - return HdcpLevels.indexOf(value) > -1; - } - var VideoRangeValues = ['SDR', 'PQ', 'HLG']; - function isVideoRange(value) { - return !!value && VideoRangeValues.indexOf(value) > -1; - } - var HlsSkip = { - No: "", - Yes: "YES", - v2: "v2" - }; - function getSkipValue(details) { - var canSkipUntil = details.canSkipUntil, - canSkipDateRanges = details.canSkipDateRanges, - age = details.age; - // A Client SHOULD NOT request a Playlist Delta Update unless it already - // has a version of the Playlist that is no older than one-half of the Skip Boundary. - // @see: https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-6.3.7 - var playlistRecentEnough = age < canSkipUntil / 2; - if (canSkipUntil && playlistRecentEnough) { - if (canSkipDateRanges) { - return HlsSkip.v2; - } - return HlsSkip.Yes; - } - return HlsSkip.No; - } - var HlsUrlParameters = /*#__PURE__*/function () { - function HlsUrlParameters(msn, part, skip) { - this.msn = void 0; - this.part = void 0; - this.skip = void 0; - this.msn = msn; - this.part = part; - this.skip = skip; - } - var _proto = HlsUrlParameters.prototype; - _proto.addDirectives = function addDirectives(uri) { - var url = new self.URL(uri); - if (this.msn !== undefined) { - url.searchParams.set('_HLS_msn', this.msn.toString()); - } - if (this.part !== undefined) { - url.searchParams.set('_HLS_part', this.part.toString()); - } - if (this.skip) { - url.searchParams.set('_HLS_skip', this.skip); - } - return url.href; - }; - return HlsUrlParameters; - }(); - var Level = /*#__PURE__*/function () { - function Level(data) { - this._attrs = void 0; - this.audioCodec = void 0; - this.bitrate = void 0; - this.codecSet = void 0; - this.url = void 0; - this.frameRate = void 0; - this.height = void 0; - this.id = void 0; - this.name = void 0; - this.videoCodec = void 0; - this.width = void 0; - this.details = void 0; - this.fragmentError = 0; - this.loadError = 0; - this.loaded = void 0; - this.realBitrate = 0; - this.supportedPromise = void 0; - this.supportedResult = void 0; - this._avgBitrate = 0; - this._audioGroups = void 0; - this._subtitleGroups = void 0; - // Deprecated (retained for backwards compatibility) - this._urlId = 0; - this.url = [data.url]; - this._attrs = [data.attrs]; - this.bitrate = data.bitrate; - if (data.details) { - this.details = data.details; - } - this.id = data.id || 0; - this.name = data.name; - this.width = data.width || 0; - this.height = data.height || 0; - this.frameRate = data.attrs.optionalFloat('FRAME-RATE', 0); - this._avgBitrate = data.attrs.decimalInteger('AVERAGE-BANDWIDTH'); - this.audioCodec = data.audioCodec; - this.videoCodec = data.videoCodec; - this.codecSet = [data.videoCodec, data.audioCodec].filter(function (c) { - return !!c; - }).map(function (s) { - return s.substring(0, 4); - }).join(','); - this.addGroupId('audio', data.attrs.AUDIO); - this.addGroupId('text', data.attrs.SUBTITLES); - } - var _proto2 = Level.prototype; - _proto2.hasAudioGroup = function hasAudioGroup(groupId) { - return hasGroup(this._audioGroups, groupId); - }; - _proto2.hasSubtitleGroup = function hasSubtitleGroup(groupId) { - return hasGroup(this._subtitleGroups, groupId); - }; - _proto2.addGroupId = function addGroupId(type, groupId) { - if (!groupId) { - return; - } - if (type === 'audio') { - var audioGroups = this._audioGroups; - if (!audioGroups) { - audioGroups = this._audioGroups = []; - } - if (audioGroups.indexOf(groupId) === -1) { - audioGroups.push(groupId); - } - } else if (type === 'text') { - var subtitleGroups = this._subtitleGroups; - if (!subtitleGroups) { - subtitleGroups = this._subtitleGroups = []; - } - if (subtitleGroups.indexOf(groupId) === -1) { - subtitleGroups.push(groupId); - } - } - } - - // Deprecated methods (retained for backwards compatibility) - ; - _proto2.addFallback = function addFallback() {}; - _createClass(Level, [{ - key: "maxBitrate", - get: function get() { - return Math.max(this.realBitrate, this.bitrate); - } - }, { - key: "averageBitrate", - get: function get() { - return this._avgBitrate || this.realBitrate || this.bitrate; - } - }, { - key: "attrs", - get: function get() { - return this._attrs[0]; - } - }, { - key: "codecs", - get: function get() { - return this.attrs.CODECS || ''; - } - }, { - key: "pathwayId", - get: function get() { - return this.attrs['PATHWAY-ID'] || '.'; - } - }, { - key: "videoRange", - get: function get() { - return this.attrs['VIDEO-RANGE'] || 'SDR'; - } - }, { - key: "score", - get: function get() { - return this.attrs.optionalFloat('SCORE', 0); - } - }, { - key: "uri", - get: function get() { - return this.url[0] || ''; - } - }, { - key: "audioGroups", - get: function get() { - return this._audioGroups; - } - }, { - key: "subtitleGroups", - get: function get() { - return this._subtitleGroups; - } - }, { - key: "urlId", - get: function get() { - return 0; - }, - set: function set(value) {} - }, { - key: "audioGroupIds", - get: function get() { - return this.audioGroups ? [this.audioGroupId] : undefined; - } - }, { - key: "textGroupIds", - get: function get() { - return this.subtitleGroups ? [this.textGroupId] : undefined; - } - }, { - key: "audioGroupId", - get: function get() { - var _this$audioGroups; - return (_this$audioGroups = this.audioGroups) == null ? void 0 : _this$audioGroups[0]; - } - }, { - key: "textGroupId", - get: function get() { - var _this$subtitleGroups; - return (_this$subtitleGroups = this.subtitleGroups) == null ? void 0 : _this$subtitleGroups[0]; - } - }]); - return Level; - }(); - function hasGroup(groups, groupId) { - if (!groupId || !groups) { - return false; - } - return groups.indexOf(groupId) !== -1; - } - - function updateFromToPTS(fragFrom, fragTo) { - var fragToPTS = fragTo.startPTS; - // if we know startPTS[toIdx] - if (isFiniteNumber(fragToPTS)) { - // update fragment duration. - // it helps to fix drifts between playlist reported duration and fragment real duration - var duration = 0; - var frag; - if (fragTo.sn > fragFrom.sn) { - duration = fragToPTS - fragFrom.start; - frag = fragFrom; - } else { - duration = fragFrom.start - fragToPTS; - frag = fragTo; - } - if (frag.duration !== duration) { - frag.duration = duration; - } - // we dont know startPTS[toIdx] - } else if (fragTo.sn > fragFrom.sn) { - var contiguous = fragFrom.cc === fragTo.cc; - // TODO: With part-loading end/durations we need to confirm the whole fragment is loaded before using (or setting) minEndPTS - if (contiguous && fragFrom.minEndPTS) { - fragTo.start = fragFrom.start + (fragFrom.minEndPTS - fragFrom.start); - } else { - fragTo.start = fragFrom.start + fragFrom.duration; - } - } else { - fragTo.start = Math.max(fragFrom.start - fragTo.duration, 0); - } - } - function updateFragPTSDTS(details, frag, startPTS, endPTS, startDTS, endDTS) { - var parsedMediaDuration = endPTS - startPTS; - if (parsedMediaDuration <= 0) { - logger.warn('Fragment should have a positive duration', frag); - endPTS = startPTS + frag.duration; - endDTS = startDTS + frag.duration; - } - var maxStartPTS = startPTS; - var minEndPTS = endPTS; - var fragStartPts = frag.startPTS; - var fragEndPts = frag.endPTS; - if (isFiniteNumber(fragStartPts)) { - // delta PTS between audio and video - var deltaPTS = Math.abs(fragStartPts - startPTS); - if (!isFiniteNumber(frag.deltaPTS)) { - frag.deltaPTS = deltaPTS; - } else { - frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS); - } - maxStartPTS = Math.max(startPTS, fragStartPts); - startPTS = Math.min(startPTS, fragStartPts); - startDTS = Math.min(startDTS, frag.startDTS); - minEndPTS = Math.min(endPTS, fragEndPts); - endPTS = Math.max(endPTS, fragEndPts); - endDTS = Math.max(endDTS, frag.endDTS); - } - var drift = startPTS - frag.start; - if (frag.start !== 0) { - frag.start = startPTS; - } - frag.duration = endPTS - frag.start; - frag.startPTS = startPTS; - frag.maxStartPTS = maxStartPTS; - frag.startDTS = startDTS; - frag.endPTS = endPTS; - frag.minEndPTS = minEndPTS; - frag.endDTS = endDTS; - var sn = frag.sn; // 'initSegment' - // exit if sn out of range - if (!details || sn < details.startSN || sn > details.endSN) { - return 0; - } - var i; - var fragIdx = sn - details.startSN; - var fragments = details.fragments; - // update frag reference in fragments array - // rationale is that fragments array might not contain this frag object. - // this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS() - // if we don't update frag, we won't be able to propagate PTS info on the playlist - // resulting in invalid sliding computation - fragments[fragIdx] = frag; - // adjust fragment PTS/duration from seqnum-1 to frag 0 - for (i = fragIdx; i > 0; i--) { - updateFromToPTS(fragments[i], fragments[i - 1]); - } - - // adjust fragment PTS/duration from seqnum to last frag - for (i = fragIdx; i < fragments.length - 1; i++) { - updateFromToPTS(fragments[i], fragments[i + 1]); - } - if (details.fragmentHint) { - updateFromToPTS(fragments[fragments.length - 1], details.fragmentHint); - } - details.PTSKnown = details.alignedSliding = true; - return drift; - } - function mergeDetails(oldDetails, newDetails) { - // Track the last initSegment processed. Initialize it to the last one on the timeline. - var currentInitSegment = null; - var oldFragments = oldDetails.fragments; - for (var i = oldFragments.length - 1; i >= 0; i--) { - var oldInit = oldFragments[i].initSegment; - if (oldInit) { - currentInitSegment = oldInit; - break; - } - } - if (oldDetails.fragmentHint) { - // prevent PTS and duration from being adjusted on the next hint - delete oldDetails.fragmentHint.endPTS; - } - // check if old/new playlists have fragments in common - // loop through overlapping SN and update startPTS , cc, and duration if any found - var ccOffset = 0; - var PTSFrag; - mapFragmentIntersection(oldDetails, newDetails, function (oldFrag, newFrag) { - if (oldFrag.relurl) { - // Do not compare CC if the old fragment has no url. This is a level.fragmentHint used by LL-HLS parts. - // It maybe be off by 1 if it was created before any parts or discontinuity tags were appended to the end - // of the playlist. - ccOffset = oldFrag.cc - newFrag.cc; - } - if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) { - newFrag.start = newFrag.startPTS = oldFrag.startPTS; - newFrag.startDTS = oldFrag.startDTS; - newFrag.maxStartPTS = oldFrag.maxStartPTS; - newFrag.endPTS = oldFrag.endPTS; - newFrag.endDTS = oldFrag.endDTS; - newFrag.minEndPTS = oldFrag.minEndPTS; - newFrag.duration = oldFrag.endPTS - oldFrag.startPTS; - if (newFrag.duration) { - PTSFrag = newFrag; - } - - // PTS is known when any segment has startPTS and endPTS - newDetails.PTSKnown = newDetails.alignedSliding = true; - } - newFrag.elementaryStreams = oldFrag.elementaryStreams; - newFrag.loader = oldFrag.loader; - newFrag.stats = oldFrag.stats; - if (oldFrag.initSegment) { - newFrag.initSegment = oldFrag.initSegment; - currentInitSegment = oldFrag.initSegment; - } - }); - if (currentInitSegment) { - var fragmentsToCheck = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments; - fragmentsToCheck.forEach(function (frag) { - var _currentInitSegment; - if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) { - frag.initSegment = currentInitSegment; - } - }); - } - if (newDetails.skippedSegments) { - newDetails.deltaUpdateFailed = newDetails.fragments.some(function (frag) { - return !frag; - }); - if (newDetails.deltaUpdateFailed) { - logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist'); - for (var _i = newDetails.skippedSegments; _i--;) { - newDetails.fragments.shift(); - } - newDetails.startSN = newDetails.fragments[0].sn; - newDetails.startCC = newDetails.fragments[0].cc; - } else if (newDetails.canSkipDateRanges) { - newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges); - } - } - var newFragments = newDetails.fragments; - if (ccOffset) { - logger.warn('discontinuity sliding from playlist, take drift into account'); - for (var _i2 = 0; _i2 < newFragments.length; _i2++) { - newFragments[_i2].cc += ccOffset; - } - } - if (newDetails.skippedSegments) { - newDetails.startCC = newDetails.fragments[0].cc; - } - - // Merge parts - mapPartIntersection(oldDetails.partList, newDetails.partList, function (oldPart, newPart) { - newPart.elementaryStreams = oldPart.elementaryStreams; - newPart.stats = oldPart.stats; - }); - - // if at least one fragment contains PTS info, recompute PTS information for all fragments - if (PTSFrag) { - updateFragPTSDTS(newDetails, PTSFrag, PTSFrag.startPTS, PTSFrag.endPTS, PTSFrag.startDTS, PTSFrag.endDTS); - } else { - // ensure that delta is within oldFragments range - // also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61]) - // in that case we also need to adjust start offset of all fragments - adjustSliding(oldDetails, newDetails); - } - if (newFragments.length) { - newDetails.totalduration = newDetails.edge - newFragments[0].start; - } - newDetails.driftStartTime = oldDetails.driftStartTime; - newDetails.driftStart = oldDetails.driftStart; - var advancedDateTime = newDetails.advancedDateTime; - if (newDetails.advanced && advancedDateTime) { - var edge = newDetails.edge; - if (!newDetails.driftStart) { - newDetails.driftStartTime = advancedDateTime; - newDetails.driftStart = edge; - } - newDetails.driftEndTime = advancedDateTime; - newDetails.driftEnd = edge; - } else { - newDetails.driftEndTime = oldDetails.driftEndTime; - newDetails.driftEnd = oldDetails.driftEnd; - newDetails.advancedDateTime = oldDetails.advancedDateTime; - } - } - function mergeDateRanges(oldDateRanges, deltaDateRanges, recentlyRemovedDateranges) { - var dateRanges = _extends({}, oldDateRanges); - if (recentlyRemovedDateranges) { - recentlyRemovedDateranges.forEach(function (id) { - delete dateRanges[id]; - }); - } - Object.keys(deltaDateRanges).forEach(function (id) { - var dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]); - if (dateRange.isValid) { - dateRanges[id] = dateRange; - } else { - logger.warn("Ignoring invalid Playlist Delta Update DATERANGE tag: \"" + JSON.stringify(deltaDateRanges[id].attr) + "\""); - } - }); - return dateRanges; - } - function mapPartIntersection(oldParts, newParts, intersectionFn) { - if (oldParts && newParts) { - var delta = 0; - for (var i = 0, len = oldParts.length; i <= len; i++) { - var _oldPart = oldParts[i]; - var _newPart = newParts[i + delta]; - if (_oldPart && _newPart && _oldPart.index === _newPart.index && _oldPart.fragment.sn === _newPart.fragment.sn) { - intersectionFn(_oldPart, _newPart); - } else { - delta--; - } - } - } - } - function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) { - var skippedSegments = newDetails.skippedSegments; - var start = Math.max(oldDetails.startSN, newDetails.startSN) - newDetails.startSN; - var end = (oldDetails.fragmentHint ? 1 : 0) + (skippedSegments ? newDetails.endSN : Math.min(oldDetails.endSN, newDetails.endSN)) - newDetails.startSN; - var delta = newDetails.startSN - oldDetails.startSN; - var newFrags = newDetails.fragmentHint ? newDetails.fragments.concat(newDetails.fragmentHint) : newDetails.fragments; - var oldFrags = oldDetails.fragmentHint ? oldDetails.fragments.concat(oldDetails.fragmentHint) : oldDetails.fragments; - for (var i = start; i <= end; i++) { - var _oldFrag = oldFrags[delta + i]; - var _newFrag = newFrags[i]; - if (skippedSegments && !_newFrag && i < skippedSegments) { - // Fill in skipped segments in delta playlist - _newFrag = newDetails.fragments[i] = _oldFrag; - } - if (_oldFrag && _newFrag) { - intersectionFn(_oldFrag, _newFrag); - } - } - } - function adjustSliding(oldDetails, newDetails) { - var delta = newDetails.startSN + newDetails.skippedSegments - oldDetails.startSN; - var oldFragments = oldDetails.fragments; - if (delta < 0 || delta >= oldFragments.length) { - return; - } - addSliding(newDetails, oldFragments[delta].start); - } - function addSliding(details, start) { - if (start) { - var fragments = details.fragments; - for (var i = details.skippedSegments; i < fragments.length; i++) { - fragments[i].start += start; - } - if (details.fragmentHint) { - details.fragmentHint.start += start; - } - } - } - function computeReloadInterval(newDetails, distanceToLiveEdgeMs) { - if (distanceToLiveEdgeMs === void 0) { - distanceToLiveEdgeMs = Infinity; - } - var reloadInterval = 1000 * newDetails.targetduration; - if (newDetails.updated) { - // Use last segment duration when shorter than target duration and near live edge - var fragments = newDetails.fragments; - var liveEdgeMaxTargetDurations = 4; - if (fragments.length && reloadInterval * liveEdgeMaxTargetDurations > distanceToLiveEdgeMs) { - var lastSegmentDuration = fragments[fragments.length - 1].duration * 1000; - if (lastSegmentDuration < reloadInterval) { - reloadInterval = lastSegmentDuration; - } - } - } else { - // estimate = 'miss half average'; - // follow HLS Spec, If the client reloads a Playlist file and finds that it has not - // changed then it MUST wait for a period of one-half the target - // duration before retrying. - reloadInterval /= 2; - } - return Math.round(reloadInterval); - } - function getFragmentWithSN(level, sn, fragCurrent) { - if (!(level != null && level.details)) { - return null; - } - var levelDetails = level.details; - var fragment = levelDetails.fragments[sn - levelDetails.startSN]; - if (fragment) { - return fragment; - } - fragment = levelDetails.fragmentHint; - if (fragment && fragment.sn === sn) { - return fragment; - } - if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) { - return fragCurrent; - } - return null; - } - function getPartWith(level, sn, partIndex) { - var _level$details; - if (!(level != null && level.details)) { - return null; - } - return findPart((_level$details = level.details) == null ? void 0 : _level$details.partList, sn, partIndex); - } - function findPart(partList, sn, partIndex) { - if (partList) { - for (var i = partList.length; i--;) { - var part = partList[i]; - if (part.index === partIndex && part.fragment.sn === sn) { - return part; - } - } - } - return null; - } - function reassignFragmentLevelIndexes(levels) { - levels.forEach(function (level, index) { - var details = level.details; - if (details != null && details.fragments) { - details.fragments.forEach(function (fragment) { - fragment.level = index; - }); - } - }); - } - - function isTimeoutError(error) { - switch (error.details) { - case ErrorDetails.FRAG_LOAD_TIMEOUT: - case ErrorDetails.KEY_LOAD_TIMEOUT: - case ErrorDetails.LEVEL_LOAD_TIMEOUT: - case ErrorDetails.MANIFEST_LOAD_TIMEOUT: - return true; - } - return false; - } - function getRetryConfig(loadPolicy, error) { - var isTimeout = isTimeoutError(error); - return loadPolicy.default[(isTimeout ? 'timeout' : 'error') + "Retry"]; - } - function getRetryDelay(retryConfig, retryCount) { - // exponential backoff capped to max retry delay - var backoffFactor = retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount); - return Math.min(backoffFactor * retryConfig.retryDelayMs, retryConfig.maxRetryDelayMs); - } - function getLoaderConfigWithoutReties(loderConfig) { - return _objectSpread2(_objectSpread2({}, loderConfig), { - errorRetry: null, - timeoutRetry: null - }); - } - function shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse) { - if (!retryConfig) { - return false; - } - var httpStatus = loaderResponse == null ? void 0 : loaderResponse.code; - var retry = retryCount < retryConfig.maxNumRetry && (retryForHttpStatus(httpStatus) || !!isTimeout); - return retryConfig.shouldRetry ? retryConfig.shouldRetry(retryConfig, retryCount, isTimeout, loaderResponse, retry) : retry; - } - function retryForHttpStatus(httpStatus) { - // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error) - return httpStatus === 0 && navigator.onLine === false || !!httpStatus && (httpStatus < 400 || httpStatus > 499); - } - - var BinarySearch = { - /** - * Searches for an item in an array which matches a certain condition. - * This requires the condition to only match one item in the array, - * and for the array to be ordered. - * - * @param list The array to search. - * @param comparisonFn - * Called and provided a candidate item as the first argument. - * Should return: - * > -1 if the item should be located at a lower index than the provided item. - * > 1 if the item should be located at a higher index than the provided item. - * > 0 if the item is the item you're looking for. - * - * @returns the object if found, otherwise returns null - */ - search: function search(list, comparisonFn) { - var minIndex = 0; - var maxIndex = list.length - 1; - var currentIndex = null; - var currentElement = null; - while (minIndex <= maxIndex) { - currentIndex = (minIndex + maxIndex) / 2 | 0; - currentElement = list[currentIndex]; - var comparisonResult = comparisonFn(currentElement); - if (comparisonResult > 0) { - minIndex = currentIndex + 1; - } else if (comparisonResult < 0) { - maxIndex = currentIndex - 1; - } else { - return currentElement; - } - } - return null; - } - }; - - /** - * Returns first fragment whose endPdt value exceeds the given PDT, or null. - * @param fragments - The array of candidate fragments - * @param PDTValue - The PDT value which must be exceeded - * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous - */ - function findFragmentByPDT(fragments, PDTValue, maxFragLookUpTolerance) { - if (PDTValue === null || !Array.isArray(fragments) || !fragments.length || !isFiniteNumber(PDTValue)) { - return null; - } - - // if less than start - var startPDT = fragments[0].programDateTime; - if (PDTValue < (startPDT || 0)) { - return null; - } - var endPDT = fragments[fragments.length - 1].endProgramDateTime; - if (PDTValue >= (endPDT || 0)) { - return null; - } - maxFragLookUpTolerance = maxFragLookUpTolerance || 0; - for (var seg = 0; seg < fragments.length; ++seg) { - var frag = fragments[seg]; - if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) { - return frag; - } - } - return null; - } - - /** - * Finds a fragment based on the SN of the previous fragment; or based on the needs of the current buffer. - * This method compensates for small buffer gaps by applying a tolerance to the start of any candidate fragment, thus - * breaking any traps which would cause the same fragment to be continuously selected within a small range. - * @param fragPrevious - The last frag successfully appended - * @param fragments - The array of candidate fragments - * @param bufferEnd - The end of the contiguous buffered range the playhead is currently within - * @param maxFragLookUpTolerance - The amount of time that a fragment's start/end can be within in order to be considered contiguous - * @returns a matching fragment or null - */ - function findFragmentByPTS(fragPrevious, fragments, bufferEnd, maxFragLookUpTolerance, nextFragLookupTolerance) { - if (bufferEnd === void 0) { - bufferEnd = 0; - } - if (maxFragLookUpTolerance === void 0) { - maxFragLookUpTolerance = 0; - } - if (nextFragLookupTolerance === void 0) { - nextFragLookupTolerance = 0.005; - } - var fragNext = null; - if (fragPrevious) { - fragNext = fragments[fragPrevious.sn - fragments[0].sn + 1] || null; - // check for buffer-end rounding error - var bufferEdgeError = fragPrevious.endDTS - bufferEnd; - if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) { - bufferEnd += 0.0000015; - } - } else if (bufferEnd === 0 && fragments[0].start === 0) { - fragNext = fragments[0]; - } - // Prefer the next fragment if it's within tolerance - if (fragNext && ((!fragPrevious || fragPrevious.level === fragNext.level) && fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, fragNext) === 0 || fragmentWithinFastStartSwitch(fragNext, fragPrevious, Math.min(nextFragLookupTolerance, maxFragLookUpTolerance)))) { - return fragNext; - } - // We might be seeking past the tolerance so find the best match - var foundFragment = BinarySearch.search(fragments, fragmentWithinToleranceTest.bind(null, bufferEnd, maxFragLookUpTolerance)); - if (foundFragment && (foundFragment !== fragPrevious || !fragNext)) { - return foundFragment; - } - // If no match was found return the next fragment after fragPrevious, or null - return fragNext; - } - function fragmentWithinFastStartSwitch(fragNext, fragPrevious, nextFragLookupTolerance) { - if (fragPrevious && fragPrevious.start === 0 && fragPrevious.level < fragNext.level && (fragPrevious.endPTS || 0) > 0) { - var firstDuration = fragPrevious.tagList.reduce(function (duration, tag) { - if (tag[0] === 'INF') { - duration += parseFloat(tag[1]); - } - return duration; - }, nextFragLookupTolerance); - return fragNext.start <= firstDuration; - } - return false; - } - - /** - * The test function used by the findFragmentBySn's BinarySearch to look for the best match to the current buffer conditions. - * @param candidate - The fragment to test - * @param bufferEnd - The end of the current buffered range the playhead is currently within - * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous - * @returns 0 if it matches, 1 if too low, -1 if too high - */ - function fragmentWithinToleranceTest(bufferEnd, maxFragLookUpTolerance, candidate) { - if (bufferEnd === void 0) { - bufferEnd = 0; - } - if (maxFragLookUpTolerance === void 0) { - maxFragLookUpTolerance = 0; - } - // eagerly accept an accurate match (no tolerance) - if (candidate.start <= bufferEnd && candidate.start + candidate.duration > bufferEnd) { - return 0; - } - // offset should be within fragment boundary - config.maxFragLookUpTolerance - // this is to cope with situations like - // bufferEnd = 9.991 - // frag[Ø] : [0,10] - // frag[1] : [10,20] - // bufferEnd is within frag[0] range ... although what we are expecting is to return frag[1] here - // frag start frag start+duration - // |-----------------------------| - // <---> <---> - // ...--------><-----------------------------><---------.... - // previous frag matching fragment next frag - // return -1 return 0 return 1 - // logger.log(`level/sn/start/end/bufEnd:${level}/${candidate.sn}/${candidate.start}/${(candidate.start+candidate.duration)}/${bufferEnd}`); - // Set the lookup tolerance to be small enough to detect the current segment - ensures we don't skip over very small segments - var candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)); - if (candidate.start + candidate.duration - candidateLookupTolerance <= bufferEnd) { - return 1; - } else if (candidate.start - candidateLookupTolerance > bufferEnd && candidate.start) { - // if maxFragLookUpTolerance will have negative value then don't return -1 for first element - return -1; - } - return 0; - } - - /** - * The test function used by the findFragmentByPdt's BinarySearch to look for the best match to the current buffer conditions. - * This function tests the candidate's program date time values, as represented in Unix time - * @param candidate - The fragment to test - * @param pdtBufferEnd - The Unix time representing the end of the current buffered range - * @param maxFragLookUpTolerance - The amount of time that a fragment's start can be within in order to be considered contiguous - * @returns true if contiguous, false otherwise - */ - function pdtWithinToleranceTest(pdtBufferEnd, maxFragLookUpTolerance, candidate) { - var candidateLookupTolerance = Math.min(maxFragLookUpTolerance, candidate.duration + (candidate.deltaPTS ? candidate.deltaPTS : 0)) * 1000; - - // endProgramDateTime can be null, default to zero - var endProgramDateTime = candidate.endProgramDateTime || 0; - return endProgramDateTime - candidateLookupTolerance > pdtBufferEnd; - } - function findFragWithCC(fragments, cc) { - return BinarySearch.search(fragments, function (candidate) { - if (candidate.cc < cc) { - return 1; - } else if (candidate.cc > cc) { - return -1; - } else { - return 0; - } - }); - } - - var NetworkErrorAction = { - DoNothing: 0, - SendEndCallback: 1, - SendAlternateToPenaltyBox: 2, - RemoveAlternatePermanently: 3, - InsertDiscontinuity: 4, - RetryRequest: 5 - }; - var ErrorActionFlags = { - None: 0, - MoveAllAlternatesMatchingHost: 1, - MoveAllAlternatesMatchingHDCP: 2, - SwitchToSDR: 4 - }; // Reserved for future use - var ErrorController = /*#__PURE__*/function () { - function ErrorController(hls) { - this.hls = void 0; - this.playlistError = 0; - this.penalizedRenditions = {}; - this.log = void 0; - this.warn = void 0; - this.error = void 0; - this.hls = hls; - this.log = logger.log.bind(logger, "[info]:"); - this.warn = logger.warn.bind(logger, "[warning]:"); - this.error = logger.error.bind(logger, "[error]:"); - this.registerListeners(); - } - var _proto = ErrorController.prototype; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.ERROR, this.onError, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - if (!hls) { - return; - } - hls.off(Events.ERROR, this.onError, this); - hls.off(Events.ERROR, this.onErrorOut, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - }; - _proto.destroy = function destroy() { - this.unregisterListeners(); - // @ts-ignore - this.hls = null; - this.penalizedRenditions = {}; - }; - _proto.startLoad = function startLoad(startPosition) {}; - _proto.stopLoad = function stopLoad() { - this.playlistError = 0; - }; - _proto.getVariantLevelIndex = function getVariantLevelIndex(frag) { - return (frag == null ? void 0 : frag.type) === PlaylistLevelType.MAIN ? frag.level : this.hls.loadLevel; - }; - _proto.onManifestLoading = function onManifestLoading() { - this.playlistError = 0; - this.penalizedRenditions = {}; - }; - _proto.onLevelUpdated = function onLevelUpdated() { - this.playlistError = 0; - }; - _proto.onError = function onError(event, data) { - var _data$frag, _data$level; - if (data.fatal) { - return; - } - var hls = this.hls; - var context = data.context; - switch (data.details) { - case ErrorDetails.FRAG_LOAD_ERROR: - case ErrorDetails.FRAG_LOAD_TIMEOUT: - case ErrorDetails.KEY_LOAD_ERROR: - case ErrorDetails.KEY_LOAD_TIMEOUT: - data.errorAction = this.getFragRetryOrSwitchAction(data); - return; - case ErrorDetails.FRAG_PARSING_ERROR: - // ignore empty segment errors marked as gap - if ((_data$frag = data.frag) != null && _data$frag.gap) { - data.errorAction = { - action: NetworkErrorAction.DoNothing, - flags: ErrorActionFlags.None - }; - return; - } - // falls through - case ErrorDetails.FRAG_GAP: - case ErrorDetails.FRAG_DECRYPT_ERROR: - { - // Switch level if possible, otherwise allow retry count to reach max error retries - data.errorAction = this.getFragRetryOrSwitchAction(data); - data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox; - return; - } - case ErrorDetails.LEVEL_EMPTY_ERROR: - case ErrorDetails.LEVEL_PARSING_ERROR: - { - var _data$context, _data$context$levelDe; - // Only retry when empty and live - var levelIndex = data.parent === PlaylistLevelType.MAIN ? data.level : hls.loadLevel; - if (data.details === ErrorDetails.LEVEL_EMPTY_ERROR && !!((_data$context = data.context) != null && (_data$context$levelDe = _data$context.levelDetails) != null && _data$context$levelDe.live)) { - data.errorAction = this.getPlaylistRetryOrSwitchAction(data, levelIndex); - } else { - // Escalate to fatal if not retrying or switching - data.levelRetry = false; - data.errorAction = this.getLevelSwitchAction(data, levelIndex); - } - } - return; - case ErrorDetails.LEVEL_LOAD_ERROR: - case ErrorDetails.LEVEL_LOAD_TIMEOUT: - if (typeof (context == null ? void 0 : context.level) === 'number') { - data.errorAction = this.getPlaylistRetryOrSwitchAction(data, context.level); - } - return; - case ErrorDetails.AUDIO_TRACK_LOAD_ERROR: - case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT: - case ErrorDetails.SUBTITLE_LOAD_ERROR: - case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT: - if (context) { - var level = hls.levels[hls.loadLevel]; - if (level && (context.type === PlaylistContextType.AUDIO_TRACK && level.hasAudioGroup(context.groupId) || context.type === PlaylistContextType.SUBTITLE_TRACK && level.hasSubtitleGroup(context.groupId))) { - // Perform Pathway switch or Redundant failover if possible for fastest recovery - // otherwise allow playlist retry count to reach max error retries - data.errorAction = this.getPlaylistRetryOrSwitchAction(data, hls.loadLevel); - data.errorAction.action = NetworkErrorAction.SendAlternateToPenaltyBox; - data.errorAction.flags = ErrorActionFlags.MoveAllAlternatesMatchingHost; - return; - } - } - return; - case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED: - { - var _level = hls.levels[hls.loadLevel]; - var restrictedHdcpLevel = _level == null ? void 0 : _level.attrs['HDCP-LEVEL']; - if (restrictedHdcpLevel) { - data.errorAction = { - action: NetworkErrorAction.SendAlternateToPenaltyBox, - flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP, - hdcpLevel: restrictedHdcpLevel - }; - } else { - this.keySystemError(data); - } - } - return; - case ErrorDetails.BUFFER_ADD_CODEC_ERROR: - case ErrorDetails.REMUX_ALLOC_ERROR: - case ErrorDetails.BUFFER_APPEND_ERROR: - data.errorAction = this.getLevelSwitchAction(data, (_data$level = data.level) != null ? _data$level : hls.loadLevel); - return; - case ErrorDetails.INTERNAL_EXCEPTION: - case ErrorDetails.BUFFER_APPENDING_ERROR: - case ErrorDetails.BUFFER_FULL_ERROR: - case ErrorDetails.LEVEL_SWITCH_ERROR: - case ErrorDetails.BUFFER_STALLED_ERROR: - case ErrorDetails.BUFFER_SEEK_OVER_HOLE: - case ErrorDetails.BUFFER_NUDGE_ON_STALL: - data.errorAction = { - action: NetworkErrorAction.DoNothing, - flags: ErrorActionFlags.None - }; - return; - } - if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) { - this.keySystemError(data); - } - }; - _proto.keySystemError = function keySystemError(data) { - var levelIndex = this.getVariantLevelIndex(data.frag); - // Do not retry level. Escalate to fatal if switching levels fails. - data.levelRetry = false; - data.errorAction = this.getLevelSwitchAction(data, levelIndex); - }; - _proto.getPlaylistRetryOrSwitchAction = function getPlaylistRetryOrSwitchAction(data, levelIndex) { - var hls = this.hls; - var retryConfig = getRetryConfig(hls.config.playlistLoadPolicy, data); - var retryCount = this.playlistError++; - var retry = shouldRetry(retryConfig, retryCount, isTimeoutError(data), data.response); - if (retry) { - return { - action: NetworkErrorAction.RetryRequest, - flags: ErrorActionFlags.None, - retryConfig: retryConfig, - retryCount: retryCount - }; - } - var errorAction = this.getLevelSwitchAction(data, levelIndex); - if (retryConfig) { - errorAction.retryConfig = retryConfig; - errorAction.retryCount = retryCount; - } - return errorAction; - }; - _proto.getFragRetryOrSwitchAction = function getFragRetryOrSwitchAction(data) { - var hls = this.hls; - // Share fragment error count accross media options (main, audio, subs) - // This allows for level based rendition switching when media option assets fail - var variantLevelIndex = this.getVariantLevelIndex(data.frag); - var level = hls.levels[variantLevelIndex]; - var _hls$config = hls.config, - fragLoadPolicy = _hls$config.fragLoadPolicy, - keyLoadPolicy = _hls$config.keyLoadPolicy; - var retryConfig = getRetryConfig(data.details.startsWith('key') ? keyLoadPolicy : fragLoadPolicy, data); - var fragmentErrors = hls.levels.reduce(function (acc, level) { - return acc + level.fragmentError; - }, 0); - // Switch levels when out of retried or level index out of bounds - if (level) { - if (data.details !== ErrorDetails.FRAG_GAP) { - level.fragmentError++; - } - var retry = shouldRetry(retryConfig, fragmentErrors, isTimeoutError(data), data.response); - if (retry) { - return { - action: NetworkErrorAction.RetryRequest, - flags: ErrorActionFlags.None, - retryConfig: retryConfig, - retryCount: fragmentErrors - }; - } - } - // Reach max retry count, or Missing level reference - // Switch to valid index - var errorAction = this.getLevelSwitchAction(data, variantLevelIndex); - // Add retry details to allow skipping of FRAG_PARSING_ERROR - if (retryConfig) { - errorAction.retryConfig = retryConfig; - errorAction.retryCount = fragmentErrors; - } - return errorAction; - }; - _proto.getLevelSwitchAction = function getLevelSwitchAction(data, levelIndex) { - var hls = this.hls; - if (levelIndex === null || levelIndex === undefined) { - levelIndex = hls.loadLevel; - } - var level = this.hls.levels[levelIndex]; - if (level) { - var _data$frag2, _data$context2; - var errorDetails = data.details; - level.loadError++; - if (errorDetails === ErrorDetails.BUFFER_APPEND_ERROR) { - level.fragmentError++; - } - // Search for next level to retry - var nextLevel = -1; - var levels = hls.levels, - loadLevel = hls.loadLevel, - minAutoLevel = hls.minAutoLevel, - maxAutoLevel = hls.maxAutoLevel; - if (!hls.autoLevelEnabled) { - hls.loadLevel = -1; - } - var fragErrorType = (_data$frag2 = data.frag) == null ? void 0 : _data$frag2.type; - // Find alternate audio codec if available on audio codec error - var isAudioCodecError = fragErrorType === PlaylistLevelType.AUDIO && errorDetails === ErrorDetails.FRAG_PARSING_ERROR || data.sourceBufferName === 'audio' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR); - var findAudioCodecAlternate = isAudioCodecError && levels.some(function (_ref) { - var audioCodec = _ref.audioCodec; - return level.audioCodec !== audioCodec; - }); - // Find alternate video codec if available on video codec error - var isVideoCodecError = data.sourceBufferName === 'video' && (errorDetails === ErrorDetails.BUFFER_ADD_CODEC_ERROR || errorDetails === ErrorDetails.BUFFER_APPEND_ERROR); - var findVideoCodecAlternate = isVideoCodecError && levels.some(function (_ref2) { - var codecSet = _ref2.codecSet, - audioCodec = _ref2.audioCodec; - return level.codecSet !== codecSet && level.audioCodec === audioCodec; - }); - var _ref3 = (_data$context2 = data.context) != null ? _data$context2 : {}, - playlistErrorType = _ref3.type, - playlistErrorGroupId = _ref3.groupId; - var _loop = function _loop() { - var candidate = (i + loadLevel) % levels.length; - if (candidate !== loadLevel && candidate >= minAutoLevel && candidate <= maxAutoLevel && levels[candidate].loadError === 0) { - var _level$audioGroups, _level$subtitleGroups; - var levelCandidate = levels[candidate]; - // Skip level switch if GAP tag is found in next level at same position - if (errorDetails === ErrorDetails.FRAG_GAP && fragErrorType === PlaylistLevelType.MAIN && data.frag) { - var levelDetails = levels[candidate].details; - if (levelDetails) { - var fragCandidate = findFragmentByPTS(data.frag, levelDetails.fragments, data.frag.start); - if (fragCandidate != null && fragCandidate.gap) { - return 0; // continue - } - } - } else if (playlistErrorType === PlaylistContextType.AUDIO_TRACK && levelCandidate.hasAudioGroup(playlistErrorGroupId) || playlistErrorType === PlaylistContextType.SUBTITLE_TRACK && levelCandidate.hasSubtitleGroup(playlistErrorGroupId)) { - // For audio/subs playlist errors find another group ID or fallthrough to redundant fail-over - return 0; // continue - } else if (fragErrorType === PlaylistLevelType.AUDIO && (_level$audioGroups = level.audioGroups) != null && _level$audioGroups.some(function (groupId) { - return levelCandidate.hasAudioGroup(groupId); - }) || fragErrorType === PlaylistLevelType.SUBTITLE && (_level$subtitleGroups = level.subtitleGroups) != null && _level$subtitleGroups.some(function (groupId) { - return levelCandidate.hasSubtitleGroup(groupId); - }) || findAudioCodecAlternate && level.audioCodec === levelCandidate.audioCodec || !findAudioCodecAlternate && level.audioCodec !== levelCandidate.audioCodec || findVideoCodecAlternate && level.codecSet === levelCandidate.codecSet) { - // For video/audio/subs frag errors find another group ID or fallthrough to redundant fail-over - return 0; // continue - } - nextLevel = candidate; - return 1; // break - } - }, - _ret; - for (var i = levels.length; i--;) { - _ret = _loop(); - if (_ret === 0) continue; - if (_ret === 1) break; - } - if (nextLevel > -1 && hls.loadLevel !== nextLevel) { - data.levelRetry = true; - this.playlistError = 0; - return { - action: NetworkErrorAction.SendAlternateToPenaltyBox, - flags: ErrorActionFlags.None, - nextAutoLevel: nextLevel - }; - } - } - // No levels to switch / Manual level selection / Level not found - // Resolve with Pathway switch, Redundant fail-over, or stay on lowest Level - return { - action: NetworkErrorAction.SendAlternateToPenaltyBox, - flags: ErrorActionFlags.MoveAllAlternatesMatchingHost - }; - }; - _proto.onErrorOut = function onErrorOut(event, data) { - var _data$errorAction; - switch ((_data$errorAction = data.errorAction) == null ? void 0 : _data$errorAction.action) { - case NetworkErrorAction.DoNothing: - break; - case NetworkErrorAction.SendAlternateToPenaltyBox: - this.sendAlternateToPenaltyBox(data); - if (!data.errorAction.resolved && data.details !== ErrorDetails.FRAG_GAP) { - data.fatal = true; - } else if (/MediaSource readyState: ended/.test(data.error.message)) { - this.warn("MediaSource ended after \"" + data.sourceBufferName + "\" sourceBuffer append error. Attempting to recover from media error."); - this.hls.recoverMediaError(); - } - break; - } - if (data.fatal) { - this.hls.stopLoad(); - return; - } - }; - _proto.sendAlternateToPenaltyBox = function sendAlternateToPenaltyBox(data) { - var hls = this.hls; - var errorAction = data.errorAction; - if (!errorAction) { - return; - } - var flags = errorAction.flags, - hdcpLevel = errorAction.hdcpLevel, - nextAutoLevel = errorAction.nextAutoLevel; - switch (flags) { - case ErrorActionFlags.None: - this.switchLevel(data, nextAutoLevel); - break; - case ErrorActionFlags.MoveAllAlternatesMatchingHDCP: - if (hdcpLevel) { - hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(hdcpLevel) - 1]; - errorAction.resolved = true; - } - this.warn("Restricting playback to HDCP-LEVEL of \"" + hls.maxHdcpLevel + "\" or lower"); - break; - } - // If not resolved by previous actions try to switch to next level - if (!errorAction.resolved) { - this.switchLevel(data, nextAutoLevel); - } - }; - _proto.switchLevel = function switchLevel(data, levelIndex) { - if (levelIndex !== undefined && data.errorAction) { - this.warn("switching to level " + levelIndex + " after " + data.details); - this.hls.nextAutoLevel = levelIndex; - data.errorAction.resolved = true; - // Stream controller is responsible for this but won't switch on false start - this.hls.nextLoadLevel = this.hls.nextAutoLevel; - } - }; - return ErrorController; - }(); - - var BasePlaylistController = /*#__PURE__*/function () { - function BasePlaylistController(hls, logPrefix) { - this.hls = void 0; - this.timer = -1; - this.requestScheduled = -1; - this.canLoad = false; - this.log = void 0; - this.warn = void 0; - this.log = logger.log.bind(logger, logPrefix + ":"); - this.warn = logger.warn.bind(logger, logPrefix + ":"); - this.hls = hls; - } - var _proto = BasePlaylistController.prototype; - _proto.destroy = function destroy() { - this.clearTimer(); - // @ts-ignore - this.hls = this.log = this.warn = null; - }; - _proto.clearTimer = function clearTimer() { - if (this.timer !== -1) { - self.clearTimeout(this.timer); - this.timer = -1; - } - }; - _proto.startLoad = function startLoad() { - this.canLoad = true; - this.requestScheduled = -1; - this.loadPlaylist(); - }; - _proto.stopLoad = function stopLoad() { - this.canLoad = false; - this.clearTimer(); - }; - _proto.switchParams = function switchParams(playlistUri, previous, current) { - var renditionReports = previous == null ? void 0 : previous.renditionReports; - if (renditionReports) { - var foundIndex = -1; - for (var i = 0; i < renditionReports.length; i++) { - var attr = renditionReports[i]; - var uri = void 0; - try { - uri = new self.URL(attr.URI, previous.url).href; - } catch (error) { - logger.warn("Could not construct new URL for Rendition Report: " + error); - uri = attr.URI || ''; - } - // Use exact match. Otherwise, the last partial match, if any, will be used - // (Playlist URI includes a query string that the Rendition Report does not) - if (uri === playlistUri) { - foundIndex = i; - break; - } else if (uri === playlistUri.substring(0, uri.length)) { - foundIndex = i; - } - } - if (foundIndex !== -1) { - var _attr = renditionReports[foundIndex]; - var msn = parseInt(_attr['LAST-MSN']) || (previous == null ? void 0 : previous.lastPartSn); - var part = parseInt(_attr['LAST-PART']) || (previous == null ? void 0 : previous.lastPartIndex); - if (this.hls.config.lowLatencyMode) { - var currentGoal = Math.min(previous.age - previous.partTarget, previous.targetduration); - if (part >= 0 && currentGoal > previous.partTarget) { - part += 1; - } - } - var skip = current && getSkipValue(current); - return new HlsUrlParameters(msn, part >= 0 ? part : undefined, skip); - } - } - }; - _proto.loadPlaylist = function loadPlaylist(hlsUrlParameters) { - if (this.requestScheduled === -1) { - this.requestScheduled = self.performance.now(); - } - // Loading is handled by the subclasses - }; - _proto.shouldLoadPlaylist = function shouldLoadPlaylist(playlist) { - return this.canLoad && !!playlist && !!playlist.url && (!playlist.details || playlist.details.live); - }; - _proto.shouldReloadPlaylist = function shouldReloadPlaylist(playlist) { - return this.timer === -1 && this.requestScheduled === -1 && this.shouldLoadPlaylist(playlist); - }; - _proto.playlistLoaded = function playlistLoaded(index, data, previousDetails) { - var _this = this; - var details = data.details, - stats = data.stats; - - // Set last updated date-time - var now = self.performance.now(); - var elapsed = stats.loading.first ? Math.max(0, now - stats.loading.first) : 0; - details.advancedDateTime = Date.now() - elapsed; - - // if current playlist is a live playlist, arm a timer to reload it - if (details.live || previousDetails != null && previousDetails.live) { - details.reloaded(previousDetails); - if (previousDetails) { - this.log("live playlist " + index + " " + (details.advanced ? 'REFRESHED ' + details.lastPartSn + '-' + details.lastPartIndex : details.updated ? 'UPDATED' : 'MISSED')); - } - // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments - if (previousDetails && details.fragments.length > 0) { - mergeDetails(previousDetails, details); - } - if (!this.canLoad || !details.live) { - return; - } - var deliveryDirectives; - var msn = undefined; - var part = undefined; - if (details.canBlockReload && details.endSN && details.advanced) { - // Load level with LL-HLS delivery directives - var lowLatencyMode = this.hls.config.lowLatencyMode; - var lastPartSn = details.lastPartSn; - var endSn = details.endSN; - var lastPartIndex = details.lastPartIndex; - var hasParts = lastPartIndex !== -1; - var lastPart = lastPartSn === endSn; - // When low latency mode is disabled, we'll skip part requests once the last part index is found - var nextSnStartIndex = lowLatencyMode ? 0 : lastPartIndex; - if (hasParts) { - msn = lastPart ? endSn + 1 : lastPartSn; - part = lastPart ? nextSnStartIndex : lastPartIndex + 1; - } else { - msn = endSn + 1; - } - // Low-Latency CDN Tune-in: "age" header and time since load indicates we're behind by more than one part - // Update directives to obtain the Playlist that has the estimated additional duration of media - var lastAdvanced = details.age; - var cdnAge = lastAdvanced + details.ageHeader; - var currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5); - if (currentGoal > 0) { - if (previousDetails && currentGoal > previousDetails.tuneInGoal) { - // If we attempted to get the next or latest playlist update, but currentGoal increased, - // then we either can't catchup, or the "age" header cannot be trusted. - this.warn("CDN Tune-in goal increased from: " + previousDetails.tuneInGoal + " to: " + currentGoal + " with playlist age: " + details.age); - currentGoal = 0; - } else { - var segments = Math.floor(currentGoal / details.targetduration); - msn += segments; - if (part !== undefined) { - var parts = Math.round(currentGoal % details.targetduration / details.partTarget); - part += parts; - } - this.log("CDN Tune-in age: " + details.ageHeader + "s last advanced " + lastAdvanced.toFixed(2) + "s goal: " + currentGoal + " skip sn " + segments + " to part " + part); - } - details.tuneInGoal = currentGoal; - } - deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part); - if (lowLatencyMode || !lastPart) { - this.loadPlaylist(deliveryDirectives); - return; - } - } else if (details.canBlockReload || details.canSkipUntil) { - deliveryDirectives = this.getDeliveryDirectives(details, data.deliveryDirectives, msn, part); - } - var bufferInfo = this.hls.mainForwardBufferInfo; - var position = bufferInfo ? bufferInfo.end - bufferInfo.len : 0; - var distanceToLiveEdgeMs = (details.edge - position) * 1000; - var reloadInterval = computeReloadInterval(details, distanceToLiveEdgeMs); - if (details.updated && now > this.requestScheduled + reloadInterval) { - this.requestScheduled = stats.loading.start; - } - if (msn !== undefined && details.canBlockReload) { - this.requestScheduled = stats.loading.first + reloadInterval - (details.partTarget * 1000 || 1000); - } else if (this.requestScheduled === -1 || this.requestScheduled + reloadInterval < now) { - this.requestScheduled = now; - } else if (this.requestScheduled - now <= 0) { - this.requestScheduled += reloadInterval; - } - var estimatedTimeUntilUpdate = this.requestScheduled - now; - estimatedTimeUntilUpdate = Math.max(0, estimatedTimeUntilUpdate); - this.log("reload live playlist " + index + " in " + Math.round(estimatedTimeUntilUpdate) + " ms"); - // this.log( - // `live reload ${details.updated ? 'REFRESHED' : 'MISSED'} - // reload in ${estimatedTimeUntilUpdate / 1000} - // round trip ${(stats.loading.end - stats.loading.start) / 1000} - // diff ${ - // (reloadInterval - - // (estimatedTimeUntilUpdate + - // stats.loading.end - - // stats.loading.start)) / - // 1000 - // } - // reload interval ${reloadInterval / 1000} - // target duration ${details.targetduration} - // distance to edge ${distanceToLiveEdgeMs / 1000}` - // ); - - this.timer = self.setTimeout(function () { - return _this.loadPlaylist(deliveryDirectives); - }, estimatedTimeUntilUpdate); - } else { - this.clearTimer(); - } - }; - _proto.getDeliveryDirectives = function getDeliveryDirectives(details, previousDeliveryDirectives, msn, part) { - var skip = getSkipValue(details); - if (previousDeliveryDirectives != null && previousDeliveryDirectives.skip && details.deltaUpdateFailed) { - msn = previousDeliveryDirectives.msn; - part = previousDeliveryDirectives.part; - skip = HlsSkip.No; - } - return new HlsUrlParameters(msn, part, skip); - }; - _proto.checkRetry = function checkRetry(errorEvent) { - var _this2 = this; - var errorDetails = errorEvent.details; - var isTimeout = isTimeoutError(errorEvent); - var errorAction = errorEvent.errorAction; - var _ref = errorAction || {}, - action = _ref.action, - _ref$retryCount = _ref.retryCount, - retryCount = _ref$retryCount === void 0 ? 0 : _ref$retryCount, - retryConfig = _ref.retryConfig; - var retry = !!errorAction && !!retryConfig && (action === NetworkErrorAction.RetryRequest || !errorAction.resolved && action === NetworkErrorAction.SendAlternateToPenaltyBox); - if (retry) { - var _errorEvent$context; - this.requestScheduled = -1; - if (retryCount >= retryConfig.maxNumRetry) { - return false; - } - if (isTimeout && (_errorEvent$context = errorEvent.context) != null && _errorEvent$context.deliveryDirectives) { - // The LL-HLS request already timed out so retry immediately - this.warn("Retrying playlist loading " + (retryCount + 1) + "/" + retryConfig.maxNumRetry + " after \"" + errorDetails + "\" without delivery-directives"); - this.loadPlaylist(); - } else { - var delay = getRetryDelay(retryConfig, retryCount); - // Schedule level/track reload - this.timer = self.setTimeout(function () { - return _this2.loadPlaylist(); - }, delay); - this.warn("Retrying playlist loading " + (retryCount + 1) + "/" + retryConfig.maxNumRetry + " after \"" + errorDetails + "\" in " + delay + "ms"); - } - // `levelRetry = true` used to inform other controllers that a retry is happening - errorEvent.levelRetry = true; - errorAction.resolved = true; - } - return retry; - }; - return BasePlaylistController; - }(); - - /* - * compute an Exponential Weighted moving average - * - https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - * - heavily inspired from shaka-player - */ - var EWMA = /*#__PURE__*/function () { - // About half of the estimated value will be from the last |halfLife| samples by weight. - function EWMA(halfLife, estimate, weight) { - if (estimate === void 0) { - estimate = 0; - } - if (weight === void 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; - } - var _proto = EWMA.prototype; - _proto.sample = function sample(weight, value) { - var adjAlpha = Math.pow(this.alpha_, weight); - this.estimate_ = value * (1 - adjAlpha) + adjAlpha * this.estimate_; - this.totalWeight_ += weight; - }; - _proto.getTotalWeight = function getTotalWeight() { - return this.totalWeight_; - }; - _proto.getEstimate = function getEstimate() { - if (this.alpha_) { - var zeroFactor = 1 - Math.pow(this.alpha_, this.totalWeight_); - if (zeroFactor) { - return this.estimate_ / zeroFactor; - } - } - return this.estimate_; - }; - return EWMA; - }(); - - /* - * 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. - */ - - var EwmaBandWidthEstimator = /*#__PURE__*/function () { - function EwmaBandWidthEstimator(slow, fast, defaultEstimate, defaultTTFB) { - if (defaultTTFB === void 0) { - 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); - } - var _proto = EwmaBandWidthEstimator.prototype; - _proto.update = function update(slow, fast) { - var slow_ = this.slow_, - fast_ = this.fast_, - ttfb_ = this.ttfb_; - 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()); - } - }; - _proto.sample = function sample(durationMs, numBytes) { - durationMs = Math.max(durationMs, this.minDelayMs_); - var numBits = 8 * numBytes; - // weight is duration in seconds - var durationS = durationMs / 1000; - // value is bandwidth in bits/s - var bandwidthInBps = numBits / durationS; - this.fast_.sample(durationS, bandwidthInBps); - this.slow_.sample(durationS, bandwidthInBps); - }; - _proto.sampleTTFB = function sampleTTFB(ttfb) { - // weight is frequency curve applied to TTFB in seconds - // (longer times have less weight with expected input under 1 second) - var seconds = ttfb / 1000; - var weight = Math.sqrt(2) * Math.exp(-Math.pow(seconds, 2) / 2); - this.ttfb_.sample(weight, Math.max(ttfb, 5)); - }; - _proto.canEstimate = function canEstimate() { - return this.fast_.getTotalWeight() >= this.minWeight_; - }; - _proto.getEstimate = function 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_; - } - }; - _proto.getEstimateTTFB = function getEstimateTTFB() { - if (this.ttfb_.getTotalWeight() >= this.minWeight_) { - return this.ttfb_.getEstimate(); - } else { - return this.defaultTTFB_; - } - }; - _proto.destroy = function destroy() {}; - return EwmaBandWidthEstimator; - }(); - - var SUPPORTED_INFO_DEFAULT = { - supported: true, - configurations: [], - decodingInfoResults: [{ - supported: true, - powerEfficient: true, - smooth: true - }] - }; - var SUPPORTED_INFO_CACHE = {}; - function requiresMediaCapabilitiesDecodingInfo(level, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference) { - // Only test support when configuration is exceeds minimum options - var audioGroups = level.audioCodec ? level.audioGroups : null; - var audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec; - var channelsPreference = audioPreference == null ? void 0 : audioPreference.channels; - var maxChannels = channelsPreference ? parseInt(channelsPreference) : audioCodecPreference ? Infinity : 2; - var audioChannels = null; - if (audioGroups != null && audioGroups.length) { - try { - if (audioGroups.length === 1 && audioGroups[0]) { - audioChannels = audioTracksByGroup.groups[audioGroups[0]].channels; - } else { - audioChannels = audioGroups.reduce(function (acc, groupId) { - if (groupId) { - var audioTrackGroup = audioTracksByGroup.groups[groupId]; - if (!audioTrackGroup) { - throw new Error("Audio track group " + groupId + " not found"); - } - // Sum all channel key values - Object.keys(audioTrackGroup.channels).forEach(function (key) { - acc[key] = (acc[key] || 0) + audioTrackGroup.channels[key]; - }); - } - return acc; - }, { - 2: 0 - }); - } - } catch (error) { - return true; - } - } - return level.videoCodec !== undefined && (level.width > 1920 && level.height > 1088 || level.height > 1920 && level.width > 1088 || level.frameRate > Math.max(currentFrameRate, 30) || level.videoRange !== 'SDR' && level.videoRange !== currentVideoRange || level.bitrate > Math.max(currentBw, 8e6)) || !!audioChannels && isFiniteNumber(maxChannels) && Object.keys(audioChannels).some(function (channels) { - return parseInt(channels) > maxChannels; - }); - } - function getMediaDecodingInfoPromise(level, audioTracksByGroup, mediaCapabilities) { - var videoCodecs = level.videoCodec; - var audioCodecs = level.audioCodec; - if (!videoCodecs || !audioCodecs || !mediaCapabilities) { - return Promise.resolve(SUPPORTED_INFO_DEFAULT); - } - var baseVideoConfiguration = { - width: level.width, - height: level.height, - bitrate: Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate)), - // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0. - framerate: level.frameRate || 30 - }; - var videoRange = level.videoRange; - if (videoRange !== 'SDR') { - baseVideoConfiguration.transferFunction = videoRange.toLowerCase(); - } - var configurations = videoCodecs.split(',').map(function (videoCodec) { - return { - type: 'media-source', - video: _objectSpread2(_objectSpread2({}, baseVideoConfiguration), {}, { - contentType: mimeTypeForCodec(videoCodec, 'video') - }) - }; - }); - if (audioCodecs && level.audioGroups) { - level.audioGroups.forEach(function (audioGroupId) { - var _audioTracksByGroup$g; - if (!audioGroupId) { - return; - } - (_audioTracksByGroup$g = audioTracksByGroup.groups[audioGroupId]) == null ? void 0 : _audioTracksByGroup$g.tracks.forEach(function (audioTrack) { - if (audioTrack.groupId === audioGroupId) { - var channels = audioTrack.channels || ''; - var channelsNumber = parseFloat(channels); - if (isFiniteNumber(channelsNumber) && channelsNumber > 2) { - configurations.push.apply(configurations, audioCodecs.split(',').map(function (audioCodec) { - return { - type: 'media-source', - audio: { - contentType: mimeTypeForCodec(audioCodec, 'audio'), - channels: '' + channelsNumber - // spatialRendering: - // audioCodec === 'ec-3' && channels.indexOf('JOC'), - } - }; - })); - } - } - }); - }); - } - return Promise.all(configurations.map(function (configuration) { - // Cache MediaCapabilities promises - var decodingInfoKey = getMediaDecodingInfoKey(configuration); - return SUPPORTED_INFO_CACHE[decodingInfoKey] || (SUPPORTED_INFO_CACHE[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration)); - })).then(function (decodingInfoResults) { - return { - supported: !decodingInfoResults.some(function (info) { - return !info.supported; - }), - configurations: configurations, - decodingInfoResults: decodingInfoResults - }; - }).catch(function (error) { - return { - supported: false, - configurations: configurations, - decodingInfoResults: [], - error: error - }; - }); - } - function getMediaDecodingInfoKey(config) { - var audio = config.audio, - video = config.video; - var mediaConfig = video || audio; - if (mediaConfig) { - var codec = mediaConfig.contentType.split('"')[1]; - if (video) { - return "r" + video.height + "x" + video.width + "f" + Math.ceil(video.framerate) + (video.transferFunction || 'sd') + "_" + codec + "_" + Math.ceil(video.bitrate / 1e5); - } - if (audio) { - return "c" + audio.channels + (audio.spatialRendering ? 's' : 'n') + "_" + codec; - } - } - return ''; - } - - /** - * @returns Whether we can detect and validate HDR capability within the window context - */ - function isHdrSupported() { - if (typeof matchMedia === 'function') { - var mediaQueryList = matchMedia('(dynamic-range: high)'); - var badQuery = matchMedia('bad query'); - if (mediaQueryList.media !== badQuery.media) { - return mediaQueryList.matches === true; - } - } - return false; - } - - /** - * Sanitizes inputs to return the active video selection options for HDR/SDR. - * When both inputs are null: - * - * `{ preferHDR: false, allowedVideoRanges: [] }` - * - * When `currentVideoRange` non-null, maintain the active range: - * - * `{ preferHDR: currentVideoRange !== 'SDR', allowedVideoRanges: [currentVideoRange] }` - * - * When VideoSelectionOption non-null: - * - * - Allow all video ranges if `allowedVideoRanges` unspecified. - * - If `preferHDR` is non-null use the value to filter `allowedVideoRanges`. - * - Else check window for HDR support and set `preferHDR` to the result. - * - * @param currentVideoRange - * @param videoPreference - */ - function getVideoSelectionOptions(currentVideoRange, videoPreference) { - var preferHDR = false; - var allowedVideoRanges = []; - if (currentVideoRange) { - preferHDR = currentVideoRange !== 'SDR'; - allowedVideoRanges = [currentVideoRange]; - } - if (videoPreference) { - allowedVideoRanges = videoPreference.allowedVideoRanges || VideoRangeValues.slice(0); - preferHDR = videoPreference.preferHDR !== undefined ? videoPreference.preferHDR : isHdrSupported(); - if (preferHDR) { - allowedVideoRanges = allowedVideoRanges.filter(function (range) { - return range !== 'SDR'; - }); - } else { - allowedVideoRanges = ['SDR']; - } - } - return { - preferHDR: preferHDR, - allowedVideoRanges: allowedVideoRanges - }; - } - - function getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference) { - var codecSets = Object.keys(codecTiers); - var channelsPreference = audioPreference == null ? void 0 : audioPreference.channels; - var audioCodecPreference = audioPreference == null ? void 0 : audioPreference.audioCodec; - var preferStereo = channelsPreference && parseInt(channelsPreference) === 2; - // Use first level set to determine stereo, and minimum resolution and framerate - var hasStereo = true; - var hasCurrentVideoRange = false; - var minHeight = Infinity; - var minFramerate = Infinity; - var minBitrate = Infinity; - var selectedScore = 0; - var videoRanges = []; - var _getVideoSelectionOpt = getVideoSelectionOptions(currentVideoRange, videoPreference), - preferHDR = _getVideoSelectionOpt.preferHDR, - allowedVideoRanges = _getVideoSelectionOpt.allowedVideoRanges; - var _loop = function _loop() { - var tier = codecTiers[codecSets[i]]; - hasStereo = tier.channels[2] > 0; - minHeight = Math.min(minHeight, tier.minHeight); - minFramerate = Math.min(minFramerate, tier.minFramerate); - minBitrate = Math.min(minBitrate, tier.minBitrate); - var matchingVideoRanges = allowedVideoRanges.filter(function (range) { - return tier.videoRanges[range] > 0; - }); - if (matchingVideoRanges.length > 0) { - hasCurrentVideoRange = true; - videoRanges = matchingVideoRanges; - } - }; - for (var i = codecSets.length; i--;) { - _loop(); - } - minHeight = isFiniteNumber(minHeight) ? minHeight : 0; - minFramerate = isFiniteNumber(minFramerate) ? minFramerate : 0; - var maxHeight = Math.max(1080, minHeight); - var maxFramerate = Math.max(30, minFramerate); - minBitrate = isFiniteNumber(minBitrate) ? minBitrate : currentBw; - currentBw = Math.max(minBitrate, currentBw); - // If there are no variants with matching preference, set currentVideoRange to undefined - if (!hasCurrentVideoRange) { - currentVideoRange = undefined; - videoRanges = []; - } - var codecSet = codecSets.reduce(function (selected, candidate) { - // Remove candiates which do not meet bitrate, default audio, stereo or channels preference, 1080p or lower, 30fps or lower, or SDR/HDR selection if present - var candidateTier = codecTiers[candidate]; - if (candidate === selected) { - return selected; - } - if (candidateTier.minBitrate > currentBw) { - logStartCodecCandidateIgnored(candidate, "min bitrate of " + candidateTier.minBitrate + " > current estimate of " + currentBw); - return selected; - } - if (!candidateTier.hasDefaultAudio) { - logStartCodecCandidateIgnored(candidate, "no renditions with default or auto-select sound found"); - return selected; - } - if (audioCodecPreference && candidate.indexOf(audioCodecPreference.substring(0, 4)) % 5 !== 0) { - logStartCodecCandidateIgnored(candidate, "audio codec preference \"" + audioCodecPreference + "\" not found"); - return selected; - } - if (channelsPreference && !preferStereo) { - if (!candidateTier.channels[channelsPreference]) { - logStartCodecCandidateIgnored(candidate, "no renditions with " + channelsPreference + " channel sound found (channels options: " + Object.keys(candidateTier.channels) + ")"); - return selected; - } - } else if ((!audioCodecPreference || preferStereo) && hasStereo && candidateTier.channels['2'] === 0) { - logStartCodecCandidateIgnored(candidate, "no renditions with stereo sound found"); - return selected; - } - if (candidateTier.minHeight > maxHeight) { - logStartCodecCandidateIgnored(candidate, "min resolution of " + candidateTier.minHeight + " > maximum of " + maxHeight); - return selected; - } - if (candidateTier.minFramerate > maxFramerate) { - logStartCodecCandidateIgnored(candidate, "min framerate of " + candidateTier.minFramerate + " > maximum of " + maxFramerate); - return selected; - } - if (!videoRanges.some(function (range) { - return candidateTier.videoRanges[range] > 0; - })) { - logStartCodecCandidateIgnored(candidate, "no variants with VIDEO-RANGE of " + JSON.stringify(videoRanges) + " found"); - return selected; - } - if (candidateTier.maxScore < selectedScore) { - logStartCodecCandidateIgnored(candidate, "max score of " + candidateTier.maxScore + " < selected max of " + selectedScore); - return selected; - } - // Remove candiates with less preferred codecs or more errors - if (selected && (codecsSetSelectionPreferenceValue(candidate) >= codecsSetSelectionPreferenceValue(selected) || candidateTier.fragmentError > codecTiers[selected].fragmentError)) { - return selected; - } - selectedScore = candidateTier.maxScore; - return candidate; - }, undefined); - return { - codecSet: codecSet, - videoRanges: videoRanges, - preferHDR: preferHDR, - minFramerate: minFramerate, - minBitrate: minBitrate - }; - } - function logStartCodecCandidateIgnored(codeSet, reason) { - logger.log("[abr] start candidates with \"" + codeSet + "\" ignored because " + reason); - } - function getAudioTracksByGroup(allAudioTracks) { - return allAudioTracks.reduce(function (audioTracksByGroup, track) { - var trackGroup = audioTracksByGroup.groups[track.groupId]; - if (!trackGroup) { - trackGroup = audioTracksByGroup.groups[track.groupId] = { - tracks: [], - channels: { - 2: 0 - }, - hasDefault: false, - hasAutoSelect: false - }; - } - trackGroup.tracks.push(track); - var channelsKey = track.channels || '2'; - trackGroup.channels[channelsKey] = (trackGroup.channels[channelsKey] || 0) + 1; - trackGroup.hasDefault = trackGroup.hasDefault || track.default; - trackGroup.hasAutoSelect = trackGroup.hasAutoSelect || track.autoselect; - if (trackGroup.hasDefault) { - audioTracksByGroup.hasDefaultAudio = true; - } - if (trackGroup.hasAutoSelect) { - audioTracksByGroup.hasAutoSelectAudio = true; - } - return audioTracksByGroup; - }, { - hasDefaultAudio: false, - hasAutoSelectAudio: false, - groups: {} - }); - } - function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) { - return levels.slice(minAutoLevel, maxAutoLevel + 1).reduce(function (tiers, level) { - if (!level.codecSet) { - return tiers; - } - var audioGroups = level.audioGroups; - var tier = tiers[level.codecSet]; - if (!tier) { - tiers[level.codecSet] = tier = { - minBitrate: Infinity, - minHeight: Infinity, - minFramerate: Infinity, - maxScore: 0, - videoRanges: { - SDR: 0 - }, - channels: { - '2': 0 - }, - hasDefaultAudio: !audioGroups, - fragmentError: 0 - }; - } - tier.minBitrate = Math.min(tier.minBitrate, level.bitrate); - var lesserWidthOrHeight = Math.min(level.height, level.width); - tier.minHeight = Math.min(tier.minHeight, lesserWidthOrHeight); - tier.minFramerate = Math.min(tier.minFramerate, level.frameRate); - tier.maxScore = Math.max(tier.maxScore, level.score); - tier.fragmentError += level.fragmentError; - tier.videoRanges[level.videoRange] = (tier.videoRanges[level.videoRange] || 0) + 1; - if (audioGroups) { - audioGroups.forEach(function (audioGroupId) { - if (!audioGroupId) { - return; - } - var audioGroup = audioTracksByGroup.groups[audioGroupId]; - if (!audioGroup) { - return; - } - // Default audio is any group with DEFAULT=YES, or if missing then any group with AUTOSELECT=YES, or all variants - tier.hasDefaultAudio = tier.hasDefaultAudio || audioTracksByGroup.hasDefaultAudio ? audioGroup.hasDefault : audioGroup.hasAutoSelect || !audioTracksByGroup.hasDefaultAudio && !audioTracksByGroup.hasAutoSelectAudio; - Object.keys(audioGroup.channels).forEach(function (channels) { - tier.channels[channels] = (tier.channels[channels] || 0) + audioGroup.channels[channels]; - }); - }); - } - return tiers; - }, {}); - } - function findMatchingOption(option, tracks, matchPredicate) { - if ('attrs' in option) { - var index = tracks.indexOf(option); - if (index !== -1) { - return index; - } - } - for (var i = 0; i < tracks.length; i++) { - var _track = tracks[i]; - if (matchesOption(option, _track, matchPredicate)) { - return i; - } - } - return -1; - } - function matchesOption(option, track, matchPredicate) { - var groupId = option.groupId, - name = option.name, - lang = option.lang, - assocLang = option.assocLang, - characteristics = option.characteristics, - isDefault = option.default; - var forced = option.forced; - return (groupId === undefined || track.groupId === groupId) && (name === undefined || track.name === name) && (lang === undefined || track.lang === lang) && (lang === undefined || track.assocLang === assocLang) && (isDefault === undefined || track.default === isDefault) && (forced === undefined || track.forced === forced) && (characteristics === undefined || characteristicsMatch(characteristics, track.characteristics)) && (matchPredicate === undefined || matchPredicate(option, track)); - } - function characteristicsMatch(characteristicsA, characteristicsB) { - if (characteristicsB === void 0) { - characteristicsB = ''; - } - var arrA = characteristicsA.split(','); - var arrB = characteristicsB.split(','); - // Expects each item to be unique: - return arrA.length === arrB.length && !arrA.some(function (el) { - return arrB.indexOf(el) === -1; - }); - } - function audioMatchPredicate(option, track) { - var audioCodec = option.audioCodec, - channels = option.channels; - return (audioCodec === undefined || (track.audioCodec || '').substring(0, 4) === audioCodec.substring(0, 4)) && (channels === undefined || channels === (track.channels || '2')); - } - function findClosestLevelWithAudioGroup(option, levels, allAudioTracks, searchIndex, matchPredicate) { - var currentLevel = levels[searchIndex]; - // Are there variants with same URI as current level? - // If so, find a match that does not require any level URI change - var variants = levels.reduce(function (variantMap, level, index) { - var uri = level.uri; - var renditions = variantMap[uri] || (variantMap[uri] = []); - renditions.push(index); - return variantMap; - }, {}); - var renditions = variants[currentLevel.uri]; - if (renditions.length > 1) { - searchIndex = Math.max.apply(Math, renditions); - } - // Find best match - var currentVideoRange = currentLevel.videoRange; - var currentFrameRate = currentLevel.frameRate; - var currentVideoCodec = currentLevel.codecSet.substring(0, 4); - var matchingVideo = searchDownAndUpList(levels, searchIndex, function (level) { - if (level.videoRange !== currentVideoRange || level.frameRate !== currentFrameRate || level.codecSet.substring(0, 4) !== currentVideoCodec) { - return false; - } - var audioGroups = level.audioGroups; - var tracks = allAudioTracks.filter(function (track) { - return !audioGroups || audioGroups.indexOf(track.groupId) !== -1; - }); - return findMatchingOption(option, tracks, matchPredicate) > -1; - }); - if (matchingVideo > -1) { - return matchingVideo; - } - return searchDownAndUpList(levels, searchIndex, function (level) { - var audioGroups = level.audioGroups; - var tracks = allAudioTracks.filter(function (track) { - return !audioGroups || audioGroups.indexOf(track.groupId) !== -1; - }); - return findMatchingOption(option, tracks, matchPredicate) > -1; - }); - } - function searchDownAndUpList(arr, searchIndex, predicate) { - for (var i = searchIndex; i; i--) { - if (predicate(arr[i])) { - return i; - } - } - for (var _i = searchIndex + 1; _i < arr.length; _i++) { - if (predicate(arr[_i])) { - return _i; - } - } - return -1; - } - - var AbrController = /*#__PURE__*/function () { - function AbrController(_hls) { - var _this = this; - this.hls = void 0; - this.lastLevelLoadSec = 0; - this.lastLoadedFragLevel = -1; - this.firstSelection = -1; - this._nextAutoLevel = -1; - this.nextAutoLevelKey = ''; - this.audioTracksByGroup = null; - this.codecTiers = null; - this.timer = -1; - this.fragCurrent = null; - this.partCurrent = null; - this.bitrateTestDelay = 0; - this.bwEstimator = void 0; - /* - This method monitors the download rate of the current fragment, and will downswitch if that fragment will not load - quickly enough to prevent underbuffering - */ - this._abandonRulesCheck = function () { - var frag = _this.fragCurrent, - part = _this.partCurrent, - hls = _this.hls; - var autoLevelEnabled = hls.autoLevelEnabled, - media = hls.media; - if (!frag || !media) { - return; - } - var now = performance.now(); - var stats = part ? part.stats : frag.stats; - var duration = part ? part.duration : frag.duration; - var timeLoading = now - stats.loading.start; - var minAutoLevel = hls.minAutoLevel; - // If frag loading is aborted, complete, or from lowest level, stop timer and return - if (stats.aborted || stats.loaded && stats.loaded === stats.total || frag.level <= minAutoLevel) { - _this.clearTimer(); - // reset forced auto level value so that next level will be selected - _this._nextAutoLevel = -1; - return; - } - - // This check only runs if we're in ABR mode and actually playing - if (!autoLevelEnabled || media.paused || !media.playbackRate || !media.readyState) { - return; - } - var bufferInfo = hls.mainForwardBufferInfo; - if (bufferInfo === null) { - return; - } - var ttfbEstimate = _this.bwEstimator.getEstimateTTFB(); - var playbackRate = Math.abs(media.playbackRate); - // To maintain stable adaptive playback, only begin monitoring frag loading after half or more of its playback duration has passed - if (timeLoading <= Math.max(ttfbEstimate, 1000 * (duration / (playbackRate * 2)))) { - return; - } - - // bufferStarvationDelay is an estimate of the amount time (in seconds) it will take to exhaust the buffer - var bufferStarvationDelay = bufferInfo.len / playbackRate; - var ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1; - var loadedFirstByte = stats.loaded && ttfb > -1; - var bwEstimate = _this.getBwEstimate(); - var levels = hls.levels; - var level = levels[frag.level]; - var expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8)); - var timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading; - if (timeStreaming < 1 && loadedFirstByte) { - timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate); - } - var loadRate = loadedFirstByte ? stats.loaded * 1000 / timeStreaming : 0; - // fragLoadDelay is an estimate of the time (in seconds) it will take to buffer the remainder of the fragment - var fragLoadedDelay = loadRate ? (expectedLen - stats.loaded) / loadRate : expectedLen * 8 / bwEstimate + ttfbEstimate / 1000; - // Only downswitch if the time to finish loading the current fragment is greater than the amount of buffer left - if (fragLoadedDelay <= bufferStarvationDelay) { - return; - } - var bwe = loadRate ? loadRate * 8 : bwEstimate; - var fragLevelNextLoadedDelay = Number.POSITIVE_INFINITY; - var nextLoadLevel; - // Iterate through lower level and try to find the largest one that avoids rebuffering - for (nextLoadLevel = frag.level - 1; nextLoadLevel > minAutoLevel; nextLoadLevel--) { - // compute time to load next fragment at lower level - // 8 = bits per byte (bps/Bps) - var levelNextBitrate = levels[nextLoadLevel].maxBitrate; - fragLevelNextLoadedDelay = _this.getTimeToLoadFrag(ttfbEstimate / 1000, bwe, duration * levelNextBitrate, !levels[nextLoadLevel].details); - if (fragLevelNextLoadedDelay < bufferStarvationDelay) { - break; - } - } - // Only emergency switch down if it takes less time to load a new fragment at lowest level instead of continuing - // to load the current one - if (fragLevelNextLoadedDelay >= fragLoadedDelay) { - return; - } - - // if estimated load time of new segment is completely unreasonable, ignore and do not emergency switch down - if (fragLevelNextLoadedDelay > duration * 10) { - return; - } - hls.nextLoadLevel = hls.nextAutoLevel = nextLoadLevel; - if (loadedFirstByte) { - // If there has been loading progress, sample bandwidth using loading time offset by minimum TTFB time - _this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded); - } else { - // If there has been no loading progress, sample TTFB - _this.bwEstimator.sampleTTFB(timeLoading); - } - var nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate; - if (_this.getBwEstimate() * _this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate) { - _this.resetEstimator(nextLoadLevelBitrate); - } - _this.clearTimer(); - logger.warn("[abr] Fragment " + frag.sn + (part ? ' part ' + part.index : '') + " of level " + frag.level + " is loading too slowly;\n Time to underbuffer: " + bufferStarvationDelay.toFixed(3) + " s\n Estimated load time for current fragment: " + fragLoadedDelay.toFixed(3) + " s\n Estimated load time for down switch fragment: " + fragLevelNextLoadedDelay.toFixed(3) + " s\n TTFB estimate: " + (ttfb | 0) + " ms\n Current BW estimate: " + (isFiniteNumber(bwEstimate) ? bwEstimate | 0 : 'Unknown') + " bps\n New BW estimate: " + (_this.getBwEstimate() | 0) + " bps\n Switching to level " + nextLoadLevel + " @ " + (nextLoadLevelBitrate | 0) + " bps"); - hls.trigger(Events.FRAG_LOAD_EMERGENCY_ABORTED, { - frag: frag, - part: part, - stats: stats - }); - }; - this.hls = _hls; - this.bwEstimator = this.initEstimator(); - this.registerListeners(); - } - var _proto = AbrController.prototype; - _proto.resetEstimator = function resetEstimator(abrEwmaDefaultEstimate) { - if (abrEwmaDefaultEstimate) { - logger.log("setting initial bwe to " + abrEwmaDefaultEstimate); - this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate; - } - this.firstSelection = -1; - this.bwEstimator = this.initEstimator(); - }; - _proto.initEstimator = function initEstimator() { - var config = this.hls.config; - return new EwmaBandWidthEstimator(config.abrEwmaSlowVoD, config.abrEwmaFastVoD, config.abrEwmaDefaultEstimate); - }; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.FRAG_LOADING, this.onFragLoading, this); - hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); - hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); - hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.on(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this); - hls.on(Events.ERROR, this.onError, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - if (!hls) { - return; - } - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.FRAG_LOADING, this.onFragLoading, this); - hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); - hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); - hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.off(Events.MAX_AUTO_LEVEL_UPDATED, this.onMaxAutoLevelUpdated, this); - hls.off(Events.ERROR, this.onError, this); - }; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.clearTimer(); - // @ts-ignore - this.hls = this._abandonRulesCheck = null; - this.fragCurrent = this.partCurrent = null; - }; - _proto.onManifestLoading = function onManifestLoading(event, data) { - this.lastLoadedFragLevel = -1; - this.firstSelection = -1; - this.lastLevelLoadSec = 0; - this.fragCurrent = this.partCurrent = null; - this.onLevelsUpdated(); - this.clearTimer(); - }; - _proto.onLevelsUpdated = function onLevelsUpdated() { - if (this.lastLoadedFragLevel > -1 && this.fragCurrent) { - this.lastLoadedFragLevel = this.fragCurrent.level; - } - this._nextAutoLevel = -1; - this.onMaxAutoLevelUpdated(); - this.codecTiers = null; - this.audioTracksByGroup = null; - }; - _proto.onMaxAutoLevelUpdated = function onMaxAutoLevelUpdated() { - this.firstSelection = -1; - this.nextAutoLevelKey = ''; - }; - _proto.onFragLoading = function onFragLoading(event, data) { - var frag = data.frag; - if (this.ignoreFragment(frag)) { - return; - } - if (!frag.bitrateTest) { - var _data$part; - this.fragCurrent = frag; - this.partCurrent = (_data$part = data.part) != null ? _data$part : null; - } - this.clearTimer(); - this.timer = self.setInterval(this._abandonRulesCheck, 100); - }; - _proto.onLevelSwitching = function onLevelSwitching(event, data) { - this.clearTimer(); - }; - _proto.onError = function onError(event, data) { - if (data.fatal) { - return; - } - switch (data.details) { - case ErrorDetails.BUFFER_ADD_CODEC_ERROR: - case ErrorDetails.BUFFER_APPEND_ERROR: - // Reset last loaded level so that a new selection can be made after calling recoverMediaError - this.lastLoadedFragLevel = -1; - this.firstSelection = -1; - break; - case ErrorDetails.FRAG_LOAD_TIMEOUT: - { - var frag = data.frag; - var fragCurrent = this.fragCurrent, - part = this.partCurrent; - if (frag && fragCurrent && frag.sn === fragCurrent.sn && frag.level === fragCurrent.level) { - var now = performance.now(); - var stats = part ? part.stats : frag.stats; - var timeLoading = now - stats.loading.start; - var ttfb = stats.loading.first ? stats.loading.first - stats.loading.start : -1; - var loadedFirstByte = stats.loaded && ttfb > -1; - if (loadedFirstByte) { - var ttfbEstimate = this.bwEstimator.getEstimateTTFB(); - this.bwEstimator.sample(timeLoading - Math.min(ttfbEstimate, ttfb), stats.loaded); - } else { - this.bwEstimator.sampleTTFB(timeLoading); - } - } - break; - } - } - }; - _proto.getTimeToLoadFrag = function getTimeToLoadFrag(timeToFirstByteSec, bandwidth, fragSizeBits, isSwitch) { - var fragLoadSec = timeToFirstByteSec + fragSizeBits / bandwidth; - var playlistLoadSec = isSwitch ? this.lastLevelLoadSec : 0; - return fragLoadSec + playlistLoadSec; - }; - _proto.onLevelLoaded = function onLevelLoaded(event, data) { - var config = this.hls.config; - var loading = data.stats.loading; - var timeLoadingMs = loading.end - loading.start; - if (isFiniteNumber(timeLoadingMs)) { - this.lastLevelLoadSec = timeLoadingMs / 1000; - } - if (data.details.live) { - this.bwEstimator.update(config.abrEwmaSlowLive, config.abrEwmaFastLive); - } else { - this.bwEstimator.update(config.abrEwmaSlowVoD, config.abrEwmaFastVoD); - } - }; - _proto.onFragLoaded = function onFragLoaded(event, _ref) { - var frag = _ref.frag, - part = _ref.part; - var stats = part ? part.stats : frag.stats; - if (frag.type === PlaylistLevelType.MAIN) { - this.bwEstimator.sampleTTFB(stats.loading.first - stats.loading.start); - } - if (this.ignoreFragment(frag)) { - return; - } - // stop monitoring bw once frag loaded - this.clearTimer(); - // reset forced auto level value so that next level will be selected - if (frag.level === this._nextAutoLevel) { - this._nextAutoLevel = -1; - } - this.firstSelection = -1; - - // compute level average bitrate - if (this.hls.config.abrMaxWithRealBitrate) { - var duration = part ? part.duration : frag.duration; - var level = this.hls.levels[frag.level]; - var loadedBytes = (level.loaded ? level.loaded.bytes : 0) + stats.loaded; - var loadedDuration = (level.loaded ? level.loaded.duration : 0) + duration; - level.loaded = { - bytes: loadedBytes, - duration: loadedDuration - }; - level.realBitrate = Math.round(8 * loadedBytes / loadedDuration); - } - if (frag.bitrateTest) { - var fragBufferedData = { - stats: stats, - frag: frag, - part: part, - id: frag.type - }; - this.onFragBuffered(Events.FRAG_BUFFERED, fragBufferedData); - frag.bitrateTest = false; - } else { - // store level id after successful fragment load for playback - this.lastLoadedFragLevel = frag.level; - } - }; - _proto.onFragBuffered = function onFragBuffered(event, data) { - var frag = data.frag, - part = data.part; - var stats = part != null && part.stats.loaded ? part.stats : frag.stats; - if (stats.aborted) { - return; - } - if (this.ignoreFragment(frag)) { - return; - } - // Use the difference between parsing and request instead of buffering and request to compute fragLoadingProcessing; - // rationale is that buffer appending only happens once media is attached. This can happen when config.startFragPrefetch - // is used. If we used buffering in that case, our BW estimate sample will be very large. - var processingMs = stats.parsing.end - stats.loading.start - Math.min(stats.loading.first - stats.loading.start, this.bwEstimator.getEstimateTTFB()); - this.bwEstimator.sample(processingMs, stats.loaded); - stats.bwEstimate = this.getBwEstimate(); - if (frag.bitrateTest) { - this.bitrateTestDelay = processingMs / 1000; - } else { - this.bitrateTestDelay = 0; - } - }; - _proto.ignoreFragment = function ignoreFragment(frag) { - // Only count non-alt-audio frags which were actually buffered in our BW calculations - return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment'; - }; - _proto.clearTimer = function clearTimer() { - if (this.timer > -1) { - self.clearInterval(this.timer); - this.timer = -1; - } - }; - _proto.getAutoLevelKey = function getAutoLevelKey() { - return this.getBwEstimate() + "_" + this.getStarvationDelay().toFixed(2); - }; - _proto.getNextABRAutoLevel = function getNextABRAutoLevel() { - var fragCurrent = this.fragCurrent, - partCurrent = this.partCurrent, - hls = this.hls; - var maxAutoLevel = hls.maxAutoLevel, - config = hls.config, - minAutoLevel = hls.minAutoLevel; - var currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0; - var avgbw = this.getBwEstimate(); - // bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted. - var bufferStarvationDelay = this.getStarvationDelay(); - var bwFactor = config.abrBandWidthFactor; - var bwUpFactor = config.abrBandWidthUpFactor; - - // First, look to see if we can find a level matching with our avg bandwidth AND that could also guarantee no rebuffering at all - if (bufferStarvationDelay) { - var _bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, 0, bwFactor, bwUpFactor); - if (_bestLevel >= 0) { - return _bestLevel; - } - } - // not possible to get rid of rebuffering... try to find level that will guarantee less than maxStarvationDelay of rebuffering - var maxStarvationDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxStarvationDelay) : config.maxStarvationDelay; - if (!bufferStarvationDelay) { - // in case buffer is empty, let's check if previous fragment was loaded to perform a bitrate test - var bitrateTestDelay = this.bitrateTestDelay; - if (bitrateTestDelay) { - // if it is the case, then we need to adjust our max starvation delay using maxLoadingDelay config value - // max video loading delay used in automatic start level selection : - // in that mode ABR controller will ensure that video loading time (ie the time to fetch the first fragment at lowest quality level + - // the time to fetch the fragment at the appropriate quality level is less than ```maxLoadingDelay``` ) - // cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration - var maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay; - maxStarvationDelay = maxLoadingDelay - bitrateTestDelay; - logger.info("[abr] bitrate test took " + Math.round(1000 * bitrateTestDelay) + "ms, set first fragment max fetchDuration to " + Math.round(1000 * maxStarvationDelay) + " ms"); - // don't use conservative factor on bitrate test - bwFactor = bwUpFactor = 1; - } - } - var bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor); - logger.info("[abr] " + (bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty') + ", optimal quality level " + bestLevel); - if (bestLevel > -1) { - return bestLevel; - } - // If no matching level found, see if min auto level would be a better option - var minLevel = hls.levels[minAutoLevel]; - var autoLevel = hls.levels[hls.loadLevel]; - if ((minLevel == null ? void 0 : minLevel.bitrate) < (autoLevel == null ? void 0 : autoLevel.bitrate)) { - return minAutoLevel; - } - // or if bitrate is not lower, continue to use loadLevel - return hls.loadLevel; - }; - _proto.getStarvationDelay = function getStarvationDelay() { - var hls = this.hls; - var media = hls.media; - if (!media) { - return Infinity; - } - // playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as - // if we're playing back at the normal rate. - var playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0; - var bufferInfo = hls.mainForwardBufferInfo; - return (bufferInfo ? bufferInfo.len : 0) / playbackRate; - }; - _proto.getBwEstimate = function getBwEstimate() { - return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate; - }; - _proto.findBestLevel = function findBestLevel(currentBw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor) { - var _level$details, - _this2 = this; - var maxFetchDuration = bufferStarvationDelay + maxStarvationDelay; - var lastLoadedFragLevel = this.lastLoadedFragLevel; - var selectionBaseLevel = lastLoadedFragLevel === -1 ? this.hls.firstLevel : lastLoadedFragLevel; - var fragCurrent = this.fragCurrent, - partCurrent = this.partCurrent; - var _this$hls = this.hls, - levels = _this$hls.levels, - allAudioTracks = _this$hls.allAudioTracks, - loadLevel = _this$hls.loadLevel, - config = _this$hls.config; - if (levels.length === 1) { - return 0; - } - var level = levels[selectionBaseLevel]; - var live = !!(level != null && (_level$details = level.details) != null && _level$details.live); - var firstSelection = loadLevel === -1 || lastLoadedFragLevel === -1; - var currentCodecSet; - var currentVideoRange = 'SDR'; - var currentFrameRate = (level == null ? void 0 : level.frameRate) || 0; - var audioPreference = config.audioPreference, - videoPreference = config.videoPreference; - var audioTracksByGroup = this.audioTracksByGroup || (this.audioTracksByGroup = getAudioTracksByGroup(allAudioTracks)); - if (firstSelection) { - if (this.firstSelection !== -1) { - return this.firstSelection; - } - var codecTiers = this.codecTiers || (this.codecTiers = getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel)); - var startTier = getStartCodecTier(codecTiers, currentVideoRange, currentBw, audioPreference, videoPreference); - var codecSet = startTier.codecSet, - videoRanges = startTier.videoRanges, - minFramerate = startTier.minFramerate, - minBitrate = startTier.minBitrate, - preferHDR = startTier.preferHDR; - currentCodecSet = codecSet; - currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0]; - currentFrameRate = minFramerate; - currentBw = Math.max(currentBw, minBitrate); - logger.log("[abr] picked start tier " + JSON.stringify(startTier)); - } else { - currentCodecSet = level == null ? void 0 : level.codecSet; - currentVideoRange = level == null ? void 0 : level.videoRange; - } - var currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0; - var ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000; - var levelsSkipped = []; - var _loop = function _loop() { - var _levelInfo$supportedR; - var levelInfo = levels[i]; - var upSwitch = i > selectionBaseLevel; - if (!levelInfo) { - return 0; // continue - } - if (config.useMediaCapabilities && !levelInfo.supportedResult && !levelInfo.supportedPromise) { - var mediaCapabilities = navigator.mediaCapabilities; - if (typeof (mediaCapabilities == null ? void 0 : mediaCapabilities.decodingInfo) === 'function' && requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)) { - levelInfo.supportedPromise = getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities); - levelInfo.supportedPromise.then(function (decodingInfo) { - if (!_this2.hls) { - return; - } - levelInfo.supportedResult = decodingInfo; - var levels = _this2.hls.levels; - var index = levels.indexOf(levelInfo); - if (decodingInfo.error) { - logger.warn("[abr] MediaCapabilities decodingInfo error: \"" + decodingInfo.error + "\" for level " + index + " " + JSON.stringify(decodingInfo)); - } else if (!decodingInfo.supported) { - logger.warn("[abr] Unsupported MediaCapabilities decodingInfo result for level " + index + " " + JSON.stringify(decodingInfo)); - if (index > -1 && levels.length > 1) { - logger.log("[abr] Removing unsupported level " + index); - _this2.hls.removeLevel(index); - } - } - }); - } else { - levelInfo.supportedResult = SUPPORTED_INFO_DEFAULT; - } - } - - // skip candidates which change codec-family or video-range, - // and which decrease or increase frame-rate for up and down-switch respectfully - if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || levelInfo.supportedResult && !((_levelInfo$supportedR = levelInfo.supportedResult.decodingInfoResults) != null && _levelInfo$supportedR[0].smooth)) { - levelsSkipped.push(i); - return 0; // continue - } - var levelDetails = levelInfo.details; - var avgDuration = (partCurrent ? levelDetails == null ? void 0 : levelDetails.partTarget : levelDetails == null ? void 0 : levelDetails.averagetargetduration) || currentFragDuration; - var adjustedbw; - // follow algorithm captured from stagefright : - // https://android.googlesource.com/platform/frameworks/av/+/master/media/libstagefright/httplive/LiveSession.cpp - // Pick the highest bandwidth stream below or equal to estimated bandwidth. - // consider only 80% of the available bandwidth, but if we are switching up, - // be even more conservative (70%) to avoid overestimating and immediately - // switching back. - if (!upSwitch) { - adjustedbw = bwFactor * currentBw; - } else { - adjustedbw = bwUpFactor * currentBw; - } - - // Use average bitrate when starvation delay (buffer length) is gt or eq two segment durations and rebuffering is not expected (maxStarvationDelay > 0) - var bitrate = currentFragDuration && bufferStarvationDelay >= currentFragDuration * 2 && maxStarvationDelay === 0 ? levels[i].averageBitrate : levels[i].maxBitrate; - var fetchDuration = _this2.getTimeToLoadFrag(ttfbEstimateSec, adjustedbw, bitrate * avgDuration, levelDetails === undefined); - var canSwitchWithinTolerance = - // if adjusted bw is greater than level bitrate AND - adjustedbw >= bitrate && ( - // no level change, or new level has no error history - i === lastLoadedFragLevel || levelInfo.loadError === 0 && levelInfo.fragmentError === 0) && ( - // fragment fetchDuration unknown OR live stream OR fragment fetchDuration less than max allowed fetch duration, then this level matches - // we don't account for max Fetch Duration for live streams, this is to avoid switching down when near the edge of live sliding window ... - // special case to support startLevel = -1 (bitrateTest) on live streams : in that case we should not exit loop so that findBestLevel will return -1 - fetchDuration <= ttfbEstimateSec || !isFiniteNumber(fetchDuration) || live && !_this2.bitrateTestDelay || fetchDuration < maxFetchDuration); - if (canSwitchWithinTolerance) { - var forcedAutoLevel = _this2.forcedAutoLevel; - if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) { - if (levelsSkipped.length) { - logger.trace("[abr] Skipped level(s) " + levelsSkipped.join(',') + " of " + maxAutoLevel + " max with CODECS and VIDEO-RANGE:\"" + levels[levelsSkipped[0]].codecs + "\" " + levels[levelsSkipped[0]].videoRange + "; not compatible with \"" + level.codecs + "\" " + currentVideoRange); - } - logger.info("[abr] switch candidate:" + selectionBaseLevel + "->" + i + " adjustedbw(" + Math.round(adjustedbw) + ")-bitrate=" + Math.round(adjustedbw - bitrate) + " ttfb:" + ttfbEstimateSec.toFixed(1) + " avgDuration:" + avgDuration.toFixed(1) + " maxFetchDuration:" + maxFetchDuration.toFixed(1) + " fetchDuration:" + fetchDuration.toFixed(1) + " firstSelection:" + firstSelection + " codecSet:" + currentCodecSet + " videoRange:" + currentVideoRange + " hls.loadLevel:" + loadLevel); - } - if (firstSelection) { - _this2.firstSelection = i; - } - // as we are looping from highest to lowest, this will return the best achievable quality level - return { - v: i - }; - } - }, - _ret; - for (var i = maxAutoLevel; i >= minAutoLevel; i--) { - _ret = _loop(); - if (_ret === 0) continue; - if (_ret) return _ret.v; - } - // not enough time budget even with quality level 0 ... rebuffering might happen - return -1; - }; - _createClass(AbrController, [{ - key: "firstAutoLevel", - get: function get() { - var _this$hls2 = this.hls, - maxAutoLevel = _this$hls2.maxAutoLevel, - minAutoLevel = _this$hls2.minAutoLevel; - var bwEstimate = this.getBwEstimate(); - var maxStartDelay = this.hls.config.maxStarvationDelay; - var abrAutoLevel = this.findBestLevel(bwEstimate, minAutoLevel, maxAutoLevel, 0, maxStartDelay, 1, 1); - if (abrAutoLevel > -1) { - return abrAutoLevel; - } - var firstLevel = this.hls.firstLevel; - var clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel); - logger.warn("[abr] Could not find best starting auto level. Defaulting to first in playlist " + firstLevel + " clamped to " + clamped); - return clamped; - } - }, { - key: "forcedAutoLevel", - get: function get() { - if (this.nextAutoLevelKey) { - return -1; - } - return this._nextAutoLevel; - } - - // return next auto level - }, { - key: "nextAutoLevel", - get: function get() { - var forcedAutoLevel = this.forcedAutoLevel; - var bwEstimator = this.bwEstimator; - var useEstimate = bwEstimator.canEstimate(); - var loadedFirstFrag = this.lastLoadedFragLevel > -1; - // in case next auto level has been forced, and bw not available or not reliable, return forced value - if (forcedAutoLevel !== -1 && (!useEstimate || !loadedFirstFrag || this.nextAutoLevelKey === this.getAutoLevelKey())) { - return forcedAutoLevel; - } - - // compute next level using ABR logic - var nextABRAutoLevel = useEstimate && loadedFirstFrag ? this.getNextABRAutoLevel() : this.firstAutoLevel; - - // use forced auto level while it hasn't errored more than ABR selection - if (forcedAutoLevel !== -1) { - var levels = this.hls.levels; - if (levels.length > Math.max(forcedAutoLevel, nextABRAutoLevel) && levels[forcedAutoLevel].loadError <= levels[nextABRAutoLevel].loadError) { - return forcedAutoLevel; - } - } - - // save result until state has changed - this._nextAutoLevel = nextABRAutoLevel; - this.nextAutoLevelKey = this.getAutoLevelKey(); - return nextABRAutoLevel; - }, - set: function set(nextLevel) { - var _this$hls3 = this.hls, - maxAutoLevel = _this$hls3.maxAutoLevel, - minAutoLevel = _this$hls3.minAutoLevel; - var value = Math.min(Math.max(nextLevel, minAutoLevel), maxAutoLevel); - if (this._nextAutoLevel !== value) { - this.nextAutoLevelKey = ''; - this._nextAutoLevel = value; - } - } - }]); - return AbrController; - }(); - - /** - * @ignore - * Sub-class specialization of EventHandler base class. - * - * TaskLoop allows to schedule a task function being called (optionnaly repeatedly) on the main loop, - * scheduled asynchroneously, avoiding recursive calls in the same tick. - * - * The task itself is implemented in `doTick`. It can be requested and called for single execution - * using the `tick` method. - * - * It will be assured that the task execution method (`tick`) only gets called once per main loop "tick", - * no matter how often it gets requested for execution. Execution in further ticks will be scheduled accordingly. - * - * If further execution requests have already been scheduled on the next tick, it can be checked with `hasNextTick`, - * and cancelled with `clearNextTick`. - * - * The task can be scheduled as an interval repeatedly with a period as parameter (see `setInterval`, `clearInterval`). - * - * Sub-classes need to implement the `doTick` method which will effectively have the task execution routine. - * - * Further explanations: - * - * The baseclass has a `tick` method that will schedule the doTick call. It may be called synchroneously - * only for a stack-depth of one. On re-entrant calls, sub-sequent calls are scheduled for next main loop ticks. - * - * When the task execution (`tick` method) is called in re-entrant way this is detected and - * we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further - * task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo). - */ - var TaskLoop = /*#__PURE__*/function () { - function TaskLoop() { - this._boundTick = void 0; - this._tickTimer = null; - this._tickInterval = null; - this._tickCallCount = 0; - this._boundTick = this.tick.bind(this); - } - var _proto = TaskLoop.prototype; - _proto.destroy = function destroy() { - this.onHandlerDestroying(); - this.onHandlerDestroyed(); - }; - _proto.onHandlerDestroying = function onHandlerDestroying() { - // clear all timers before unregistering from event bus - this.clearNextTick(); - this.clearInterval(); - }; - _proto.onHandlerDestroyed = function onHandlerDestroyed() {}; - _proto.hasInterval = function hasInterval() { - return !!this._tickInterval; - }; - _proto.hasNextTick = function hasNextTick() { - return !!this._tickTimer; - } - - /** - * @param millis - Interval time (ms) - * @eturns True when interval has been scheduled, false when already scheduled (no effect) - */; - _proto.setInterval = function setInterval(millis) { - if (!this._tickInterval) { - this._tickCallCount = 0; - this._tickInterval = self.setInterval(this._boundTick, millis); - return true; - } - return false; - } - - /** - * @returns True when interval was cleared, false when none was set (no effect) - */; - _proto.clearInterval = function clearInterval() { - if (this._tickInterval) { - self.clearInterval(this._tickInterval); - this._tickInterval = null; - return true; - } - return false; - } - - /** - * @returns True when timeout was cleared, false when none was set (no effect) - */; - _proto.clearNextTick = function clearNextTick() { - if (this._tickTimer) { - self.clearTimeout(this._tickTimer); - this._tickTimer = null; - return true; - } - return false; - } - - /** - * Will call the subclass doTick implementation in this main loop tick - * or in the next one (via setTimeout(,0)) in case it has already been called - * in this tick (in case this is a re-entrant call). - */; - _proto.tick = function tick() { - this._tickCallCount++; - if (this._tickCallCount === 1) { - this.doTick(); - // re-entrant call to tick from previous doTick call stack - // -> schedule a call on the next main loop iteration to process this task processing request - if (this._tickCallCount > 1) { - // make sure only one timer exists at any time at max - this.tickImmediate(); - } - this._tickCallCount = 0; - } - }; - _proto.tickImmediate = function tickImmediate() { - this.clearNextTick(); - this._tickTimer = self.setTimeout(this._boundTick, 0); - } - - /** - * For subclass to implement task logic - * @abstract - */; - _proto.doTick = function doTick() {}; - return TaskLoop; - }(); - - var FragmentState = { - NOT_LOADED: "NOT_LOADED", - APPENDING: "APPENDING", - PARTIAL: "PARTIAL", - OK: "OK" - }; - var FragmentTracker = /*#__PURE__*/function () { - function FragmentTracker(hls) { - this.activePartLists = Object.create(null); - this.endListFragments = Object.create(null); - this.fragments = Object.create(null); - this.timeRanges = Object.create(null); - this.bufferPadding = 0.2; - this.hls = void 0; - this.hasGaps = false; - this.hls = hls; - this._registerListeners(); - } - var _proto = FragmentTracker.prototype; - _proto._registerListeners = function _registerListeners() { - var hls = this.hls; - hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); - hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); - }; - _proto._unregisterListeners = function _unregisterListeners() { - var hls = this.hls; - hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); - hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); - }; - _proto.destroy = function destroy() { - this._unregisterListeners(); - // @ts-ignore - this.fragments = - // @ts-ignore - this.activePartLists = - // @ts-ignore - this.endListFragments = this.timeRanges = null; - } - - /** - * Return a Fragment or Part with an appended range that matches the position and levelType - * Otherwise, return null - */; - _proto.getAppendedFrag = function getAppendedFrag(position, levelType) { - var activeParts = this.activePartLists[levelType]; - if (activeParts) { - for (var i = activeParts.length; i--;) { - var activePart = activeParts[i]; - if (!activePart) { - break; - } - var appendedPTS = activePart.end; - if (activePart.start <= position && appendedPTS !== null && position <= appendedPTS) { - return activePart; - } - } - } - return this.getBufferedFrag(position, levelType); - } - - /** - * Return a buffered Fragment that matches the position and levelType. - * A buffered Fragment is one whose loading, parsing and appending is done (completed or "partial" meaning aborted). - * If not found any Fragment, return null - */; - _proto.getBufferedFrag = function getBufferedFrag(position, levelType) { - var fragments = this.fragments; - var keys = Object.keys(fragments); - for (var i = keys.length; i--;) { - var fragmentEntity = fragments[keys[i]]; - if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) { - var frag = fragmentEntity.body; - if (frag.start <= position && position <= frag.end) { - return frag; - } - } - } - return null; - } - - /** - * Partial fragments effected by coded frame eviction will be removed - * The browser will unload parts of the buffer to free up memory for new buffer data - * Fragments will need to be reloaded when the buffer is freed up, removing partial fragments will allow them to reload(since there might be parts that are still playable) - */; - _proto.detectEvictedFragments = function detectEvictedFragments(elementaryStream, timeRange, playlistType, appendedPart) { - var _this = this; - if (this.timeRanges) { - this.timeRanges[elementaryStream] = timeRange; - } - // Check if any flagged fragments have been unloaded - // excluding anything newer than appendedPartSn - var appendedPartSn = (appendedPart == null ? void 0 : appendedPart.fragment.sn) || -1; - Object.keys(this.fragments).forEach(function (key) { - var fragmentEntity = _this.fragments[key]; - if (!fragmentEntity) { - return; - } - if (appendedPartSn >= fragmentEntity.body.sn) { - return; - } - if (!fragmentEntity.buffered && !fragmentEntity.loaded) { - if (fragmentEntity.body.type === playlistType) { - _this.removeFragment(fragmentEntity.body); - } - return; - } - var esData = fragmentEntity.range[elementaryStream]; - if (!esData) { - return; - } - esData.time.some(function (time) { - var isNotBuffered = !_this.isTimeBuffered(time.startPTS, time.endPTS, timeRange); - if (isNotBuffered) { - // Unregister partial fragment as it needs to load again to be reused - _this.removeFragment(fragmentEntity.body); - } - return isNotBuffered; - }); - }); - } - - /** - * Checks if the fragment passed in is loaded in the buffer properly - * Partially loaded fragments will be registered as a partial fragment - */; - _proto.detectPartialFragments = function detectPartialFragments(data) { - var _this2 = this; - var timeRanges = this.timeRanges; - var frag = data.frag, - part = data.part; - if (!timeRanges || frag.sn === 'initSegment') { - return; - } - var fragKey = getFragmentKey(frag); - var fragmentEntity = this.fragments[fragKey]; - if (!fragmentEntity || fragmentEntity.buffered && frag.gap) { - return; - } - var isFragHint = !frag.relurl; - Object.keys(timeRanges).forEach(function (elementaryStream) { - var streamInfo = frag.elementaryStreams[elementaryStream]; - if (!streamInfo) { - return; - } - var timeRange = timeRanges[elementaryStream]; - var partial = isFragHint || streamInfo.partial === true; - fragmentEntity.range[elementaryStream] = _this2.getBufferedTimes(frag, part, partial, timeRange); - }); - fragmentEntity.loaded = null; - if (Object.keys(fragmentEntity.range).length) { - fragmentEntity.buffered = true; - var endList = fragmentEntity.body.endList = frag.endList || fragmentEntity.body.endList; - if (endList) { - this.endListFragments[fragmentEntity.body.type] = fragmentEntity; - } - if (!isPartial(fragmentEntity)) { - // Remove older fragment parts from lookup after frag is tracked as buffered - this.removeParts(frag.sn - 1, frag.type); - } - } else { - // remove fragment if nothing was appended - this.removeFragment(fragmentEntity.body); - } - }; - _proto.removeParts = function removeParts(snToKeep, levelType) { - var activeParts = this.activePartLists[levelType]; - if (!activeParts) { - return; - } - this.activePartLists[levelType] = activeParts.filter(function (part) { - return part.fragment.sn >= snToKeep; - }); - }; - _proto.fragBuffered = function fragBuffered(frag, force) { - var fragKey = getFragmentKey(frag); - var fragmentEntity = this.fragments[fragKey]; - if (!fragmentEntity && force) { - fragmentEntity = this.fragments[fragKey] = { - body: frag, - appendedPTS: null, - loaded: null, - buffered: false, - range: Object.create(null) - }; - if (frag.gap) { - this.hasGaps = true; - } - } - if (fragmentEntity) { - fragmentEntity.loaded = null; - fragmentEntity.buffered = true; - } - }; - _proto.getBufferedTimes = function getBufferedTimes(fragment, part, partial, timeRange) { - var buffered = { - time: [], - partial: partial - }; - var startPTS = fragment.start; - var endPTS = fragment.end; - var minEndPTS = fragment.minEndPTS || endPTS; - var maxStartPTS = fragment.maxStartPTS || startPTS; - for (var i = 0; i < timeRange.length; i++) { - var startTime = timeRange.start(i) - this.bufferPadding; - var endTime = timeRange.end(i) + this.bufferPadding; - if (maxStartPTS >= startTime && minEndPTS <= endTime) { - // Fragment is entirely contained in buffer - // No need to check the other timeRange times since it's completely playable - buffered.time.push({ - startPTS: Math.max(startPTS, timeRange.start(i)), - endPTS: Math.min(endPTS, timeRange.end(i)) - }); - break; - } else if (startPTS < endTime && endPTS > startTime) { - var start = Math.max(startPTS, timeRange.start(i)); - var end = Math.min(endPTS, timeRange.end(i)); - if (end > start) { - buffered.partial = true; - // Check for intersection with buffer - // Get playable sections of the fragment - buffered.time.push({ - startPTS: start, - endPTS: end - }); - } - } else if (endPTS <= startTime) { - // No need to check the rest of the timeRange as it is in order - break; - } - } - return buffered; - } - - /** - * Gets the partial fragment for a certain time - */; - _proto.getPartialFragment = function getPartialFragment(time) { - var bestFragment = null; - var timePadding; - var startTime; - var endTime; - var bestOverlap = 0; - var bufferPadding = this.bufferPadding, - fragments = this.fragments; - Object.keys(fragments).forEach(function (key) { - var fragmentEntity = fragments[key]; - if (!fragmentEntity) { - return; - } - if (isPartial(fragmentEntity)) { - startTime = fragmentEntity.body.start - bufferPadding; - endTime = fragmentEntity.body.end + bufferPadding; - if (time >= startTime && time <= endTime) { - // Use the fragment that has the most padding from start and end time - timePadding = Math.min(time - startTime, endTime - time); - if (bestOverlap <= timePadding) { - bestFragment = fragmentEntity.body; - bestOverlap = timePadding; - } - } - } - }); - return bestFragment; - }; - _proto.isEndListAppended = function isEndListAppended(type) { - var lastFragmentEntity = this.endListFragments[type]; - return lastFragmentEntity !== undefined && (lastFragmentEntity.buffered || isPartial(lastFragmentEntity)); - }; - _proto.getState = function getState(fragment) { - var fragKey = getFragmentKey(fragment); - var fragmentEntity = this.fragments[fragKey]; - if (fragmentEntity) { - if (!fragmentEntity.buffered) { - return FragmentState.APPENDING; - } else if (isPartial(fragmentEntity)) { - return FragmentState.PARTIAL; - } else { - return FragmentState.OK; - } - } - return FragmentState.NOT_LOADED; - }; - _proto.isTimeBuffered = function isTimeBuffered(startPTS, endPTS, timeRange) { - var startTime; - var endTime; - for (var i = 0; i < timeRange.length; i++) { - startTime = timeRange.start(i) - this.bufferPadding; - endTime = timeRange.end(i) + this.bufferPadding; - if (startPTS >= startTime && endPTS <= endTime) { - return true; - } - if (endPTS <= startTime) { - // No need to check the rest of the timeRange as it is in order - return false; - } - } - return false; - }; - _proto.onFragLoaded = function onFragLoaded(event, data) { - var frag = data.frag, - part = data.part; - // don't track initsegment (for which sn is not a number) - // don't track frags used for bitrateTest, they're irrelevant. - if (frag.sn === 'initSegment' || frag.bitrateTest) { - return; - } - - // Fragment entity `loaded` FragLoadedData is null when loading parts - var loaded = part ? null : data; - var fragKey = getFragmentKey(frag); - this.fragments[fragKey] = { - body: frag, - appendedPTS: null, - loaded: loaded, - buffered: false, - range: Object.create(null) - }; - }; - _proto.onBufferAppended = function onBufferAppended(event, data) { - var _this3 = this; - var frag = data.frag, - part = data.part, - timeRanges = data.timeRanges; - if (frag.sn === 'initSegment') { - return; - } - var playlistType = frag.type; - if (part) { - var activeParts = this.activePartLists[playlistType]; - if (!activeParts) { - this.activePartLists[playlistType] = activeParts = []; - } - activeParts.push(part); - } - // Store the latest timeRanges loaded in the buffer - this.timeRanges = timeRanges; - Object.keys(timeRanges).forEach(function (elementaryStream) { - var timeRange = timeRanges[elementaryStream]; - _this3.detectEvictedFragments(elementaryStream, timeRange, playlistType, part); - }); - }; - _proto.onFragBuffered = function onFragBuffered(event, data) { - this.detectPartialFragments(data); - }; - _proto.hasFragment = function hasFragment(fragment) { - var fragKey = getFragmentKey(fragment); - return !!this.fragments[fragKey]; - }; - _proto.hasParts = function hasParts(type) { - var _this$activePartLists; - return !!((_this$activePartLists = this.activePartLists[type]) != null && _this$activePartLists.length); - }; - _proto.removeFragmentsInRange = function removeFragmentsInRange(start, end, playlistType, withGapOnly, unbufferedOnly) { - var _this4 = this; - if (withGapOnly && !this.hasGaps) { - return; - } - Object.keys(this.fragments).forEach(function (key) { - var fragmentEntity = _this4.fragments[key]; - if (!fragmentEntity) { - return; - } - var frag = fragmentEntity.body; - if (frag.type !== playlistType || withGapOnly && !frag.gap) { - return; - } - if (frag.start < end && frag.end > start && (fragmentEntity.buffered || unbufferedOnly)) { - _this4.removeFragment(frag); - } - }); - }; - _proto.removeFragment = function removeFragment(fragment) { - var fragKey = getFragmentKey(fragment); - fragment.stats.loaded = 0; - fragment.clearElementaryStreamInfo(); - var activeParts = this.activePartLists[fragment.type]; - if (activeParts) { - var snToRemove = fragment.sn; - this.activePartLists[fragment.type] = activeParts.filter(function (part) { - return part.fragment.sn !== snToRemove; - }); - } - delete this.fragments[fragKey]; - if (fragment.endList) { - delete this.endListFragments[fragment.type]; - } - }; - _proto.removeAllFragments = function removeAllFragments() { - this.fragments = Object.create(null); - this.endListFragments = Object.create(null); - this.activePartLists = Object.create(null); - this.hasGaps = false; - }; - return FragmentTracker; - }(); - function isPartial(fragmentEntity) { - var _fragmentEntity$range, _fragmentEntity$range2, _fragmentEntity$range3; - return fragmentEntity.buffered && (fragmentEntity.body.gap || ((_fragmentEntity$range = fragmentEntity.range.video) == null ? void 0 : _fragmentEntity$range.partial) || ((_fragmentEntity$range2 = fragmentEntity.range.audio) == null ? void 0 : _fragmentEntity$range2.partial) || ((_fragmentEntity$range3 = fragmentEntity.range.audiovideo) == null ? void 0 : _fragmentEntity$range3.partial)); - } - function getFragmentKey(fragment) { - return fragment.type + "_" + fragment.level + "_" + fragment.sn; - } - - /** - * Provides methods dealing with buffer length retrieval for example. - * - * In general, a helper around HTML5 MediaElement TimeRanges gathered from `buffered` property. - * - * Also @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/buffered - */ - - var noopBuffered = { - length: 0, - start: function start() { - return 0; - }, - end: function end() { - return 0; - } - }; - var BufferHelper = /*#__PURE__*/function () { - function BufferHelper() {} - /** - * Return true if `media`'s buffered include `position` - */ - BufferHelper.isBuffered = function isBuffered(media, position) { - try { - if (media) { - var buffered = BufferHelper.getBuffered(media); - for (var i = 0; i < buffered.length; i++) { - if (position >= buffered.start(i) && position <= buffered.end(i)) { - return true; - } - } - } - } catch (error) { - // this is to catch - // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer': - // This SourceBuffer has been removed from the parent media source - } - return false; - }; - BufferHelper.bufferInfo = function bufferInfo(media, pos, maxHoleDuration) { - try { - if (media) { - var vbuffered = BufferHelper.getBuffered(media); - var buffered = []; - var i; - for (i = 0; i < vbuffered.length; i++) { - buffered.push({ - start: vbuffered.start(i), - end: vbuffered.end(i) - }); - } - return this.bufferedInfo(buffered, pos, maxHoleDuration); - } - } catch (error) { - // this is to catch - // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer': - // This SourceBuffer has been removed from the parent media source - } - return { - len: 0, - start: pos, - end: pos, - nextStart: undefined - }; - }; - BufferHelper.bufferedInfo = function bufferedInfo(buffered, pos, maxHoleDuration) { - pos = Math.max(0, pos); - // sort on buffer.start/smaller end (IE does not always return sorted buffered range) - buffered.sort(function (a, b) { - var diff = a.start - b.start; - if (diff) { - return diff; - } else { - return b.end - a.end; - } - }); - var buffered2 = []; - if (maxHoleDuration) { - // there might be some small holes between buffer time range - // consider that holes smaller than maxHoleDuration are irrelevant and build another - // buffer time range representations that discards those holes - for (var i = 0; i < buffered.length; i++) { - var buf2len = buffered2.length; - if (buf2len) { - var buf2end = buffered2[buf2len - 1].end; - // if small hole (value between 0 or maxHoleDuration ) or overlapping (negative) - if (buffered[i].start - buf2end < maxHoleDuration) { - // merge overlapping time ranges - // update lastRange.end only if smaller than item.end - // e.g. [ 1, 15] with [ 2,8] => [ 1,15] (no need to modify lastRange.end) - // whereas [ 1, 8] with [ 2,15] => [ 1,15] ( lastRange should switch from [1,8] to [1,15]) - if (buffered[i].end > buf2end) { - buffered2[buf2len - 1].end = buffered[i].end; - } - } else { - // big hole - buffered2.push(buffered[i]); - } - } else { - // first value - buffered2.push(buffered[i]); - } - } - } else { - buffered2 = buffered; - } - var bufferLen = 0; - - // bufferStartNext can possibly be undefined based on the conditional logic below - var bufferStartNext; - - // bufferStart and bufferEnd are buffer boundaries around current video position - var bufferStart = pos; - var bufferEnd = pos; - for (var _i = 0; _i < buffered2.length; _i++) { - var start = buffered2[_i].start; - var end = buffered2[_i].end; - // logger.log('buf start/end:' + buffered.start(i) + '/' + buffered.end(i)); - if (pos + maxHoleDuration >= start && pos < end) { - // play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length - bufferStart = start; - bufferEnd = end; - bufferLen = bufferEnd - pos; - } else if (pos + maxHoleDuration < start) { - bufferStartNext = start; - break; - } - } - return { - len: bufferLen, - start: bufferStart || 0, - end: bufferEnd || 0, - nextStart: bufferStartNext - }; - } - - /** - * Safe method to get buffered property. - * SourceBuffer.buffered may throw if SourceBuffer is removed from it's MediaSource - */; - BufferHelper.getBuffered = function getBuffered(media) { - try { - return media.buffered; - } catch (e) { - logger.log('failed to get media.buffered', e); - return noopBuffered; - } - }; - return BufferHelper; - }(); - - var ChunkMetadata = function ChunkMetadata(level, sn, id, size, part, partial) { - if (size === void 0) { - size = 0; - } - if (part === void 0) { - part = -1; - } - if (partial === void 0) { - partial = false; - } - this.level = void 0; - this.sn = void 0; - this.part = void 0; - this.id = void 0; - this.size = void 0; - this.partial = void 0; - this.transmuxing = getNewPerformanceTiming(); - this.buffering = { - audio: getNewPerformanceTiming(), - video: getNewPerformanceTiming(), - audiovideo: getNewPerformanceTiming() - }; - this.level = level; - this.sn = sn; - this.id = id; - this.size = size; - this.part = part; - this.partial = partial; - }; - function getNewPerformanceTiming() { - return { - start: 0, - executeStart: 0, - executeEnd: 0, - end: 0 - }; - } - - function findFirstFragWithCC(fragments, cc) { - for (var i = 0, len = fragments.length; i < len; i++) { - var _fragments$i; - if (((_fragments$i = fragments[i]) == null ? void 0 : _fragments$i.cc) === cc) { - return fragments[i]; - } - } - return null; - } - function shouldAlignOnDiscontinuities(lastFrag, switchDetails, details) { - if (switchDetails) { - if (details.endCC > details.startCC || lastFrag && lastFrag.cc < details.startCC) { - return true; - } - } - return false; - } - - // Find the first frag in the previous level which matches the CC of the first frag of the new level - function findDiscontinuousReferenceFrag(prevDetails, curDetails) { - var prevFrags = prevDetails.fragments; - var curFrags = curDetails.fragments; - if (!curFrags.length || !prevFrags.length) { - logger.log('No fragments to align'); - return; - } - var prevStartFrag = findFirstFragWithCC(prevFrags, curFrags[0].cc); - if (!prevStartFrag || prevStartFrag && !prevStartFrag.startPTS) { - logger.log('No frag in previous level to align on'); - return; - } - return prevStartFrag; - } - function adjustFragmentStart(frag, sliding) { - if (frag) { - var start = frag.start + sliding; - frag.start = frag.startPTS = start; - frag.endPTS = start + frag.duration; - } - } - function adjustSlidingStart(sliding, details) { - // Update segments - var fragments = details.fragments; - for (var i = 0, len = fragments.length; i < len; i++) { - adjustFragmentStart(fragments[i], sliding); - } - // Update LL-HLS parts at the end of the playlist - if (details.fragmentHint) { - adjustFragmentStart(details.fragmentHint, sliding); - } - details.alignedSliding = true; - } - - /** - * Using the parameters of the last level, this function computes PTS' of the new fragments so that they form a - * contiguous stream with the last fragments. - * The PTS of a fragment lets Hls.js know where it fits into a stream - by knowing every PTS, we know which fragment to - * download at any given time. PTS is normally computed when the fragment is demuxed, so taking this step saves us time - * and an extra download. - * @param lastFrag - * @param lastLevel - * @param details - */ - function alignStream(lastFrag, switchDetails, details) { - if (!switchDetails) { - return; - } - alignDiscontinuities(lastFrag, details, switchDetails); - if (!details.alignedSliding && switchDetails) { - // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level. - // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same - // discontinuity sequence. - alignMediaPlaylistByPDT(details, switchDetails); - } - if (!details.alignedSliding && switchDetails && !details.skippedSegments) { - // Try to align on sn so that we pick a better start fragment. - // Do not perform this on playlists with delta updates as this is only to align levels on switch - // and adjustSliding only adjusts fragments after skippedSegments. - adjustSliding(switchDetails, details); - } - } - - /** - * Computes the PTS if a new level's fragments using the PTS of a fragment in the last level which shares the same - * discontinuity sequence. - * @param lastFrag - The last Fragment which shares the same discontinuity sequence - * @param lastLevel - The details of the last loaded level - * @param details - The details of the new level - */ - function alignDiscontinuities(lastFrag, details, switchDetails) { - if (shouldAlignOnDiscontinuities(lastFrag, switchDetails, details)) { - var referenceFrag = findDiscontinuousReferenceFrag(switchDetails, details); - if (referenceFrag && isFiniteNumber(referenceFrag.start)) { - logger.log("Adjusting PTS using last level due to CC increase within current level " + details.url); - adjustSlidingStart(referenceFrag.start, details); - } - } - } - - /** - * Ensures appropriate time-alignment between renditions based on PDT. - * This function assumes the timelines represented in `refDetails` are accurate, including the PDTs - * for the last discontinuity sequence number shared by both playlists when present, - * and uses the "wallclock"/PDT timeline as a cross-reference to `details`, adjusting the presentation - * times/timelines of `details` accordingly. - * Given the asynchronous nature of fetches and initial loads of live `main` and audio/subtitle tracks, - * the primary purpose of this function is to ensure the "local timelines" of audio/subtitle tracks - * are aligned to the main/video timeline, using PDT as the cross-reference/"anchor" that should - * be consistent across playlists, per the HLS spec. - * @param details - The details of the rendition you'd like to time-align (e.g. an audio rendition). - * @param refDetails - The details of the reference rendition with start and PDT times for alignment. - */ - function alignMediaPlaylistByPDT(details, refDetails) { - if (!details.hasProgramDateTime || !refDetails.hasProgramDateTime) { - return; - } - var fragments = details.fragments; - var refFragments = refDetails.fragments; - if (!fragments.length || !refFragments.length) { - return; - } - - // Calculate a delta to apply to all fragments according to the delta in PDT times and start times - // of a fragment in the reference details, and a fragment in the target details of the same discontinuity. - // If a fragment of the same discontinuity was not found use the middle fragment of both. - var refFrag; - var frag; - var targetCC = Math.min(refDetails.endCC, details.endCC); - if (refDetails.startCC < targetCC && details.startCC < targetCC) { - refFrag = findFirstFragWithCC(refFragments, targetCC); - frag = findFirstFragWithCC(fragments, targetCC); - } - if (!refFrag || !frag) { - refFrag = refFragments[Math.floor(refFragments.length / 2)]; - frag = findFirstFragWithCC(fragments, refFrag.cc) || fragments[Math.floor(fragments.length / 2)]; - } - var refPDT = refFrag.programDateTime; - var targetPDT = frag.programDateTime; - if (!refPDT || !targetPDT) { - return; - } - var delta = (targetPDT - refPDT) / 1000 - (frag.start - refFrag.start); - adjustSlidingStart(delta, details); - } - - var MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb - var FragmentLoader = /*#__PURE__*/function () { - function FragmentLoader(config) { - this.config = void 0; - this.loader = null; - this.partLoadTimeout = -1; - this.config = config; - } - var _proto = FragmentLoader.prototype; - _proto.destroy = function destroy() { - if (this.loader) { - this.loader.destroy(); - this.loader = null; - } - }; - _proto.abort = function abort() { - if (this.loader) { - // Abort the loader for current fragment. Only one may load at any given time - this.loader.abort(); - } - }; - _proto.load = function load(frag, _onProgress) { - var _this = this; - var url = frag.url; - if (!url) { - return Promise.reject(new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.FRAG_LOAD_ERROR, - fatal: false, - frag: frag, - error: new Error("Fragment does not have a " + (url ? 'part list' : 'url')), - networkDetails: null - })); - } - this.abort(); - var config = this.config; - var FragmentILoader = config.fLoader; - var DefaultILoader = config.loader; - return new Promise(function (resolve, reject) { - if (_this.loader) { - _this.loader.destroy(); - } - if (frag.gap) { - if (frag.tagList.some(function (tags) { - return tags[0] === 'GAP'; - })) { - reject(createGapLoadError(frag)); - return; - } else { - // Reset temporary treatment as GAP tag - frag.gap = false; - } - } - var loader = _this.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config); - var loaderContext = createLoaderContext(frag); - var loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default); - var loaderConfig = { - loadPolicy: loadPolicy, - timeout: loadPolicy.maxLoadTimeMs, - maxRetry: 0, - retryDelay: 0, - maxRetryDelay: 0, - highWaterMark: frag.sn === 'initSegment' ? Infinity : MIN_CHUNK_SIZE - }; - // Assign frag stats to the loader's stats reference - frag.stats = loader.stats; - loader.load(loaderContext, loaderConfig, { - onSuccess: function onSuccess(response, stats, context, networkDetails) { - _this.resetLoader(frag, loader); - var payload = response.data; - if (context.resetIV && frag.decryptdata) { - frag.decryptdata.iv = new Uint8Array(payload.slice(0, 16)); - payload = payload.slice(16); - } - resolve({ - frag: frag, - part: null, - payload: payload, - networkDetails: networkDetails - }); - }, - onError: function onError(response, context, networkDetails, stats) { - _this.resetLoader(frag, loader); - reject(new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.FRAG_LOAD_ERROR, - fatal: false, - frag: frag, - response: _objectSpread2({ - url: url, - data: undefined - }, response), - error: new Error("HTTP Error " + response.code + " " + response.text), - networkDetails: networkDetails, - stats: stats - })); - }, - onAbort: function onAbort(stats, context, networkDetails) { - _this.resetLoader(frag, loader); - reject(new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.INTERNAL_ABORTED, - fatal: false, - frag: frag, - error: new Error('Aborted'), - networkDetails: networkDetails, - stats: stats - })); - }, - onTimeout: function onTimeout(stats, context, networkDetails) { - _this.resetLoader(frag, loader); - reject(new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.FRAG_LOAD_TIMEOUT, - fatal: false, - frag: frag, - error: new Error("Timeout after " + loaderConfig.timeout + "ms"), - networkDetails: networkDetails, - stats: stats - })); - }, - onProgress: function onProgress(stats, context, data, networkDetails) { - if (_onProgress) { - _onProgress({ - frag: frag, - part: null, - payload: data, - networkDetails: networkDetails - }); - } - } - }); - }); - }; - _proto.loadPart = function loadPart(frag, part, onProgress) { - var _this2 = this; - this.abort(); - var config = this.config; - var FragmentILoader = config.fLoader; - var DefaultILoader = config.loader; - return new Promise(function (resolve, reject) { - if (_this2.loader) { - _this2.loader.destroy(); - } - if (frag.gap || part.gap) { - reject(createGapLoadError(frag, part)); - return; - } - var loader = _this2.loader = frag.loader = FragmentILoader ? new FragmentILoader(config) : new DefaultILoader(config); - var loaderContext = createLoaderContext(frag, part); - // Should we define another load policy for parts? - var loadPolicy = getLoaderConfigWithoutReties(config.fragLoadPolicy.default); - var loaderConfig = { - loadPolicy: loadPolicy, - timeout: loadPolicy.maxLoadTimeMs, - maxRetry: 0, - retryDelay: 0, - maxRetryDelay: 0, - highWaterMark: MIN_CHUNK_SIZE - }; - // Assign part stats to the loader's stats reference - part.stats = loader.stats; - loader.load(loaderContext, loaderConfig, { - onSuccess: function onSuccess(response, stats, context, networkDetails) { - _this2.resetLoader(frag, loader); - _this2.updateStatsFromPart(frag, part); - var partLoadedData = { - frag: frag, - part: part, - payload: response.data, - networkDetails: networkDetails - }; - onProgress(partLoadedData); - resolve(partLoadedData); - }, - onError: function onError(response, context, networkDetails, stats) { - _this2.resetLoader(frag, loader); - reject(new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.FRAG_LOAD_ERROR, - fatal: false, - frag: frag, - part: part, - response: _objectSpread2({ - url: loaderContext.url, - data: undefined - }, response), - error: new Error("HTTP Error " + response.code + " " + response.text), - networkDetails: networkDetails, - stats: stats - })); - }, - onAbort: function onAbort(stats, context, networkDetails) { - frag.stats.aborted = part.stats.aborted; - _this2.resetLoader(frag, loader); - reject(new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.INTERNAL_ABORTED, - fatal: false, - frag: frag, - part: part, - error: new Error('Aborted'), - networkDetails: networkDetails, - stats: stats - })); - }, - onTimeout: function onTimeout(stats, context, networkDetails) { - _this2.resetLoader(frag, loader); - reject(new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.FRAG_LOAD_TIMEOUT, - fatal: false, - frag: frag, - part: part, - error: new Error("Timeout after " + loaderConfig.timeout + "ms"), - networkDetails: networkDetails, - stats: stats - })); - } - }); - }); - }; - _proto.updateStatsFromPart = function updateStatsFromPart(frag, part) { - var fragStats = frag.stats; - var partStats = part.stats; - var partTotal = partStats.total; - fragStats.loaded += partStats.loaded; - if (partTotal) { - var estTotalParts = Math.round(frag.duration / part.duration); - var estLoadedParts = Math.min(Math.round(fragStats.loaded / partTotal), estTotalParts); - var estRemainingParts = estTotalParts - estLoadedParts; - var estRemainingBytes = estRemainingParts * Math.round(fragStats.loaded / estLoadedParts); - fragStats.total = fragStats.loaded + estRemainingBytes; - } else { - fragStats.total = Math.max(fragStats.loaded, fragStats.total); - } - var fragLoading = fragStats.loading; - var partLoading = partStats.loading; - if (fragLoading.start) { - // add to fragment loader latency - fragLoading.first += partLoading.first - partLoading.start; - } else { - fragLoading.start = partLoading.start; - fragLoading.first = partLoading.first; - } - fragLoading.end = partLoading.end; - }; - _proto.resetLoader = function resetLoader(frag, loader) { - frag.loader = null; - if (this.loader === loader) { - self.clearTimeout(this.partLoadTimeout); - this.loader = null; - } - loader.destroy(); - }; - return FragmentLoader; - }(); - function createLoaderContext(frag, part) { - if (part === void 0) { - part = null; - } - var segment = part || frag; - var loaderContext = { - frag: frag, - part: part, - responseType: 'arraybuffer', - url: segment.url, - headers: {}, - rangeStart: 0, - rangeEnd: 0 - }; - var start = segment.byteRangeStartOffset; - var end = segment.byteRangeEndOffset; - if (isFiniteNumber(start) && isFiniteNumber(end)) { - var _frag$decryptdata; - var byteRangeStart = start; - var byteRangeEnd = end; - if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') { - // MAP segment encrypted with method 'AES-128', when served with HTTP Range, - // has the unencrypted size specified in the range. - // Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6 - var fragmentLen = end - start; - if (fragmentLen % 16) { - byteRangeEnd = end + (16 - fragmentLen % 16); - } - if (start !== 0) { - loaderContext.resetIV = true; - byteRangeStart = start - 16; - } - } - loaderContext.rangeStart = byteRangeStart; - loaderContext.rangeEnd = byteRangeEnd; - } - return loaderContext; - } - function createGapLoadError(frag, part) { - var error = new Error("GAP " + (frag.gap ? 'tag' : 'attribute') + " found"); - var errorData = { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_GAP, - fatal: false, - frag: frag, - error: error, - networkDetails: null - }; - if (part) { - errorData.part = part; - } - (part ? part : frag).stats.aborted = true; - return new LoadError(errorData); - } - var LoadError = /*#__PURE__*/function (_Error) { - _inheritsLoose(LoadError, _Error); - function LoadError(data) { - var _this3; - _this3 = _Error.call(this, data.error.message) || this; - _this3.data = void 0; - _this3.data = data; - return _this3; - } - return LoadError; - }( /*#__PURE__*/_wrapNativeSuper(Error)); - - var AESCrypto = /*#__PURE__*/function () { - function AESCrypto(subtle, iv) { - this.subtle = void 0; - this.aesIV = void 0; - this.subtle = subtle; - this.aesIV = iv; - } - var _proto = AESCrypto.prototype; - _proto.decrypt = function decrypt(data, key) { - return this.subtle.decrypt({ - name: 'AES-CBC', - iv: this.aesIV - }, key, data); - }; - return AESCrypto; - }(); - - var FastAESKey = /*#__PURE__*/function () { - function FastAESKey(subtle, key) { - this.subtle = void 0; - this.key = void 0; - this.subtle = subtle; - this.key = key; - } - var _proto = FastAESKey.prototype; - _proto.expandKey = function expandKey() { - return this.subtle.importKey('raw', this.key, { - name: 'AES-CBC' - }, false, ['encrypt', 'decrypt']); - }; - return FastAESKey; - }(); - - // PKCS7 - function removePadding(array) { - var outputBytes = array.byteLength; - var paddingBytes = outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1); - if (paddingBytes) { - return sliceUint8(array, 0, outputBytes - paddingBytes); - } - return array; - } - var AESDecryptor = /*#__PURE__*/function () { - function AESDecryptor() { - this.rcon = [0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; - this.subMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)]; - this.invSubMix = [new Uint32Array(256), new Uint32Array(256), new Uint32Array(256), new Uint32Array(256)]; - this.sBox = new Uint32Array(256); - this.invSBox = new Uint32Array(256); - this.key = new Uint32Array(0); - this.ksRows = 0; - this.keySize = 0; - this.keySchedule = void 0; - this.invKeySchedule = void 0; - this.initTable(); - } - - // Using view.getUint32() also swaps the byte order. - var _proto = AESDecryptor.prototype; - _proto.uint8ArrayToUint32Array_ = function uint8ArrayToUint32Array_(arrayBuffer) { - var view = new DataView(arrayBuffer); - var newArray = new Uint32Array(4); - for (var i = 0; i < 4; i++) { - newArray[i] = view.getUint32(i * 4); - } - return newArray; - }; - _proto.initTable = function initTable() { - var sBox = this.sBox; - var invSBox = this.invSBox; - var subMix = this.subMix; - var subMix0 = subMix[0]; - var subMix1 = subMix[1]; - var subMix2 = subMix[2]; - var subMix3 = subMix[3]; - var invSubMix = this.invSubMix; - var invSubMix0 = invSubMix[0]; - var invSubMix1 = invSubMix[1]; - var invSubMix2 = invSubMix[2]; - var invSubMix3 = invSubMix[3]; - var d = new Uint32Array(256); - var x = 0; - var xi = 0; - var i = 0; - for (i = 0; i < 256; i++) { - if (i < 128) { - d[i] = i << 1; - } else { - d[i] = i << 1 ^ 0x11b; - } - } - for (i = 0; i < 256; i++) { - var sx = xi ^ xi << 1 ^ xi << 2 ^ xi << 3 ^ xi << 4; - sx = sx >>> 8 ^ sx & 0xff ^ 0x63; - sBox[x] = sx; - invSBox[sx] = x; - - // Compute multiplication - var x2 = d[x]; - var x4 = d[x2]; - var x8 = d[x4]; - - // Compute sub/invSub bytes, mix columns tables - var t = d[sx] * 0x101 ^ sx * 0x1010100; - subMix0[x] = t << 24 | t >>> 8; - subMix1[x] = t << 16 | t >>> 16; - subMix2[x] = t << 8 | t >>> 24; - subMix3[x] = t; - - // Compute inv sub bytes, inv mix columns tables - t = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100; - invSubMix0[sx] = t << 24 | t >>> 8; - invSubMix1[sx] = t << 16 | t >>> 16; - invSubMix2[sx] = t << 8 | t >>> 24; - invSubMix3[sx] = t; - - // Compute next counter - if (!x) { - x = xi = 1; - } else { - x = x2 ^ d[d[d[x8 ^ x2]]]; - xi ^= d[d[xi]]; - } - } - }; - _proto.expandKey = function expandKey(keyBuffer) { - // convert keyBuffer to Uint32Array - var key = this.uint8ArrayToUint32Array_(keyBuffer); - var sameKey = true; - var offset = 0; - while (offset < key.length && sameKey) { - sameKey = key[offset] === this.key[offset]; - offset++; - } - if (sameKey) { - return; - } - this.key = key; - var keySize = this.keySize = key.length; - if (keySize !== 4 && keySize !== 6 && keySize !== 8) { - throw new Error('Invalid aes key size=' + keySize); - } - var ksRows = this.ksRows = (keySize + 6 + 1) * 4; - var ksRow; - var invKsRow; - var keySchedule = this.keySchedule = new Uint32Array(ksRows); - var invKeySchedule = this.invKeySchedule = new Uint32Array(ksRows); - var sbox = this.sBox; - var rcon = this.rcon; - var invSubMix = this.invSubMix; - var invSubMix0 = invSubMix[0]; - var invSubMix1 = invSubMix[1]; - var invSubMix2 = invSubMix[2]; - var invSubMix3 = invSubMix[3]; - var prev; - var t; - for (ksRow = 0; ksRow < ksRows; ksRow++) { - if (ksRow < keySize) { - prev = keySchedule[ksRow] = key[ksRow]; - continue; - } - t = prev; - if (ksRow % keySize === 0) { - // Rot word - t = t << 8 | t >>> 24; - - // Sub word - t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff]; - - // Mix Rcon - t ^= rcon[ksRow / keySize | 0] << 24; - } else if (keySize > 6 && ksRow % keySize === 4) { - // Sub word - t = sbox[t >>> 24] << 24 | sbox[t >>> 16 & 0xff] << 16 | sbox[t >>> 8 & 0xff] << 8 | sbox[t & 0xff]; - } - keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0; - } - for (invKsRow = 0; invKsRow < ksRows; invKsRow++) { - ksRow = ksRows - invKsRow; - if (invKsRow & 3) { - t = keySchedule[ksRow]; - } else { - t = keySchedule[ksRow - 4]; - } - if (invKsRow < 4 || ksRow <= 4) { - invKeySchedule[invKsRow] = t; - } else { - invKeySchedule[invKsRow] = invSubMix0[sbox[t >>> 24]] ^ invSubMix1[sbox[t >>> 16 & 0xff]] ^ invSubMix2[sbox[t >>> 8 & 0xff]] ^ invSubMix3[sbox[t & 0xff]]; - } - invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0; - } - } - - // Adding this as a method greatly improves performance. - ; - _proto.networkToHostOrderSwap = function networkToHostOrderSwap(word) { - return word << 24 | (word & 0xff00) << 8 | (word & 0xff0000) >> 8 | word >>> 24; - }; - _proto.decrypt = function decrypt(inputArrayBuffer, offset, aesIV) { - var nRounds = this.keySize + 6; - var invKeySchedule = this.invKeySchedule; - var invSBOX = this.invSBox; - var invSubMix = this.invSubMix; - var invSubMix0 = invSubMix[0]; - var invSubMix1 = invSubMix[1]; - var invSubMix2 = invSubMix[2]; - var invSubMix3 = invSubMix[3]; - var initVector = this.uint8ArrayToUint32Array_(aesIV); - var initVector0 = initVector[0]; - var initVector1 = initVector[1]; - var initVector2 = initVector[2]; - var initVector3 = initVector[3]; - var inputInt32 = new Int32Array(inputArrayBuffer); - var outputInt32 = new Int32Array(inputInt32.length); - var t0, t1, t2, t3; - var s0, s1, s2, s3; - var inputWords0, inputWords1, inputWords2, inputWords3; - var ksRow, i; - var swapWord = this.networkToHostOrderSwap; - while (offset < inputInt32.length) { - inputWords0 = swapWord(inputInt32[offset]); - inputWords1 = swapWord(inputInt32[offset + 1]); - inputWords2 = swapWord(inputInt32[offset + 2]); - inputWords3 = swapWord(inputInt32[offset + 3]); - s0 = inputWords0 ^ invKeySchedule[0]; - s1 = inputWords3 ^ invKeySchedule[1]; - s2 = inputWords2 ^ invKeySchedule[2]; - s3 = inputWords1 ^ invKeySchedule[3]; - ksRow = 4; - - // Iterate through the rounds of decryption - for (i = 1; i < nRounds; i++) { - t0 = invSubMix0[s0 >>> 24] ^ invSubMix1[s1 >> 16 & 0xff] ^ invSubMix2[s2 >> 8 & 0xff] ^ invSubMix3[s3 & 0xff] ^ invKeySchedule[ksRow]; - t1 = invSubMix0[s1 >>> 24] ^ invSubMix1[s2 >> 16 & 0xff] ^ invSubMix2[s3 >> 8 & 0xff] ^ invSubMix3[s0 & 0xff] ^ invKeySchedule[ksRow + 1]; - t2 = invSubMix0[s2 >>> 24] ^ invSubMix1[s3 >> 16 & 0xff] ^ invSubMix2[s0 >> 8 & 0xff] ^ invSubMix3[s1 & 0xff] ^ invKeySchedule[ksRow + 2]; - t3 = invSubMix0[s3 >>> 24] ^ invSubMix1[s0 >> 16 & 0xff] ^ invSubMix2[s1 >> 8 & 0xff] ^ invSubMix3[s2 & 0xff] ^ invKeySchedule[ksRow + 3]; - // Update state - s0 = t0; - s1 = t1; - s2 = t2; - s3 = t3; - ksRow = ksRow + 4; - } - - // Shift rows, sub bytes, add round key - t0 = invSBOX[s0 >>> 24] << 24 ^ invSBOX[s1 >> 16 & 0xff] << 16 ^ invSBOX[s2 >> 8 & 0xff] << 8 ^ invSBOX[s3 & 0xff] ^ invKeySchedule[ksRow]; - t1 = invSBOX[s1 >>> 24] << 24 ^ invSBOX[s2 >> 16 & 0xff] << 16 ^ invSBOX[s3 >> 8 & 0xff] << 8 ^ invSBOX[s0 & 0xff] ^ invKeySchedule[ksRow + 1]; - t2 = invSBOX[s2 >>> 24] << 24 ^ invSBOX[s3 >> 16 & 0xff] << 16 ^ invSBOX[s0 >> 8 & 0xff] << 8 ^ invSBOX[s1 & 0xff] ^ invKeySchedule[ksRow + 2]; - t3 = invSBOX[s3 >>> 24] << 24 ^ invSBOX[s0 >> 16 & 0xff] << 16 ^ invSBOX[s1 >> 8 & 0xff] << 8 ^ invSBOX[s2 & 0xff] ^ invKeySchedule[ksRow + 3]; - - // Write - outputInt32[offset] = swapWord(t0 ^ initVector0); - outputInt32[offset + 1] = swapWord(t3 ^ initVector1); - outputInt32[offset + 2] = swapWord(t2 ^ initVector2); - outputInt32[offset + 3] = swapWord(t1 ^ initVector3); - - // reset initVector to last 4 unsigned int - initVector0 = inputWords0; - initVector1 = inputWords1; - initVector2 = inputWords2; - initVector3 = inputWords3; - offset = offset + 4; - } - return outputInt32.buffer; - }; - return AESDecryptor; - }(); - - var CHUNK_SIZE = 16; // 16 bytes, 128 bits - var Decrypter = /*#__PURE__*/function () { - function Decrypter(config, _temp) { - var _ref = _temp === void 0 ? {} : _temp, - _ref$removePKCS7Paddi = _ref.removePKCS7Padding, - removePKCS7Padding = _ref$removePKCS7Paddi === void 0 ? true : _ref$removePKCS7Paddi; - this.logEnabled = true; - this.removePKCS7Padding = void 0; - this.subtle = null; - this.softwareDecrypter = null; - this.key = null; - this.fastAesKey = null; - this.remainderData = null; - this.currentIV = null; - this.currentResult = null; - this.useSoftware = void 0; - this.useSoftware = config.enableSoftwareAES; - this.removePKCS7Padding = removePKCS7Padding; - // built in decryptor expects PKCS7 padding - if (removePKCS7Padding) { - try { - var browserCrypto = self.crypto; - if (browserCrypto) { - this.subtle = browserCrypto.subtle || browserCrypto.webkitSubtle; - } - } catch (e) { - /* no-op */ - } - } - this.useSoftware = !this.subtle; - } - var _proto = Decrypter.prototype; - _proto.destroy = function destroy() { - this.subtle = null; - this.softwareDecrypter = null; - this.key = null; - this.fastAesKey = null; - this.remainderData = null; - this.currentIV = null; - this.currentResult = null; - }; - _proto.isSync = function isSync() { - return this.useSoftware; - }; - _proto.flush = function flush() { - var currentResult = this.currentResult, - remainderData = this.remainderData; - if (!currentResult || remainderData) { - this.reset(); - return null; - } - var data = new Uint8Array(currentResult); - this.reset(); - if (this.removePKCS7Padding) { - return removePadding(data); - } - return data; - }; - _proto.reset = function reset() { - this.currentResult = null; - this.currentIV = null; - this.remainderData = null; - if (this.softwareDecrypter) { - this.softwareDecrypter = null; - } - }; - _proto.decrypt = function decrypt(data, key, iv) { - var _this = this; - if (this.useSoftware) { - return new Promise(function (resolve, reject) { - _this.softwareDecrypt(new Uint8Array(data), key, iv); - var decryptResult = _this.flush(); - if (decryptResult) { - resolve(decryptResult.buffer); - } else { - reject(new Error('[softwareDecrypt] Failed to decrypt data')); - } - }); - } - return this.webCryptoDecrypt(new Uint8Array(data), key, iv); - } - - // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached - // data is handled in the flush() call - ; - _proto.softwareDecrypt = function softwareDecrypt(data, key, iv) { - var currentIV = this.currentIV, - currentResult = this.currentResult, - remainderData = this.remainderData; - this.logOnce('JS AES decrypt'); - // The output is staggered during progressive parsing - the current result is cached, and emitted on the next call - // This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached - // the end on flush(), but by that time we have already received all bytes for the segment. - // Progressive decryption does not work with WebCrypto - - if (remainderData) { - data = appendUint8Array(remainderData, data); - this.remainderData = null; - } - - // Byte length must be a multiple of 16 (AES-128 = 128 bit blocks = 16 bytes) - var currentChunk = this.getValidChunk(data); - if (!currentChunk.length) { - return null; - } - if (currentIV) { - iv = currentIV; - } - var softwareDecrypter = this.softwareDecrypter; - if (!softwareDecrypter) { - softwareDecrypter = this.softwareDecrypter = new AESDecryptor(); - } - softwareDecrypter.expandKey(key); - var result = currentResult; - this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv); - this.currentIV = sliceUint8(currentChunk, -16).buffer; - if (!result) { - return null; - } - return result; - }; - _proto.webCryptoDecrypt = function webCryptoDecrypt(data, key, iv) { - var _this2 = this; - if (this.key !== key || !this.fastAesKey) { - if (!this.subtle) { - return Promise.resolve(this.onWebCryptoError(data, key, iv)); - } - this.key = key; - this.fastAesKey = new FastAESKey(this.subtle, key); - } - return this.fastAesKey.expandKey().then(function (aesKey) { - // decrypt using web crypto - if (!_this2.subtle) { - return Promise.reject(new Error('web crypto not initialized')); - } - _this2.logOnce('WebCrypto AES decrypt'); - var crypto = new AESCrypto(_this2.subtle, new Uint8Array(iv)); - return crypto.decrypt(data.buffer, aesKey); - }).catch(function (err) { - logger.warn("[decrypter]: WebCrypto Error, disable WebCrypto API, " + err.name + ": " + err.message); - return _this2.onWebCryptoError(data, key, iv); - }); - }; - _proto.onWebCryptoError = function onWebCryptoError(data, key, iv) { - this.useSoftware = true; - this.logEnabled = true; - this.softwareDecrypt(data, key, iv); - var decryptResult = this.flush(); - if (decryptResult) { - return decryptResult.buffer; - } - throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data'); - }; - _proto.getValidChunk = function getValidChunk(data) { - var currentChunk = data; - var splitPoint = data.length - data.length % CHUNK_SIZE; - if (splitPoint !== data.length) { - currentChunk = sliceUint8(data, 0, splitPoint); - this.remainderData = sliceUint8(data, splitPoint); - } - return currentChunk; - }; - _proto.logOnce = function logOnce(msg) { - if (!this.logEnabled) { - return; - } - logger.log("[decrypter]: " + msg); - this.logEnabled = false; - }; - return Decrypter; - }(); - - /** - * TimeRanges to string helper - */ - - var TimeRanges = { - toString: function toString(r) { - var log = ''; - var len = r.length; - for (var i = 0; i < len; i++) { - log += "[" + r.start(i).toFixed(3) + "-" + r.end(i).toFixed(3) + "]"; - } - return log; - } - }; - - var State = { - STOPPED: 'STOPPED', - IDLE: 'IDLE', - KEY_LOADING: 'KEY_LOADING', - FRAG_LOADING: 'FRAG_LOADING', - FRAG_LOADING_WAITING_RETRY: 'FRAG_LOADING_WAITING_RETRY', - WAITING_TRACK: 'WAITING_TRACK', - PARSING: 'PARSING', - PARSED: 'PARSED', - ENDED: 'ENDED', - ERROR: 'ERROR', - WAITING_INIT_PTS: 'WAITING_INIT_PTS', - WAITING_LEVEL: 'WAITING_LEVEL' - }; - var BaseStreamController = /*#__PURE__*/function (_TaskLoop) { - _inheritsLoose(BaseStreamController, _TaskLoop); - function BaseStreamController(hls, fragmentTracker, keyLoader, logPrefix, playlistType) { - var _this; - _this = _TaskLoop.call(this) || this; - _this.hls = void 0; - _this.fragPrevious = null; - _this.fragCurrent = null; - _this.fragmentTracker = void 0; - _this.transmuxer = null; - _this._state = State.STOPPED; - _this.playlistType = void 0; - _this.media = null; - _this.mediaBuffer = null; - _this.config = void 0; - _this.bitrateTest = false; - _this.lastCurrentTime = 0; - _this.nextLoadPosition = 0; - _this.startPosition = 0; - _this.startTimeOffset = null; - _this.loadedmetadata = false; - _this.retryDate = 0; - _this.levels = null; - _this.fragmentLoader = void 0; - _this.keyLoader = void 0; - _this.levelLastLoaded = null; - _this.startFragRequested = false; - _this.decrypter = void 0; - _this.initPTS = []; - _this.onvseeking = null; - _this.onvended = null; - _this.logPrefix = ''; - _this.log = void 0; - _this.warn = void 0; - _this.playlistType = playlistType; - _this.logPrefix = logPrefix; - _this.log = logger.log.bind(logger, logPrefix + ":"); - _this.warn = logger.warn.bind(logger, logPrefix + ":"); - _this.hls = hls; - _this.fragmentLoader = new FragmentLoader(hls.config); - _this.keyLoader = keyLoader; - _this.fragmentTracker = fragmentTracker; - _this.config = hls.config; - _this.decrypter = new Decrypter(hls.config); - hls.on(Events.MANIFEST_LOADED, _this.onManifestLoaded, _assertThisInitialized(_this)); - return _this; - } - var _proto = BaseStreamController.prototype; - _proto.doTick = function doTick() { - this.onTickEnd(); - }; - _proto.onTickEnd = function onTickEnd() {} - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ; - _proto.startLoad = function startLoad(startPosition) {}; - _proto.stopLoad = function stopLoad() { - this.fragmentLoader.abort(); - this.keyLoader.abort(this.playlistType); - var frag = this.fragCurrent; - if (frag != null && frag.loader) { - frag.abortRequests(); - this.fragmentTracker.removeFragment(frag); - } - this.resetTransmuxer(); - this.fragCurrent = null; - this.fragPrevious = null; - this.clearInterval(); - this.clearNextTick(); - this.state = State.STOPPED; - }; - _proto._streamEnded = function _streamEnded(bufferInfo, levelDetails) { - // If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached, - // of nothing loading/loaded return false - if (levelDetails.live || bufferInfo.nextStart || !bufferInfo.end || !this.media) { - return false; - } - var partList = levelDetails.partList; - // Since the last part isn't guaranteed to correspond to the last playlist segment for Low-Latency HLS, - // check instead if the last part is buffered. - if (partList != null && partList.length) { - var lastPart = partList[partList.length - 1]; - - // Checking the midpoint of the part for potential margin of error and related issues. - // NOTE: Technically I believe parts could yield content that is < the computed duration (including potential a duration of 0) - // and still be spec-compliant, so there may still be edge cases here. Likewise, there could be issues in end of stream - // part mismatches for independent audio and video playlists/segments. - var lastPartBuffered = BufferHelper.isBuffered(this.media, lastPart.start + lastPart.duration / 2); - return lastPartBuffered; - } - var playlistType = levelDetails.fragments[levelDetails.fragments.length - 1].type; - return this.fragmentTracker.isEndListAppended(playlistType); - }; - _proto.getLevelDetails = function getLevelDetails() { - if (this.levels && this.levelLastLoaded !== null) { - var _this$levelLastLoaded; - return (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details; - } - }; - _proto.onMediaAttached = function onMediaAttached(event, data) { - var media = this.media = this.mediaBuffer = data.media; - this.onvseeking = this.onMediaSeeking.bind(this); - this.onvended = this.onMediaEnded.bind(this); - media.addEventListener('seeking', this.onvseeking); - media.addEventListener('ended', this.onvended); - var config = this.config; - if (this.levels && config.autoStartLoad && this.state === State.STOPPED) { - this.startLoad(config.startPosition); - } - }; - _proto.onMediaDetaching = function onMediaDetaching() { - var media = this.media; - if (media != null && media.ended) { - this.log('MSE detaching and video ended, reset startPosition'); - this.startPosition = this.lastCurrentTime = 0; - } - - // remove video listeners - if (media && this.onvseeking && this.onvended) { - media.removeEventListener('seeking', this.onvseeking); - media.removeEventListener('ended', this.onvended); - this.onvseeking = this.onvended = null; - } - if (this.keyLoader) { - this.keyLoader.detach(); - } - this.media = this.mediaBuffer = null; - this.loadedmetadata = false; - this.fragmentTracker.removeAllFragments(); - this.stopLoad(); - }; - _proto.onMediaSeeking = function onMediaSeeking() { - var config = this.config, - fragCurrent = this.fragCurrent, - media = this.media, - mediaBuffer = this.mediaBuffer, - state = this.state; - var currentTime = media ? media.currentTime : 0; - var bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole); - this.log("media seeking to " + (isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime) + ", state: " + state); - if (this.state === State.ENDED) { - this.resetLoadingState(); - } else if (fragCurrent) { - // Seeking while frag load is in progress - var tolerance = config.maxFragLookUpTolerance; - var fragStartOffset = fragCurrent.start - tolerance; - var fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance; - // if seeking out of buffered range or into new one - if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) { - var pastFragment = currentTime > fragEndOffset; - // if the seek position is outside the current fragment range - if (currentTime < fragStartOffset || pastFragment) { - if (pastFragment && fragCurrent.loader) { - this.log('seeking outside of buffer while fragment load in progress, cancel fragment load'); - fragCurrent.abortRequests(); - this.resetLoadingState(); - } - this.fragPrevious = null; - } - } - } - if (media) { - // Remove gap fragments - this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true); - this.lastCurrentTime = currentTime; - } - - // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target - if (!this.loadedmetadata && !bufferInfo.len) { - this.nextLoadPosition = this.startPosition = currentTime; - } - - // Async tick to speed up processing - this.tickImmediate(); - }; - _proto.onMediaEnded = function onMediaEnded() { - // reset startPosition and lastCurrentTime to restart playback @ stream beginning - this.startPosition = this.lastCurrentTime = 0; - }; - _proto.onManifestLoaded = function onManifestLoaded(event, data) { - this.startTimeOffset = data.startTimeOffset; - this.initPTS = []; - }; - _proto.onHandlerDestroying = function onHandlerDestroying() { - this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - this.stopLoad(); - _TaskLoop.prototype.onHandlerDestroying.call(this); - // @ts-ignore - this.hls = null; - }; - _proto.onHandlerDestroyed = function onHandlerDestroyed() { - this.state = State.STOPPED; - if (this.fragmentLoader) { - this.fragmentLoader.destroy(); - } - if (this.keyLoader) { - this.keyLoader.destroy(); - } - if (this.decrypter) { - this.decrypter.destroy(); - } - this.hls = this.log = this.warn = this.decrypter = this.keyLoader = this.fragmentLoader = this.fragmentTracker = null; - _TaskLoop.prototype.onHandlerDestroyed.call(this); - }; - _proto.loadFragment = function loadFragment(frag, level, targetBufferTime) { - this._loadFragForPlayback(frag, level, targetBufferTime); - }; - _proto._loadFragForPlayback = function _loadFragForPlayback(frag, level, targetBufferTime) { - var _this2 = this; - var progressCallback = function progressCallback(data) { - if (_this2.fragContextChanged(frag)) { - _this2.warn("Fragment " + frag.sn + (data.part ? ' p: ' + data.part.index : '') + " of level " + frag.level + " was dropped during download."); - _this2.fragmentTracker.removeFragment(frag); - return; - } - frag.stats.chunkCount++; - _this2._handleFragmentLoadProgress(data); - }; - this._doFragLoad(frag, level, targetBufferTime, progressCallback).then(function (data) { - if (!data) { - // if we're here we probably needed to backtrack or are waiting for more parts - return; - } - var state = _this2.state; - if (_this2.fragContextChanged(frag)) { - if (state === State.FRAG_LOADING || !_this2.fragCurrent && state === State.PARSING) { - _this2.fragmentTracker.removeFragment(frag); - _this2.state = State.IDLE; - } - return; - } - if ('payload' in data) { - _this2.log("Loaded fragment " + frag.sn + " of level " + frag.level); - _this2.hls.trigger(Events.FRAG_LOADED, data); - } - - // Pass through the whole payload; controllers not implementing progressive loading receive data from this callback - _this2._handleFragmentLoadComplete(data); - }).catch(function (reason) { - if (_this2.state === State.STOPPED || _this2.state === State.ERROR) { - return; - } - _this2.warn("Frag error: " + ((reason == null ? void 0 : reason.message) || reason)); - _this2.resetFragmentLoading(frag); - }); - }; - _proto.clearTrackerIfNeeded = function clearTrackerIfNeeded(frag) { - var _this$mediaBuffer; - var fragmentTracker = this.fragmentTracker; - var fragState = fragmentTracker.getState(frag); - if (fragState === FragmentState.APPENDING) { - // Lower the max buffer length and try again - var playlistType = frag.type; - var bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType); - var minForwardBufferLength = Math.max(frag.duration, bufferedInfo ? bufferedInfo.len : this.config.maxBufferLength); - // If backtracking, always remove from the tracker without reducing max buffer length - var backtrackFragment = this.backtrackFragment; - var backtracked = backtrackFragment ? frag.sn - backtrackFragment.sn : 0; - if (backtracked === 1 || this.reduceMaxBufferLength(minForwardBufferLength, frag.duration)) { - fragmentTracker.removeFragment(frag); - } - } else if (((_this$mediaBuffer = this.mediaBuffer) == null ? void 0 : _this$mediaBuffer.buffered.length) === 0) { - // Stop gap for bad tracker / buffer flush behavior - fragmentTracker.removeAllFragments(); - } else if (fragmentTracker.hasParts(frag.type)) { - // In low latency mode, remove fragments for which only some parts were buffered - fragmentTracker.detectPartialFragments({ - frag: frag, - part: null, - stats: frag.stats, - id: frag.type - }); - if (fragmentTracker.getState(frag) === FragmentState.PARTIAL) { - fragmentTracker.removeFragment(frag); - } - } - }; - _proto.checkLiveUpdate = function checkLiveUpdate(details) { - if (details.updated && !details.live) { - // Live stream ended, update fragment tracker - var lastFragment = details.fragments[details.fragments.length - 1]; - this.fragmentTracker.detectPartialFragments({ - frag: lastFragment, - part: null, - stats: lastFragment.stats, - id: lastFragment.type - }); - } - if (!details.fragments[0]) { - details.deltaUpdateFailed = true; - } - }; - _proto.flushMainBuffer = function flushMainBuffer(startOffset, endOffset, type) { - if (type === void 0) { - type = null; - } - if (!(startOffset - endOffset)) { - return; - } - // When alternate audio is playing, the audio-stream-controller is responsible for the audio buffer. Otherwise, - // passing a null type flushes both buffers - var flushScope = { - startOffset: startOffset, - endOffset: endOffset, - type: type - }; - this.hls.trigger(Events.BUFFER_FLUSHING, flushScope); - }; - _proto._loadInitSegment = function _loadInitSegment(frag, level) { - var _this3 = this; - this._doFragLoad(frag, level).then(function (data) { - if (!data || _this3.fragContextChanged(frag) || !_this3.levels) { - throw new Error('init load aborted'); - } - return data; - }).then(function (data) { - var hls = _this3.hls; - var payload = data.payload; - var decryptData = frag.decryptdata; - - // check to see if the payload needs to be decrypted - if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') { - var startTime = self.performance.now(); - // decrypt init segment data - return _this3.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(function (err) { - hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_DECRYPT_ERROR, - fatal: false, - error: err, - reason: err.message, - frag: frag - }); - throw err; - }).then(function (decryptedData) { - var endTime = self.performance.now(); - hls.trigger(Events.FRAG_DECRYPTED, { - frag: frag, - payload: decryptedData, - stats: { - tstart: startTime, - tdecrypt: endTime - } - }); - data.payload = decryptedData; - return _this3.completeInitSegmentLoad(data); - }); - } - return _this3.completeInitSegmentLoad(data); - }).catch(function (reason) { - if (_this3.state === State.STOPPED || _this3.state === State.ERROR) { - return; - } - _this3.warn(reason); - _this3.resetFragmentLoading(frag); - }); - }; - _proto.completeInitSegmentLoad = function completeInitSegmentLoad(data) { - var levels = this.levels; - if (!levels) { - throw new Error('init load aborted, missing levels'); - } - var stats = data.frag.stats; - this.state = State.IDLE; - data.frag.data = new Uint8Array(data.payload); - stats.parsing.start = stats.buffering.start = self.performance.now(); - stats.parsing.end = stats.buffering.end = self.performance.now(); - this.tick(); - }; - _proto.fragContextChanged = function fragContextChanged(frag) { - var fragCurrent = this.fragCurrent; - return !frag || !fragCurrent || frag.sn !== fragCurrent.sn || frag.level !== fragCurrent.level; - }; - _proto.fragBufferedComplete = function fragBufferedComplete(frag, part) { - var _frag$startPTS, _frag$endPTS, _this$fragCurrent, _this$fragPrevious; - var media = this.mediaBuffer ? this.mediaBuffer : this.media; - this.log("Buffered " + frag.type + " sn: " + frag.sn + (part ? ' part: ' + part.index : '') + " of " + (this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track') + " " + frag.level + " (frag:[" + ((_frag$startPTS = frag.startPTS) != null ? _frag$startPTS : NaN).toFixed(3) + "-" + ((_frag$endPTS = frag.endPTS) != null ? _frag$endPTS : NaN).toFixed(3) + "] > buffer:" + (media ? TimeRanges.toString(BufferHelper.getBuffered(media)) : '(detached)') + ")"); - if (frag.sn !== 'initSegment') { - var _this$levels; - if (frag.type !== PlaylistLevelType.SUBTITLE) { - var el = frag.elementaryStreams; - if (!Object.keys(el).some(function (type) { - return !!el[type]; - })) { - // empty segment - this.state = State.IDLE; - return; - } - } - var level = (_this$levels = this.levels) == null ? void 0 : _this$levels[frag.level]; - if (level != null && level.fragmentError) { - this.log("Resetting level fragment error count of " + level.fragmentError + " on frag buffered"); - level.fragmentError = 0; - } - } - this.state = State.IDLE; - if (!media) { - return; - } - if (!this.loadedmetadata && frag.type == PlaylistLevelType.MAIN && media.buffered.length && ((_this$fragCurrent = this.fragCurrent) == null ? void 0 : _this$fragCurrent.sn) === ((_this$fragPrevious = this.fragPrevious) == null ? void 0 : _this$fragPrevious.sn)) { - this.loadedmetadata = true; - this.seekToStartPos(); - } - this.tick(); - }; - _proto.seekToStartPos = function seekToStartPos() {}; - _proto._handleFragmentLoadComplete = function _handleFragmentLoadComplete(fragLoadedEndData) { - var transmuxer = this.transmuxer; - if (!transmuxer) { - return; - } - var frag = fragLoadedEndData.frag, - part = fragLoadedEndData.part, - partsLoaded = fragLoadedEndData.partsLoaded; - // If we did not load parts, or loaded all parts, we have complete (not partial) fragment data - var complete = !partsLoaded || partsLoaded.length === 0 || partsLoaded.some(function (fragLoaded) { - return !fragLoaded; - }); - var chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount + 1, 0, part ? part.index : -1, !complete); - transmuxer.flush(chunkMeta); - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ; - _proto._handleFragmentLoadProgress = function _handleFragmentLoadProgress(frag) {}; - _proto._doFragLoad = function _doFragLoad(frag, level, targetBufferTime, progressCallback) { - var _frag$decryptdata, - _this4 = this; - if (targetBufferTime === void 0) { - targetBufferTime = null; - } - var details = level == null ? void 0 : level.details; - if (!this.levels || !details) { - throw new Error("frag load aborted, missing level" + (details ? '' : ' detail') + "s"); - } - var keyLoadingPromise = null; - if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) { - this.log("Loading key for " + frag.sn + " of [" + details.startSN + "-" + details.endSN + "], " + (this.logPrefix === '[stream-controller]' ? 'level' : 'track') + " " + frag.level); - this.state = State.KEY_LOADING; - this.fragCurrent = frag; - keyLoadingPromise = this.keyLoader.load(frag).then(function (keyLoadedData) { - if (!_this4.fragContextChanged(keyLoadedData.frag)) { - _this4.hls.trigger(Events.KEY_LOADED, keyLoadedData); - if (_this4.state === State.KEY_LOADING) { - _this4.state = State.IDLE; - } - return keyLoadedData; - } - }); - this.hls.trigger(Events.KEY_LOADING, { - frag: frag - }); - if (this.fragCurrent === null) { - keyLoadingPromise = Promise.reject(new Error("frag load aborted, context changed in KEY_LOADING")); - } - } else if (!frag.encrypted && details.encryptedFragments.length) { - this.keyLoader.loadClear(frag, details.encryptedFragments); - } - targetBufferTime = Math.max(frag.start, targetBufferTime || 0); - if (this.config.lowLatencyMode && frag.sn !== 'initSegment') { - var partList = details.partList; - if (partList && progressCallback) { - if (targetBufferTime > frag.end && details.fragmentHint) { - frag = details.fragmentHint; - } - var partIndex = this.getNextPart(partList, frag, targetBufferTime); - if (partIndex > -1) { - var part = partList[partIndex]; - this.log("Loading part sn: " + frag.sn + " p: " + part.index + " cc: " + frag.cc + " of playlist [" + details.startSN + "-" + details.endSN + "] parts [0-" + partIndex + "-" + (partList.length - 1) + "] " + (this.logPrefix === '[stream-controller]' ? 'level' : 'track') + ": " + frag.level + ", target: " + parseFloat(targetBufferTime.toFixed(3))); - this.nextLoadPosition = part.start + part.duration; - this.state = State.FRAG_LOADING; - var _result; - if (keyLoadingPromise) { - _result = keyLoadingPromise.then(function (keyLoadedData) { - if (!keyLoadedData || _this4.fragContextChanged(keyLoadedData.frag)) { - return null; - } - return _this4.doFragPartsLoad(frag, part, level, progressCallback); - }).catch(function (error) { - return _this4.handleFragLoadError(error); - }); - } else { - _result = this.doFragPartsLoad(frag, part, level, progressCallback).catch(function (error) { - return _this4.handleFragLoadError(error); - }); - } - this.hls.trigger(Events.FRAG_LOADING, { - frag: frag, - part: part, - targetBufferTime: targetBufferTime - }); - if (this.fragCurrent === null) { - return Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING parts")); - } - return _result; - } else if (!frag.url || this.loadedEndOfParts(partList, targetBufferTime)) { - // Fragment hint has no parts - return Promise.resolve(null); - } - } - } - this.log("Loading fragment " + frag.sn + " cc: " + frag.cc + " " + (details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : '') + (this.logPrefix === '[stream-controller]' ? 'level' : 'track') + ": " + frag.level + ", target: " + parseFloat(targetBufferTime.toFixed(3))); - // Don't update nextLoadPosition for fragments which are not buffered - if (isFiniteNumber(frag.sn) && !this.bitrateTest) { - this.nextLoadPosition = frag.start + frag.duration; - } - this.state = State.FRAG_LOADING; - - // Load key before streaming fragment data - var dataOnProgress = this.config.progressive; - var result; - if (dataOnProgress && keyLoadingPromise) { - result = keyLoadingPromise.then(function (keyLoadedData) { - if (!keyLoadedData || _this4.fragContextChanged(keyLoadedData == null ? void 0 : keyLoadedData.frag)) { - return null; - } - return _this4.fragmentLoader.load(frag, progressCallback); - }).catch(function (error) { - return _this4.handleFragLoadError(error); - }); - } else { - // load unencrypted fragment data with progress event, - // or handle fragment result after key and fragment are finished loading - result = Promise.all([this.fragmentLoader.load(frag, dataOnProgress ? progressCallback : undefined), keyLoadingPromise]).then(function (_ref) { - var fragLoadedData = _ref[0]; - if (!dataOnProgress && fragLoadedData && progressCallback) { - progressCallback(fragLoadedData); - } - return fragLoadedData; - }).catch(function (error) { - return _this4.handleFragLoadError(error); - }); - } - this.hls.trigger(Events.FRAG_LOADING, { - frag: frag, - targetBufferTime: targetBufferTime - }); - if (this.fragCurrent === null) { - return Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING")); - } - return result; - }; - _proto.doFragPartsLoad = function doFragPartsLoad(frag, fromPart, level, progressCallback) { - var _this5 = this; - return new Promise(function (resolve, reject) { - var _level$details; - var partsLoaded = []; - var initialPartList = (_level$details = level.details) == null ? void 0 : _level$details.partList; - var loadPart = function loadPart(part) { - _this5.fragmentLoader.loadPart(frag, part, progressCallback).then(function (partLoadedData) { - partsLoaded[part.index] = partLoadedData; - var loadedPart = partLoadedData.part; - _this5.hls.trigger(Events.FRAG_LOADED, partLoadedData); - var nextPart = getPartWith(level, frag.sn, part.index + 1) || findPart(initialPartList, frag.sn, part.index + 1); - if (nextPart) { - loadPart(nextPart); - } else { - return resolve({ - frag: frag, - part: loadedPart, - partsLoaded: partsLoaded - }); - } - }).catch(reject); - }; - loadPart(fromPart); - }); - }; - _proto.handleFragLoadError = function handleFragLoadError(error) { - if ('data' in error) { - var data = error.data; - if (error.data && data.details === ErrorDetails.INTERNAL_ABORTED) { - this.handleFragLoadAborted(data.frag, data.part); - } else { - this.hls.trigger(Events.ERROR, data); - } - } else { - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.OTHER_ERROR, - details: ErrorDetails.INTERNAL_EXCEPTION, - err: error, - error: error, - fatal: true - }); - } - return null; - }; - _proto._handleTransmuxerFlush = function _handleTransmuxerFlush(chunkMeta) { - var context = this.getCurrentContext(chunkMeta); - if (!context || this.state !== State.PARSING) { - if (!this.fragCurrent && this.state !== State.STOPPED && this.state !== State.ERROR) { - this.state = State.IDLE; - } - return; - } - var frag = context.frag, - part = context.part, - level = context.level; - var now = self.performance.now(); - frag.stats.parsing.end = now; - if (part) { - part.stats.parsing.end = now; - } - this.updateLevelTiming(frag, part, level, chunkMeta.partial); - }; - _proto.getCurrentContext = function getCurrentContext(chunkMeta) { - var levels = this.levels, - fragCurrent = this.fragCurrent; - var levelIndex = chunkMeta.level, - sn = chunkMeta.sn, - partIndex = chunkMeta.part; - if (!(levels != null && levels[levelIndex])) { - this.warn("Levels object was unset while buffering fragment " + sn + " of level " + levelIndex + ". The current chunk will not be buffered."); - return null; - } - var level = levels[levelIndex]; - var part = partIndex > -1 ? getPartWith(level, sn, partIndex) : null; - var frag = part ? part.fragment : getFragmentWithSN(level, sn, fragCurrent); - if (!frag) { - return null; - } - if (fragCurrent && fragCurrent !== frag) { - frag.stats = fragCurrent.stats; - } - return { - frag: frag, - part: part, - level: level - }; - }; - _proto.bufferFragmentData = function bufferFragmentData(data, frag, part, chunkMeta, noBacktracking) { - var _buffer; - if (!data || this.state !== State.PARSING) { - return; - } - var data1 = data.data1, - data2 = data.data2; - var buffer = data1; - if (data1 && data2) { - // Combine the moof + mdat so that we buffer with a single append - buffer = appendUint8Array(data1, data2); - } - if (!((_buffer = buffer) != null && _buffer.length)) { - return; - } - var segment = { - type: data.type, - frag: frag, - part: part, - chunkMeta: chunkMeta, - parent: frag.type, - data: buffer - }; - this.hls.trigger(Events.BUFFER_APPENDING, segment); - if (data.dropped && data.independent && !part) { - if (noBacktracking) { - return; - } - // Clear buffer so that we reload previous segments sequentially if required - this.flushBufferGap(frag); - } - }; - _proto.flushBufferGap = function flushBufferGap(frag) { - var media = this.media; - if (!media) { - return; - } - // If currentTime is not buffered, clear the back buffer so that we can backtrack as much as needed - if (!BufferHelper.isBuffered(media, media.currentTime)) { - this.flushMainBuffer(0, frag.start); - return; - } - // Remove back-buffer without interrupting playback to allow back tracking - var currentTime = media.currentTime; - var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); - var fragDuration = frag.duration; - var segmentFraction = Math.min(this.config.maxFragLookUpTolerance * 2, fragDuration * 0.25); - var start = Math.max(Math.min(frag.start - segmentFraction, bufferInfo.end - segmentFraction), currentTime + segmentFraction); - if (frag.start - start > segmentFraction) { - this.flushMainBuffer(start, frag.start); - } - }; - _proto.getFwdBufferInfo = function getFwdBufferInfo(bufferable, type) { - var pos = this.getLoadPosition(); - if (!isFiniteNumber(pos)) { - return null; - } - return this.getFwdBufferInfoAtPos(bufferable, pos, type); - }; - _proto.getFwdBufferInfoAtPos = function getFwdBufferInfoAtPos(bufferable, pos, type) { - var maxBufferHole = this.config.maxBufferHole; - var bufferInfo = BufferHelper.bufferInfo(bufferable, pos, maxBufferHole); - // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos - if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) { - var bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type); - if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) { - return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole)); - } - } - return bufferInfo; - }; - _proto.getMaxBufferLength = function getMaxBufferLength(levelBitrate) { - var config = this.config; - var maxBufLen; - if (levelBitrate) { - maxBufLen = Math.max(8 * config.maxBufferSize / levelBitrate, config.maxBufferLength); - } else { - maxBufLen = config.maxBufferLength; - } - return Math.min(maxBufLen, config.maxMaxBufferLength); - }; - _proto.reduceMaxBufferLength = function reduceMaxBufferLength(threshold, fragDuration) { - var config = this.config; - var minLength = Math.max(Math.min(threshold - fragDuration, config.maxBufferLength), fragDuration); - var reducedLength = Math.max(threshold - fragDuration * 3, config.maxMaxBufferLength / 2, minLength); - if (reducedLength >= minLength) { - // reduce max buffer length as it might be too high. we do this to avoid loop flushing ... - config.maxMaxBufferLength = reducedLength; - this.warn("Reduce max buffer length to " + reducedLength + "s"); - return true; - } - return false; - }; - _proto.getAppendedFrag = function getAppendedFrag(position, playlistType) { - var fragOrPart = this.fragmentTracker.getAppendedFrag(position, PlaylistLevelType.MAIN); - if (fragOrPart && 'fragment' in fragOrPart) { - return fragOrPart.fragment; - } - return fragOrPart; - }; - _proto.getNextFragment = function getNextFragment(pos, levelDetails) { - var fragments = levelDetails.fragments; - var fragLen = fragments.length; - if (!fragLen) { - return null; - } - - // find fragment index, contiguous with end of buffer position - var config = this.config; - var start = fragments[0].start; - var frag; - if (levelDetails.live) { - var initialLiveManifestSize = config.initialLiveManifestSize; - if (fragLen < initialLiveManifestSize) { - this.warn("Not enough fragments to start playback (have: " + fragLen + ", need: " + initialLiveManifestSize + ")"); - return null; - } - // The real fragment start times for a live stream are only known after the PTS range for that level is known. - // In order to discover the range, we load the best matching fragment for that level and demux it. - // Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that - // we get the fragment matching that start time - if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) { - frag = this.getInitialLiveFragment(levelDetails, fragments); - this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos; - } - } else if (pos <= start) { - // VoD playlist: if loadPosition before start of playlist, load first fragment - frag = fragments[0]; - } - - // If we haven't run into any special cases already, just load the fragment most closely matching the requested position - if (!frag) { - var end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd; - frag = this.getFragmentAtPosition(pos, end, levelDetails); - } - return this.mapToInitFragWhenRequired(frag); - }; - _proto.isLoopLoading = function isLoopLoading(frag, targetBufferTime) { - var trackerState = this.fragmentTracker.getState(frag); - return (trackerState === FragmentState.OK || trackerState === FragmentState.PARTIAL && !!frag.gap) && this.nextLoadPosition > targetBufferTime; - }; - _proto.getNextFragmentLoopLoading = function getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, playlistType, maxBufLen) { - var gapStart = frag.gap; - var nextFragment = this.getNextFragment(this.nextLoadPosition, levelDetails); - if (nextFragment === null) { - return nextFragment; - } - frag = nextFragment; - if (gapStart && frag && !frag.gap && bufferInfo.nextStart) { - // Media buffered after GAP tags should not make the next buffer timerange exceed forward buffer length - var nextbufferInfo = this.getFwdBufferInfoAtPos(this.mediaBuffer ? this.mediaBuffer : this.media, bufferInfo.nextStart, playlistType); - if (nextbufferInfo !== null && bufferInfo.len + nextbufferInfo.len >= maxBufLen) { - // Returning here might result in not finding an audio and video candiate to skip to - this.log("buffer full after gaps in \"" + playlistType + "\" playlist starting at sn: " + frag.sn); - return null; - } - } - return frag; - }; - _proto.mapToInitFragWhenRequired = function mapToInitFragWhenRequired(frag) { - // If an initSegment is present, it must be buffered first - if (frag != null && frag.initSegment && !(frag != null && frag.initSegment.data) && !this.bitrateTest) { - return frag.initSegment; - } - return frag; - }; - _proto.getNextPart = function getNextPart(partList, frag, targetBufferTime) { - var nextPart = -1; - var contiguous = false; - var independentAttrOmitted = true; - for (var i = 0, len = partList.length; i < len; i++) { - var part = partList[i]; - independentAttrOmitted = independentAttrOmitted && !part.independent; - if (nextPart > -1 && targetBufferTime < part.start) { - break; - } - var loaded = part.loaded; - if (loaded) { - nextPart = -1; - } else if ((contiguous || part.independent || independentAttrOmitted) && part.fragment === frag) { - nextPart = i; - } - contiguous = loaded; - } - return nextPart; - }; - _proto.loadedEndOfParts = function loadedEndOfParts(partList, targetBufferTime) { - var lastPart = partList[partList.length - 1]; - return lastPart && targetBufferTime > lastPart.start && lastPart.loaded; - } - - /* - This method is used find the best matching first fragment for a live playlist. This fragment is used to calculate the - "sliding" of the playlist, which is its offset from the start of playback. After sliding we can compute the real - start and end times for each fragment in the playlist (after which this method will not need to be called). - */; - _proto.getInitialLiveFragment = function getInitialLiveFragment(levelDetails, fragments) { - var fragPrevious = this.fragPrevious; - var frag = null; - if (fragPrevious) { - if (levelDetails.hasProgramDateTime) { - // Prefer using PDT, because it can be accurate enough to choose the correct fragment without knowing the level sliding - this.log("Live playlist, switching playlist, load frag with same PDT: " + fragPrevious.programDateTime); - frag = findFragmentByPDT(fragments, fragPrevious.endProgramDateTime, this.config.maxFragLookUpTolerance); - } - if (!frag) { - // SN does not need to be accurate between renditions, but depending on the packaging it may be so. - var targetSN = fragPrevious.sn + 1; - if (targetSN >= levelDetails.startSN && targetSN <= levelDetails.endSN) { - var fragNext = fragments[targetSN - levelDetails.startSN]; - // Ensure that we're staying within the continuity range, since PTS resets upon a new range - if (fragPrevious.cc === fragNext.cc) { - frag = fragNext; - this.log("Live playlist, switching playlist, load frag with next SN: " + frag.sn); - } - } - // It's important to stay within the continuity range if available; otherwise the fragments in the playlist - // will have the wrong start times - if (!frag) { - frag = findFragWithCC(fragments, fragPrevious.cc); - if (frag) { - this.log("Live playlist, switching playlist, load frag with same CC: " + frag.sn); - } - } - } - } else { - // Find a new start fragment when fragPrevious is null - var liveStart = this.hls.liveSyncPosition; - if (liveStart !== null) { - frag = this.getFragmentAtPosition(liveStart, this.bitrateTest ? levelDetails.fragmentEnd : levelDetails.edge, levelDetails); - } - } - return frag; - } - - /* - This method finds the best matching fragment given the provided position. - */; - _proto.getFragmentAtPosition = function getFragmentAtPosition(bufferEnd, end, levelDetails) { - var config = this.config; - var fragPrevious = this.fragPrevious; - var fragments = levelDetails.fragments, - endSN = levelDetails.endSN; - var fragmentHint = levelDetails.fragmentHint; - var maxFragLookUpTolerance = config.maxFragLookUpTolerance; - var partList = levelDetails.partList; - var loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint); - if (loadingParts && fragmentHint && !this.bitrateTest) { - // Include incomplete fragment with parts at end - fragments = fragments.concat(fragmentHint); - endSN = fragmentHint.sn; - } - var frag; - if (bufferEnd < end) { - var lookupTolerance = bufferEnd > end - maxFragLookUpTolerance ? 0 : maxFragLookUpTolerance; - // Remove the tolerance if it would put the bufferEnd past the actual end of stream - // Uses buffer and sequence number to calculate switch segment (required if using EXT-X-DISCONTINUITY-SEQUENCE) - frag = findFragmentByPTS(fragPrevious, fragments, bufferEnd, lookupTolerance); - } else { - // reach end of playlist - frag = fragments[fragments.length - 1]; - } - if (frag) { - var curSNIdx = frag.sn - levelDetails.startSN; - // Move fragPrevious forward to support forcing the next fragment to load - // when the buffer catches up to a previously buffered range. - var fragState = this.fragmentTracker.getState(frag); - if (fragState === FragmentState.OK || fragState === FragmentState.PARTIAL && frag.gap) { - fragPrevious = frag; - } - if (fragPrevious && frag.sn === fragPrevious.sn && (!loadingParts || partList[0].fragment.sn > frag.sn)) { - // Force the next fragment to load if the previous one was already selected. This can occasionally happen with - // non-uniform fragment durations - var sameLevel = fragPrevious && frag.level === fragPrevious.level; - if (sameLevel) { - var nextFrag = fragments[curSNIdx + 1]; - if (frag.sn < endSN && this.fragmentTracker.getState(nextFrag) !== FragmentState.OK) { - frag = nextFrag; - } else { - frag = null; - } - } - } - } - return frag; - }; - _proto.synchronizeToLiveEdge = function synchronizeToLiveEdge(levelDetails) { - var config = this.config, - media = this.media; - if (!media) { - return; - } - var liveSyncPosition = this.hls.liveSyncPosition; - var currentTime = media.currentTime; - var start = levelDetails.fragments[0].start; - var end = levelDetails.edge; - var withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end; - // Continue if we can seek forward to sync position or if current time is outside of sliding window - if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) { - // Continue if buffer is starving or if current time is behind max latency - var maxLatency = config.liveMaxLatencyDuration !== undefined ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration; - if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) { - if (!this.loadedmetadata) { - this.nextLoadPosition = liveSyncPosition; - } - // Only seek if ready and there is not a significant forward buffer available for playback - if (media.readyState) { - this.warn("Playback: " + currentTime.toFixed(3) + " is located too far from the end of live sliding playlist: " + end + ", reset currentTime to : " + liveSyncPosition.toFixed(3)); - media.currentTime = liveSyncPosition; - } - } - } - }; - _proto.alignPlaylists = function alignPlaylists(details, previousDetails, switchDetails) { - // FIXME: If not for `shouldAlignOnDiscontinuities` requiring fragPrevious.cc, - // this could all go in level-helper mergeDetails() - var length = details.fragments.length; - if (!length) { - this.warn("No fragments in live playlist"); - return 0; - } - var slidingStart = details.fragments[0].start; - var firstLevelLoad = !previousDetails; - var aligned = details.alignedSliding && isFiniteNumber(slidingStart); - if (firstLevelLoad || !aligned && !slidingStart) { - var fragPrevious = this.fragPrevious; - alignStream(fragPrevious, switchDetails, details); - var alignedSlidingStart = details.fragments[0].start; - this.log("Live playlist sliding: " + alignedSlidingStart.toFixed(2) + " start-sn: " + (previousDetails ? previousDetails.startSN : 'na') + "->" + details.startSN + " prev-sn: " + (fragPrevious ? fragPrevious.sn : 'na') + " fragments: " + length); - return alignedSlidingStart; - } - return slidingStart; - }; - _proto.waitForCdnTuneIn = function waitForCdnTuneIn(details) { - // Wait for Low-Latency CDN Tune-in to get an updated playlist - var advancePartLimit = 3; - return details.live && details.canBlockReload && details.partTarget && details.tuneInGoal > Math.max(details.partHoldBack, details.partTarget * advancePartLimit); - }; - _proto.setStartPosition = function setStartPosition(details, sliding) { - // compute start position if set to -1. use it straight away if value is defined - var startPosition = this.startPosition; - if (startPosition < sliding) { - startPosition = -1; - } - if (startPosition === -1 || this.lastCurrentTime === -1) { - // Use Playlist EXT-X-START:TIME-OFFSET when set - // Prioritize Multivariant Playlist offset so that main, audio, and subtitle stream-controller start times match - var offsetInMultivariantPlaylist = this.startTimeOffset !== null; - var startTimeOffset = offsetInMultivariantPlaylist ? this.startTimeOffset : details.startTimeOffset; - if (startTimeOffset !== null && isFiniteNumber(startTimeOffset)) { - startPosition = sliding + startTimeOffset; - if (startTimeOffset < 0) { - startPosition += details.totalduration; - } - startPosition = Math.min(Math.max(sliding, startPosition), sliding + details.totalduration); - this.log("Start time offset " + startTimeOffset + " found in " + (offsetInMultivariantPlaylist ? 'multivariant' : 'media') + " playlist, adjust startPosition to " + startPosition); - this.startPosition = startPosition; - } else if (details.live) { - // Leave this.startPosition at -1, so that we can use `getInitialLiveFragment` logic when startPosition has - // not been specified via the config or an as an argument to startLoad (#3736). - startPosition = this.hls.liveSyncPosition || sliding; - } else { - this.startPosition = startPosition = 0; - } - this.lastCurrentTime = startPosition; - } - this.nextLoadPosition = startPosition; - }; - _proto.getLoadPosition = function getLoadPosition() { - var media = this.media; - // if we have not yet loaded any fragment, start loading from start position - var pos = 0; - if (this.loadedmetadata && media) { - pos = media.currentTime; - } else if (this.nextLoadPosition) { - pos = this.nextLoadPosition; - } - return pos; - }; - _proto.handleFragLoadAborted = function handleFragLoadAborted(frag, part) { - if (this.transmuxer && frag.sn !== 'initSegment' && frag.stats.aborted) { - this.warn("Fragment " + frag.sn + (part ? ' part ' + part.index : '') + " of level " + frag.level + " was aborted"); - this.resetFragmentLoading(frag); - } - }; - _proto.resetFragmentLoading = function resetFragmentLoading(frag) { - if (!this.fragCurrent || !this.fragContextChanged(frag) && this.state !== State.FRAG_LOADING_WAITING_RETRY) { - this.state = State.IDLE; - } - }; - _proto.onFragmentOrKeyLoadError = function onFragmentOrKeyLoadError(filterType, data) { - if (data.chunkMeta && !data.frag) { - var context = this.getCurrentContext(data.chunkMeta); - if (context) { - data.frag = context.frag; - } - } - var frag = data.frag; - // Handle frag error related to caller's filterType - if (!frag || frag.type !== filterType || !this.levels) { - return; - } - if (this.fragContextChanged(frag)) { - var _this$fragCurrent2; - this.warn("Frag load error must match current frag to retry " + frag.url + " > " + ((_this$fragCurrent2 = this.fragCurrent) == null ? void 0 : _this$fragCurrent2.url)); - return; - } - var gapTagEncountered = data.details === ErrorDetails.FRAG_GAP; - if (gapTagEncountered) { - this.fragmentTracker.fragBuffered(frag, true); - } - // keep retrying until the limit will be reached - var errorAction = data.errorAction; - var _ref2 = errorAction || {}, - action = _ref2.action, - _ref2$retryCount = _ref2.retryCount, - retryCount = _ref2$retryCount === void 0 ? 0 : _ref2$retryCount, - retryConfig = _ref2.retryConfig; - if (errorAction && action === NetworkErrorAction.RetryRequest && retryConfig) { - this.resetStartWhenNotLoaded(this.levelLastLoaded); - var delay = getRetryDelay(retryConfig, retryCount); - this.warn("Fragment " + frag.sn + " of " + filterType + " " + frag.level + " errored with " + data.details + ", retrying loading " + (retryCount + 1) + "/" + retryConfig.maxNumRetry + " in " + delay + "ms"); - errorAction.resolved = true; - this.retryDate = self.performance.now() + delay; - this.state = State.FRAG_LOADING_WAITING_RETRY; - } else if (retryConfig && errorAction) { - this.resetFragmentErrors(filterType); - if (retryCount < retryConfig.maxNumRetry) { - // Network retry is skipped when level switch is preferred - if (!gapTagEncountered && action !== NetworkErrorAction.RemoveAlternatePermanently) { - errorAction.resolved = true; - } - } else { - logger.warn(data.details + " reached or exceeded max retry (" + retryCount + ")"); - return; - } - } else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) { - this.state = State.WAITING_LEVEL; - } else { - this.state = State.ERROR; - } - // Perform next async tick sooner to speed up error action resolution - this.tickImmediate(); - }; - _proto.reduceLengthAndFlushBuffer = function reduceLengthAndFlushBuffer(data) { - // if in appending state - if (this.state === State.PARSING || this.state === State.PARSED) { - var frag = data.frag; - var playlistType = data.parent; - var bufferedInfo = this.getFwdBufferInfo(this.mediaBuffer, playlistType); - // 0.5 : tolerance needed as some browsers stalls playback before reaching buffered end - // reduce max buf len if current position is buffered - var buffered = bufferedInfo && bufferedInfo.len > 0.5; - if (buffered) { - this.reduceMaxBufferLength(bufferedInfo.len, (frag == null ? void 0 : frag.duration) || 10); - } - var flushBuffer = !buffered; - if (flushBuffer) { - // current position is not buffered, but browser is still complaining about buffer full error - // this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708 - // in that case flush the whole audio buffer to recover - this.warn("Buffer full error while media.currentTime is not buffered, flush " + playlistType + " buffer"); - } - if (frag) { - this.fragmentTracker.removeFragment(frag); - this.nextLoadPosition = frag.start; - } - this.resetLoadingState(); - return flushBuffer; - } - return false; - }; - _proto.resetFragmentErrors = function resetFragmentErrors(filterType) { - if (filterType === PlaylistLevelType.AUDIO) { - // Reset current fragment since audio track audio is essential and may not have a fail-over track - this.fragCurrent = null; - } - // Fragment errors that result in a level switch or redundant fail-over - // should reset the stream controller state to idle - if (!this.loadedmetadata) { - this.startFragRequested = false; - } - if (this.state !== State.STOPPED) { - this.state = State.IDLE; - } - }; - _proto.afterBufferFlushed = function afterBufferFlushed(media, bufferType, playlistType) { - if (!media) { - return; - } - // After successful buffer flushing, filter flushed fragments from bufferedFrags use mediaBuffered instead of media - // (so that we will check against video.buffered ranges in case of alt audio track) - var bufferedTimeRanges = BufferHelper.getBuffered(media); - this.fragmentTracker.detectEvictedFragments(bufferType, bufferedTimeRanges, playlistType); - if (this.state === State.ENDED) { - this.resetLoadingState(); - } - }; - _proto.resetLoadingState = function resetLoadingState() { - this.log('Reset loading state'); - this.fragCurrent = null; - this.fragPrevious = null; - this.state = State.IDLE; - }; - _proto.resetStartWhenNotLoaded = function resetStartWhenNotLoaded(level) { - // if loadedmetadata is not set, it means that first frag request failed - // in that case, reset startFragRequested flag - if (!this.loadedmetadata) { - this.startFragRequested = false; - var details = level ? level.details : null; - if (details != null && details.live) { - // Update the start position and return to IDLE to recover live start - this.startPosition = -1; - this.setStartPosition(details, 0); - this.resetLoadingState(); - } else { - this.nextLoadPosition = this.startPosition; - } - } - }; - _proto.resetWhenMissingContext = function resetWhenMissingContext(chunkMeta) { - this.warn("The loading context changed while buffering fragment " + chunkMeta.sn + " of level " + chunkMeta.level + ". This chunk will not be buffered."); - this.removeUnbufferedFrags(); - this.resetStartWhenNotLoaded(this.levelLastLoaded); - this.resetLoadingState(); - }; - _proto.removeUnbufferedFrags = function removeUnbufferedFrags(start) { - if (start === void 0) { - start = 0; - } - this.fragmentTracker.removeFragmentsInRange(start, Infinity, this.playlistType, false, true); - }; - _proto.updateLevelTiming = function updateLevelTiming(frag, part, level, partial) { - var _this6 = this, - _this$transmuxer; - var details = level.details; - if (!details) { - this.warn('level.details undefined'); - return; - } - var parsed = Object.keys(frag.elementaryStreams).reduce(function (result, type) { - var info = frag.elementaryStreams[type]; - if (info) { - var parsedDuration = info.endPTS - info.startPTS; - if (parsedDuration <= 0) { - // Destroy the transmuxer after it's next time offset failed to advance because duration was <= 0. - // The new transmuxer will be configured with a time offset matching the next fragment start, - // preventing the timeline from shifting. - _this6.warn("Could not parse fragment " + frag.sn + " " + type + " duration reliably (" + parsedDuration + ")"); - return result || false; - } - var drift = partial ? 0 : updateFragPTSDTS(details, frag, info.startPTS, info.endPTS, info.startDTS, info.endDTS); - _this6.hls.trigger(Events.LEVEL_PTS_UPDATED, { - details: details, - level: level, - drift: drift, - type: type, - frag: frag, - start: info.startPTS, - end: info.endPTS - }); - return true; - } - return result; - }, false); - if (!parsed && ((_this$transmuxer = this.transmuxer) == null ? void 0 : _this$transmuxer.error) === null) { - var error = new Error("Found no media in fragment " + frag.sn + " of level " + frag.level + " resetting transmuxer to fallback to playlist timing"); - if (level.fragmentError === 0) { - // Mark and track the odd empty segment as a gap to avoid reloading - level.fragmentError++; - frag.gap = true; - this.fragmentTracker.removeFragment(frag); - this.fragmentTracker.fragBuffered(frag, true); - } - this.warn(error.message); - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_PARSING_ERROR, - fatal: false, - error: error, - frag: frag, - reason: "Found no media in msn " + frag.sn + " of level \"" + level.url + "\"" - }); - if (!this.hls) { - return; - } - this.resetTransmuxer(); - // For this error fallthrough. Marking parsed will allow advancing to next fragment. - } - this.state = State.PARSED; - this.hls.trigger(Events.FRAG_PARSED, { - frag: frag, - part: part - }); - }; - _proto.resetTransmuxer = function resetTransmuxer() { - if (this.transmuxer) { - this.transmuxer.destroy(); - this.transmuxer = null; - } - }; - _proto.recoverWorkerError = function recoverWorkerError(data) { - if (data.event === 'demuxerWorker') { - this.fragmentTracker.removeAllFragments(); - this.resetTransmuxer(); - this.resetStartWhenNotLoaded(this.levelLastLoaded); - this.resetLoadingState(); - } - }; - _createClass(BaseStreamController, [{ - key: "state", - get: function get() { - return this._state; - }, - set: function set(nextState) { - var previousState = this._state; - if (previousState !== nextState) { - this._state = nextState; - this.log(previousState + "->" + nextState); - } - } - }]); - return BaseStreamController; - }(TaskLoop); - - var ChunkCache = /*#__PURE__*/function () { - function ChunkCache() { - this.chunks = []; - this.dataLength = 0; - } - var _proto = ChunkCache.prototype; - _proto.push = function push(chunk) { - this.chunks.push(chunk); - this.dataLength += chunk.length; - }; - _proto.flush = function flush() { - var chunks = this.chunks, - dataLength = this.dataLength; - var result; - if (!chunks.length) { - return new Uint8Array(0); - } else if (chunks.length === 1) { - result = chunks[0]; - } else { - result = concatUint8Arrays(chunks, dataLength); - } - this.reset(); - return result; - }; - _proto.reset = function reset() { - this.chunks.length = 0; - this.dataLength = 0; - }; - return ChunkCache; - }(); - function concatUint8Arrays(chunks, dataLength) { - var result = new Uint8Array(dataLength); - var offset = 0; - for (var i = 0; i < chunks.length; i++) { - var chunk = chunks[i]; - result.set(chunk, offset); - offset += chunk.length; - } - return result; - } - - function dummyTrack(type, inputTimeScale) { - if (type === void 0) { - type = ''; - } - if (inputTimeScale === void 0) { - inputTimeScale = 90000; - } - return { - type: type, - id: -1, - pid: -1, - inputTimeScale: inputTimeScale, - sequenceNumber: -1, - samples: [], - dropped: 0 - }; - } - - var BaseAudioDemuxer = /*#__PURE__*/function () { - function BaseAudioDemuxer() { - this._audioTrack = void 0; - this._id3Track = void 0; - this.frameIndex = 0; - this.cachedData = null; - this.basePTS = null; - this.initPTS = null; - this.lastPTS = null; - } - var _proto = BaseAudioDemuxer.prototype; - _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { - this._id3Track = { - type: 'id3', - id: 3, - pid: -1, - inputTimeScale: 90000, - sequenceNumber: 0, - samples: [], - dropped: 0 - }; - }; - _proto.resetTimeStamp = function resetTimeStamp(deaultTimestamp) { - this.initPTS = deaultTimestamp; - this.resetContiguity(); - }; - _proto.resetContiguity = function resetContiguity() { - this.basePTS = null; - this.lastPTS = null; - this.frameIndex = 0; - }; - _proto.canParse = function canParse(data, offset) { - return false; - }; - _proto.appendFrame = function appendFrame(track, data, offset) {} - - // feed incoming data to the front of the parsing pipeline - ; - _proto.demux = function demux(data, timeOffset) { - if (this.cachedData) { - data = appendUint8Array(this.cachedData, data); - this.cachedData = null; - } - var id3Data = getID3Data(data, 0); - var offset = id3Data ? id3Data.length : 0; - var lastDataIndex; - var track = this._audioTrack; - var id3Track = this._id3Track; - var timestamp = id3Data ? getTimeStamp(id3Data) : undefined; - var length = data.length; - if (this.basePTS === null || this.frameIndex === 0 && isFiniteNumber(timestamp)) { - this.basePTS = initPTSFn(timestamp, timeOffset, this.initPTS); - this.lastPTS = this.basePTS; - } - if (this.lastPTS === null) { - this.lastPTS = this.basePTS; - } - - // more expressive than alternative: id3Data?.length - if (id3Data && id3Data.length > 0) { - id3Track.samples.push({ - pts: this.lastPTS, - dts: this.lastPTS, - data: id3Data, - type: MetadataSchema.audioId3, - duration: Number.POSITIVE_INFINITY - }); - } - while (offset < length) { - if (this.canParse(data, offset)) { - var frame = this.appendFrame(track, data, offset); - if (frame) { - this.frameIndex++; - this.lastPTS = frame.sample.pts; - offset += frame.length; - lastDataIndex = offset; - } else { - offset = length; - } - } else if (canParse$2(data, offset)) { - // after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data - id3Data = getID3Data(data, offset); - id3Track.samples.push({ - pts: this.lastPTS, - dts: this.lastPTS, - data: id3Data, - type: MetadataSchema.audioId3, - duration: Number.POSITIVE_INFINITY - }); - offset += id3Data.length; - lastDataIndex = offset; - } else { - offset++; - } - if (offset === length && lastDataIndex !== length) { - var partialData = sliceUint8(data, lastDataIndex); - if (this.cachedData) { - this.cachedData = appendUint8Array(this.cachedData, partialData); - } else { - this.cachedData = partialData; - } - } - } - return { - audioTrack: track, - videoTrack: dummyTrack(), - id3Track: id3Track, - textTrack: dummyTrack() - }; - }; - _proto.demuxSampleAes = function demuxSampleAes(data, keyData, timeOffset) { - return Promise.reject(new Error("[" + this + "] This demuxer does not support Sample-AES decryption")); - }; - _proto.flush = function flush(timeOffset) { - // Parse cache in case of remaining frames. - var cachedData = this.cachedData; - if (cachedData) { - this.cachedData = null; - this.demux(cachedData, 0); - } - return { - audioTrack: this._audioTrack, - videoTrack: dummyTrack(), - id3Track: this._id3Track, - textTrack: dummyTrack() - }; - }; - _proto.destroy = function destroy() {}; - return BaseAudioDemuxer; - }(); - /** - * Initialize PTS - * <p> - * use timestamp unless it is undefined, NaN or Infinity - * </p> - */ - var initPTSFn = function initPTSFn(timestamp, timeOffset, initPTS) { - if (isFiniteNumber(timestamp)) { - return timestamp * 90; - } - var init90kHz = initPTS ? initPTS.baseTime * 90000 / initPTS.timescale : 0; - return timeOffset * 90000 + init90kHz; - }; - - /** - * ADTS parser helper - * @link https://wiki.multimedia.cx/index.php?title=ADTS - */ - function getAudioConfig(observer, data, offset, audioCodec) { - var adtsObjectType; - var adtsExtensionSamplingIndex; - var adtsChannelConfig; - var config; - var userAgent = navigator.userAgent.toLowerCase(); - var manifestCodec = audioCodec; - var adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; - // byte 2 - adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1; - var adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2; - if (adtsSamplingIndex > adtsSamplingRates.length - 1) { - var error = new Error("invalid ADTS sampling index:" + adtsSamplingIndex); - observer.emit(Events.ERROR, Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_PARSING_ERROR, - fatal: true, - error: error, - reason: error.message - }); - return; - } - adtsChannelConfig = (data[offset + 2] & 0x01) << 2; - // byte 3 - adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6; - logger.log("manifest codec:" + audioCodec + ", ADTS type:" + adtsObjectType + ", samplingIndex:" + adtsSamplingIndex); - // firefox: freq less than 24kHz = AAC SBR (HE-AAC) - if (/firefox/i.test(userAgent)) { - if (adtsSamplingIndex >= 6) { - adtsObjectType = 5; - config = new Array(4); - // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies - // there is a factor 2 between frame sample rate and output sample rate - // multiply frequency by 2 (see table below, equivalent to substract 3) - adtsExtensionSamplingIndex = adtsSamplingIndex - 3; - } else { - adtsObjectType = 2; - config = new Array(2); - adtsExtensionSamplingIndex = adtsSamplingIndex; - } - // Android : always use AAC - } else if (userAgent.indexOf('android') !== -1) { - adtsObjectType = 2; - config = new Array(2); - adtsExtensionSamplingIndex = adtsSamplingIndex; - } else { - /* for other browsers (Chrome/Vivaldi/Opera ...) - always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...) - */ - adtsObjectType = 5; - config = new Array(4); - // if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz) - if (audioCodec && (audioCodec.indexOf('mp4a.40.29') !== -1 || audioCodec.indexOf('mp4a.40.5') !== -1) || !audioCodec && adtsSamplingIndex >= 6) { - // HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies - // there is a factor 2 between frame sample rate and output sample rate - // multiply frequency by 2 (see table below, equivalent to substract 3) - adtsExtensionSamplingIndex = adtsSamplingIndex - 3; - } else { - // if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio) - // Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo. - if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSamplingIndex >= 6 && adtsChannelConfig === 1 || /vivaldi/i.test(userAgent)) || !audioCodec && adtsChannelConfig === 1) { - adtsObjectType = 2; - config = new Array(2); - } - adtsExtensionSamplingIndex = adtsSamplingIndex; - } - } - /* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config - ISO 14496-3 (AAC).pdf - Table 1.13 — Syntax of AudioSpecificConfig() - Audio Profile / Audio Object Type - 0: Null - 1: AAC Main - 2: AAC LC (Low Complexity) - 3: AAC SSR (Scalable Sample Rate) - 4: AAC LTP (Long Term Prediction) - 5: SBR (Spectral Band Replication) - 6: AAC Scalable - sampling freq - 0: 96000 Hz - 1: 88200 Hz - 2: 64000 Hz - 3: 48000 Hz - 4: 44100 Hz - 5: 32000 Hz - 6: 24000 Hz - 7: 22050 Hz - 8: 16000 Hz - 9: 12000 Hz - 10: 11025 Hz - 11: 8000 Hz - 12: 7350 Hz - 13: Reserved - 14: Reserved - 15: frequency is written explictly - Channel Configurations - These are the channel configurations: - 0: Defined in AOT Specifc Config - 1: 1 channel: front-center - 2: 2 channels: front-left, front-right - */ - // audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1 - config[0] = adtsObjectType << 3; - // samplingFrequencyIndex - config[0] |= (adtsSamplingIndex & 0x0e) >> 1; - config[1] |= (adtsSamplingIndex & 0x01) << 7; - // channelConfiguration - config[1] |= adtsChannelConfig << 3; - if (adtsObjectType === 5) { - // adtsExtensionSamplingIndex - config[1] |= (adtsExtensionSamplingIndex & 0x0e) >> 1; - config[2] = (adtsExtensionSamplingIndex & 0x01) << 7; - // adtsObjectType (force to 2, chrome is checking that object type is less than 5 ??? - // https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc - config[2] |= 2 << 2; - config[3] = 0; - } - return { - config: config, - samplerate: adtsSamplingRates[adtsSamplingIndex], - channelCount: adtsChannelConfig, - codec: 'mp4a.40.' + adtsObjectType, - manifestCodec: manifestCodec - }; - } - function isHeaderPattern$1(data, offset) { - return data[offset] === 0xff && (data[offset + 1] & 0xf6) === 0xf0; - } - function getHeaderLength(data, offset) { - return data[offset + 1] & 0x01 ? 7 : 9; - } - function getFullFrameLength(data, offset) { - return (data[offset + 3] & 0x03) << 11 | data[offset + 4] << 3 | (data[offset + 5] & 0xe0) >>> 5; - } - function canGetFrameLength(data, offset) { - return offset + 5 < data.length; - } - function isHeader$1(data, offset) { - // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1 - // Layer bits (position 14 and 15) in header should be always 0 for ADTS - // More info https://wiki.multimedia.cx/index.php?title=ADTS - return offset + 1 < data.length && isHeaderPattern$1(data, offset); - } - function canParse$1(data, offset) { - return canGetFrameLength(data, offset) && isHeaderPattern$1(data, offset) && getFullFrameLength(data, offset) <= data.length - offset; - } - function probe$1(data, offset) { - // same as isHeader but we also check that ADTS frame follows last ADTS frame - // or end of data is reached - if (isHeader$1(data, offset)) { - // ADTS header Length - var headerLength = getHeaderLength(data, offset); - if (offset + headerLength >= data.length) { - return false; - } - // ADTS frame Length - var frameLength = getFullFrameLength(data, offset); - if (frameLength <= headerLength) { - return false; - } - var newOffset = offset + frameLength; - return newOffset === data.length || isHeader$1(data, newOffset); - } - return false; - } - function initTrackConfig(track, observer, data, offset, audioCodec) { - if (!track.samplerate) { - var config = getAudioConfig(observer, data, offset, audioCodec); - if (!config) { - return; - } - track.config = config.config; - track.samplerate = config.samplerate; - track.channelCount = config.channelCount; - track.codec = config.codec; - track.manifestCodec = config.manifestCodec; - logger.log("parsed codec:" + track.codec + ", rate:" + config.samplerate + ", channels:" + config.channelCount); - } - } - function getFrameDuration(samplerate) { - return 1024 * 90000 / samplerate; - } - function parseFrameHeader(data, offset) { - // The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header - var headerLength = getHeaderLength(data, offset); - if (offset + headerLength <= data.length) { - // retrieve frame size - var frameLength = getFullFrameLength(data, offset) - headerLength; - if (frameLength > 0) { - // logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`); - return { - headerLength: headerLength, - frameLength: frameLength - }; - } - } - } - function appendFrame$1(track, data, offset, pts, frameIndex) { - var frameDuration = getFrameDuration(track.samplerate); - var stamp = pts + frameIndex * frameDuration; - var header = parseFrameHeader(data, offset); - var unit; - if (header) { - var frameLength = header.frameLength, - headerLength = header.headerLength; - var _length = headerLength + frameLength; - var missing = Math.max(0, offset + _length - data.length); - // logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`); - if (missing) { - unit = new Uint8Array(_length - headerLength); - unit.set(data.subarray(offset + headerLength, data.length), 0); - } else { - unit = data.subarray(offset + headerLength, offset + _length); - } - var _sample = { - unit: unit, - pts: stamp - }; - if (!missing) { - track.samples.push(_sample); - } - return { - sample: _sample, - length: _length, - missing: missing - }; - } - // overflow incomplete header - var length = data.length - offset; - unit = new Uint8Array(length); - unit.set(data.subarray(offset, data.length), 0); - var sample = { - unit: unit, - pts: stamp - }; - return { - sample: sample, - length: length, - missing: -1 - }; - } - - /** - * MPEG parser helper - */ - - var chromeVersion$1 = null; - var BitratesMap = [32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]; - var SamplingRateMap = [44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000, 8000]; - var SamplesCoefficients = [ - // MPEG 2.5 - [0, - // Reserved - 72, - // Layer3 - 144, - // Layer2 - 12 // Layer1 - ], - // Reserved - [0, - // Reserved - 0, - // Layer3 - 0, - // Layer2 - 0 // Layer1 - ], - // MPEG 2 - [0, - // Reserved - 72, - // Layer3 - 144, - // Layer2 - 12 // Layer1 - ], - // MPEG 1 - [0, - // Reserved - 144, - // Layer3 - 144, - // Layer2 - 12 // Layer1 - ]]; - var BytesInSlot = [0, - // Reserved - 1, - // Layer3 - 1, - // Layer2 - 4 // Layer1 - ]; - function appendFrame(track, data, offset, pts, frameIndex) { - // Using http://www.datavoyage.com/mpgscript/mpeghdr.htm as a reference - if (offset + 24 > data.length) { - return; - } - var header = parseHeader(data, offset); - if (header && offset + header.frameLength <= data.length) { - var frameDuration = header.samplesPerFrame * 90000 / header.sampleRate; - var stamp = pts + frameIndex * frameDuration; - var sample = { - unit: data.subarray(offset, offset + header.frameLength), - pts: stamp, - dts: stamp - }; - track.config = []; - track.channelCount = header.channelCount; - track.samplerate = header.sampleRate; - track.samples.push(sample); - return { - sample: sample, - length: header.frameLength, - missing: 0 - }; - } - } - function parseHeader(data, offset) { - var mpegVersion = data[offset + 1] >> 3 & 3; - var mpegLayer = data[offset + 1] >> 1 & 3; - var bitRateIndex = data[offset + 2] >> 4 & 15; - var sampleRateIndex = data[offset + 2] >> 2 & 3; - if (mpegVersion !== 1 && bitRateIndex !== 0 && bitRateIndex !== 15 && sampleRateIndex !== 3) { - var paddingBit = data[offset + 2] >> 1 & 1; - var channelMode = data[offset + 3] >> 6; - var columnInBitrates = mpegVersion === 3 ? 3 - mpegLayer : mpegLayer === 3 ? 3 : 4; - var bitRate = BitratesMap[columnInBitrates * 14 + bitRateIndex - 1] * 1000; - var columnInSampleRates = mpegVersion === 3 ? 0 : mpegVersion === 2 ? 1 : 2; - var sampleRate = SamplingRateMap[columnInSampleRates * 3 + sampleRateIndex]; - var channelCount = channelMode === 3 ? 1 : 2; // If bits of channel mode are `11` then it is a single channel (Mono) - var sampleCoefficient = SamplesCoefficients[mpegVersion][mpegLayer]; - var bytesInSlot = BytesInSlot[mpegLayer]; - var samplesPerFrame = sampleCoefficient * 8 * bytesInSlot; - var frameLength = Math.floor(sampleCoefficient * bitRate / sampleRate + paddingBit) * bytesInSlot; - if (chromeVersion$1 === null) { - var userAgent = navigator.userAgent || ''; - var result = userAgent.match(/Chrome\/(\d+)/i); - chromeVersion$1 = result ? parseInt(result[1]) : 0; - } - var needChromeFix = !!chromeVersion$1 && chromeVersion$1 <= 87; - if (needChromeFix && mpegLayer === 2 && bitRate >= 224000 && channelMode === 0) { - // Work around bug in Chromium by setting channelMode to dual-channel (01) instead of stereo (00) - data[offset + 3] = data[offset + 3] | 0x80; - } - return { - sampleRate: sampleRate, - channelCount: channelCount, - frameLength: frameLength, - samplesPerFrame: samplesPerFrame - }; - } - } - function isHeaderPattern(data, offset) { - return data[offset] === 0xff && (data[offset + 1] & 0xe0) === 0xe0 && (data[offset + 1] & 0x06) !== 0x00; - } - function isHeader(data, offset) { - // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1 - // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III) - // More info http://www.mp3-tech.org/programmer/frame_header.html - return offset + 1 < data.length && isHeaderPattern(data, offset); - } - function canParse(data, offset) { - var headerSize = 4; - return isHeaderPattern(data, offset) && headerSize <= data.length - offset; - } - function probe(data, offset) { - // same as isHeader but we also check that MPEG frame follows last MPEG frame - // or end of data is reached - if (offset + 1 < data.length && isHeaderPattern(data, offset)) { - // MPEG header Length - var headerLength = 4; - // MPEG frame Length - var header = parseHeader(data, offset); - var frameLength = headerLength; - if (header != null && header.frameLength) { - frameLength = header.frameLength; - } - var newOffset = offset + frameLength; - return newOffset === data.length || isHeader(data, newOffset); - } - return false; - } - - var AACDemuxer = /*#__PURE__*/function (_BaseAudioDemuxer) { - _inheritsLoose(AACDemuxer, _BaseAudioDemuxer); - function AACDemuxer(observer, config) { - var _this; - _this = _BaseAudioDemuxer.call(this) || this; - _this.observer = void 0; - _this.config = void 0; - _this.observer = observer; - _this.config = config; - return _this; - } - var _proto = AACDemuxer.prototype; - _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { - _BaseAudioDemuxer.prototype.resetInitSegment.call(this, initSegment, audioCodec, videoCodec, trackDuration); - this._audioTrack = { - container: 'audio/adts', - type: 'audio', - id: 2, - pid: -1, - sequenceNumber: 0, - segmentCodec: 'aac', - samples: [], - manifestCodec: audioCodec, - duration: trackDuration, - inputTimeScale: 90000, - dropped: 0 - }; - } - - // Source for probe info - https://wiki.multimedia.cx/index.php?title=ADTS - ; - AACDemuxer.probe = function probe$2(data) { - if (!data) { - return false; - } - - // Check for the ADTS sync word - // Look for ADTS header | 1111 1111 | 1111 X00X | where X can be either 0 or 1 - // Layer bits (position 14 and 15) in header should be always 0 for ADTS - // More info https://wiki.multimedia.cx/index.php?title=ADTS - var id3Data = getID3Data(data, 0); - var offset = (id3Data == null ? void 0 : id3Data.length) || 0; - if (probe(data, offset)) { - return false; - } - for (var length = data.length; offset < length; offset++) { - if (probe$1(data, offset)) { - logger.log('ADTS sync word found !'); - return true; - } - } - return false; - }; - _proto.canParse = function canParse(data, offset) { - return canParse$1(data, offset); - }; - _proto.appendFrame = function appendFrame(track, data, offset) { - initTrackConfig(track, this.observer, data, offset, track.manifestCodec); - var frame = appendFrame$1(track, data, offset, this.basePTS, this.frameIndex); - if (frame && frame.missing === 0) { - return frame; - } - }; - return AACDemuxer; - }(BaseAudioDemuxer); - - var emsgSchemePattern = /\/emsg[-/]ID3/i; - var MP4Demuxer = /*#__PURE__*/function () { - function MP4Demuxer(observer, config) { - this.remainderData = null; - this.timeOffset = 0; - this.config = void 0; - this.videoTrack = void 0; - this.audioTrack = void 0; - this.id3Track = void 0; - this.txtTrack = void 0; - this.config = config; - } - var _proto = MP4Demuxer.prototype; - _proto.resetTimeStamp = function resetTimeStamp() {}; - _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { - var videoTrack = this.videoTrack = dummyTrack('video', 1); - var audioTrack = this.audioTrack = dummyTrack('audio', 1); - var captionTrack = this.txtTrack = dummyTrack('text', 1); - this.id3Track = dummyTrack('id3', 1); - this.timeOffset = 0; - if (!(initSegment != null && initSegment.byteLength)) { - return; - } - var initData = parseInitSegment(initSegment); - if (initData.video) { - var _initData$video = initData.video, - id = _initData$video.id, - timescale = _initData$video.timescale, - codec = _initData$video.codec; - videoTrack.id = id; - videoTrack.timescale = captionTrack.timescale = timescale; - videoTrack.codec = codec; - } - if (initData.audio) { - var _initData$audio = initData.audio, - _id = _initData$audio.id, - _timescale = _initData$audio.timescale, - _codec = _initData$audio.codec; - audioTrack.id = _id; - audioTrack.timescale = _timescale; - audioTrack.codec = _codec; - } - captionTrack.id = RemuxerTrackIdConfig.text; - videoTrack.sampleDuration = 0; - videoTrack.duration = audioTrack.duration = trackDuration; - }; - _proto.resetContiguity = function resetContiguity() { - this.remainderData = null; - }; - MP4Demuxer.probe = function probe(data) { - return hasMoofData(data); - }; - _proto.demux = function demux(data, timeOffset) { - this.timeOffset = timeOffset; - // Load all data into the avc track. The CMAF remuxer will look for the data in the samples object; the rest of the fields do not matter - var videoSamples = data; - var videoTrack = this.videoTrack; - var textTrack = this.txtTrack; - if (this.config.progressive) { - // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else. - // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee - // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception. - if (this.remainderData) { - videoSamples = appendUint8Array(this.remainderData, data); - } - var segmentedData = segmentValidRange(videoSamples); - this.remainderData = segmentedData.remainder; - videoTrack.samples = segmentedData.valid || new Uint8Array(); - } else { - videoTrack.samples = videoSamples; - } - var id3Track = this.extractID3Track(videoTrack, timeOffset); - textTrack.samples = parseSamples(timeOffset, videoTrack); - return { - videoTrack: videoTrack, - audioTrack: this.audioTrack, - id3Track: id3Track, - textTrack: this.txtTrack - }; - }; - _proto.flush = function flush() { - var timeOffset = this.timeOffset; - var videoTrack = this.videoTrack; - var textTrack = this.txtTrack; - videoTrack.samples = this.remainderData || new Uint8Array(); - this.remainderData = null; - var id3Track = this.extractID3Track(videoTrack, this.timeOffset); - textTrack.samples = parseSamples(timeOffset, videoTrack); - return { - videoTrack: videoTrack, - audioTrack: dummyTrack(), - id3Track: id3Track, - textTrack: dummyTrack() - }; - }; - _proto.extractID3Track = function extractID3Track(videoTrack, timeOffset) { - var id3Track = this.id3Track; - if (videoTrack.samples.length) { - var emsgs = findBox(videoTrack.samples, ['emsg']); - if (emsgs) { - emsgs.forEach(function (data) { - var emsgInfo = parseEmsg(data); - if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) { - var pts = isFiniteNumber(emsgInfo.presentationTime) ? emsgInfo.presentationTime / emsgInfo.timeScale : timeOffset + emsgInfo.presentationTimeDelta / emsgInfo.timeScale; - var duration = emsgInfo.eventDuration === 0xffffffff ? Number.POSITIVE_INFINITY : emsgInfo.eventDuration / emsgInfo.timeScale; - // Safari takes anything <= 0.001 seconds and maps it to Infinity - if (duration <= 0.001) { - duration = Number.POSITIVE_INFINITY; - } - var payload = emsgInfo.payload; - id3Track.samples.push({ - data: payload, - len: payload.byteLength, - dts: pts, - pts: pts, - type: MetadataSchema.emsg, - duration: duration - }); - } - }); - } - } - return id3Track; - }; - _proto.demuxSampleAes = function demuxSampleAes(data, keyData, timeOffset) { - return Promise.reject(new Error('The MP4 demuxer does not support SAMPLE-AES decryption')); - }; - _proto.destroy = function destroy() {}; - return MP4Demuxer; - }(); - - var getAudioBSID = function getAudioBSID(data, offset) { - // check the bsid to confirm ac-3 | ec-3 - var bsid = 0; - var numBits = 5; - offset += numBits; - var temp = new Uint32Array(1); // unsigned 32 bit for temporary storage - var mask = new Uint32Array(1); // unsigned 32 bit mask value - var _byte = new Uint8Array(1); // unsigned 8 bit for temporary storage - while (numBits > 0) { - _byte[0] = data[offset]; - // read remaining bits, upto 8 bits at a time - var bits = Math.min(numBits, 8); - var shift = 8 - bits; - mask[0] = 0xff000000 >>> 24 + shift << shift; - temp[0] = (_byte[0] & mask[0]) >> shift; - bsid = !bsid ? temp[0] : bsid << bits | temp[0]; - offset += 1; - numBits -= bits; - } - return bsid; - }; - - var AC3Demuxer = /*#__PURE__*/function (_BaseAudioDemuxer) { - _inheritsLoose(AC3Demuxer, _BaseAudioDemuxer); - function AC3Demuxer(observer) { - var _this; - _this = _BaseAudioDemuxer.call(this) || this; - _this.observer = void 0; - _this.observer = observer; - return _this; - } - var _proto = AC3Demuxer.prototype; - _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { - _BaseAudioDemuxer.prototype.resetInitSegment.call(this, initSegment, audioCodec, videoCodec, trackDuration); - this._audioTrack = { - container: 'audio/ac-3', - type: 'audio', - id: 2, - pid: -1, - sequenceNumber: 0, - segmentCodec: 'ac3', - samples: [], - manifestCodec: audioCodec, - duration: trackDuration, - inputTimeScale: 90000, - dropped: 0 - }; - }; - _proto.canParse = function canParse(data, offset) { - return offset + 64 < data.length; - }; - _proto.appendFrame = function appendFrame(track, data, offset) { - var frameLength = _appendFrame(track, data, offset, this.basePTS, this.frameIndex); - if (frameLength !== -1) { - var sample = track.samples[track.samples.length - 1]; - return { - sample: sample, - length: frameLength, - missing: 0 - }; - } - }; - AC3Demuxer.probe = function probe(data) { - if (!data) { - return false; - } - var id3Data = getID3Data(data, 0); - if (!id3Data) { - return false; - } - - // look for the ac-3 sync bytes - var offset = id3Data.length; - if (data[offset] === 0x0b && data[offset + 1] === 0x77 && getTimeStamp(id3Data) !== undefined && - // check the bsid to confirm ac-3 - getAudioBSID(data, offset) < 16) { - return true; - } - return false; - }; - return AC3Demuxer; - }(BaseAudioDemuxer); - function _appendFrame(track, data, start, pts, frameIndex) { - if (start + 8 > data.length) { - return -1; // not enough bytes left - } - if (data[start] !== 0x0b || data[start + 1] !== 0x77) { - return -1; // invalid magic - } - - // get sample rate - var samplingRateCode = data[start + 4] >> 6; - if (samplingRateCode >= 3) { - return -1; // invalid sampling rate - } - var samplingRateMap = [48000, 44100, 32000]; - var sampleRate = samplingRateMap[samplingRateCode]; - - // get frame size - var frameSizeCode = data[start + 4] & 0x3f; - var frameSizeMap = [64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105, 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174, 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244, 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417, 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558, 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, 975, 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, 1728, 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920]; - var frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2; - if (start + frameLength > data.length) { - return -1; - } - - // get channel count - var channelMode = data[start + 6] >> 5; - var skipCount = 0; - if (channelMode === 2) { - skipCount += 2; - } else { - if (channelMode & 1 && channelMode !== 1) { - skipCount += 2; - } - if (channelMode & 4) { - skipCount += 2; - } - } - var lfeon = (data[start + 6] << 8 | data[start + 7]) >> 12 - skipCount & 1; - var channelsMap = [2, 1, 2, 3, 3, 4, 4, 5]; - var channelCount = channelsMap[channelMode] + lfeon; - - // build dac3 box - var bsid = data[start + 5] >> 3; - var bsmod = data[start + 5] & 7; - var config = new Uint8Array([samplingRateCode << 6 | bsid << 1 | bsmod >> 2, (bsmod & 3) << 6 | channelMode << 3 | lfeon << 2 | frameSizeCode >> 4, frameSizeCode << 4 & 0xe0]); - var frameDuration = 1536 / sampleRate * 90000; - var stamp = pts + frameIndex * frameDuration; - var unit = data.subarray(start, start + frameLength); - track.config = config; - track.channelCount = channelCount; - track.samplerate = sampleRate; - track.samples.push({ - unit: unit, - pts: stamp - }); - return frameLength; - } - - var BaseVideoParser = /*#__PURE__*/function () { - function BaseVideoParser() { - this.VideoSample = null; - } - var _proto = BaseVideoParser.prototype; - _proto.createVideoSample = function createVideoSample(key, pts, dts, debug) { - return { - key: key, - frame: false, - pts: pts, - dts: dts, - units: [], - debug: debug, - length: 0 - }; - }; - _proto.getLastNalUnit = function getLastNalUnit(samples) { - var _VideoSample; - var VideoSample = this.VideoSample; - var lastUnit; - // try to fallback to previous sample if current one is empty - if (!VideoSample || VideoSample.units.length === 0) { - VideoSample = samples[samples.length - 1]; - } - if ((_VideoSample = VideoSample) != null && _VideoSample.units) { - var units = VideoSample.units; - lastUnit = units[units.length - 1]; - } - return lastUnit; - }; - _proto.pushAccessUnit = function pushAccessUnit(VideoSample, videoTrack) { - if (VideoSample.units.length && VideoSample.frame) { - // if sample does not have PTS/DTS, patch with last sample PTS/DTS - if (VideoSample.pts === undefined) { - var samples = videoTrack.samples; - var nbSamples = samples.length; - if (nbSamples) { - var lastSample = samples[nbSamples - 1]; - VideoSample.pts = lastSample.pts; - VideoSample.dts = lastSample.dts; - } else { - // dropping samples, no timestamp found - videoTrack.dropped++; - return; - } - } - videoTrack.samples.push(VideoSample); - } - if (VideoSample.debug.length) { - logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug); - } - }; - return BaseVideoParser; - }(); - - /** - * Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264. - */ - - var ExpGolomb = /*#__PURE__*/function () { - function ExpGolomb(data) { - this.data = void 0; - this.bytesAvailable = void 0; - this.word = void 0; - this.bitsAvailable = void 0; - this.data = data; - // the number of bytes left to examine in this.data - this.bytesAvailable = data.byteLength; - // the current word being examined - this.word = 0; // :uint - // the number of bits left to examine in the current word - this.bitsAvailable = 0; // :uint - } - - // ():void - var _proto = ExpGolomb.prototype; - _proto.loadWord = function loadWord() { - var data = this.data; - var bytesAvailable = this.bytesAvailable; - var position = data.byteLength - bytesAvailable; - var workingBytes = new Uint8Array(4); - var availableBytes = Math.min(4, bytesAvailable); - if (availableBytes === 0) { - throw new Error('no bytes available'); - } - workingBytes.set(data.subarray(position, position + availableBytes)); - this.word = new DataView(workingBytes.buffer).getUint32(0); - // track the amount of this.data that has been processed - this.bitsAvailable = availableBytes * 8; - this.bytesAvailable -= availableBytes; - } - - // (count:int):void - ; - _proto.skipBits = function skipBits(count) { - var skipBytes; // :int - count = Math.min(count, this.bytesAvailable * 8 + this.bitsAvailable); - if (this.bitsAvailable > count) { - this.word <<= count; - this.bitsAvailable -= count; - } else { - count -= this.bitsAvailable; - skipBytes = count >> 3; - count -= skipBytes << 3; - this.bytesAvailable -= skipBytes; - this.loadWord(); - this.word <<= count; - this.bitsAvailable -= count; - } - } - - // (size:int):uint - ; - _proto.readBits = function readBits(size) { - var bits = Math.min(this.bitsAvailable, size); // :uint - var valu = this.word >>> 32 - bits; // :uint - if (size > 32) { - logger.error('Cannot read more than 32 bits at a time'); - } - this.bitsAvailable -= bits; - if (this.bitsAvailable > 0) { - this.word <<= bits; - } else if (this.bytesAvailable > 0) { - this.loadWord(); - } else { - throw new Error('no bits available'); - } - bits = size - bits; - if (bits > 0 && this.bitsAvailable) { - return valu << bits | this.readBits(bits); - } else { - return valu; - } - } - - // ():uint - ; - _proto.skipLZ = function skipLZ() { - var leadingZeroCount; // :uint - for (leadingZeroCount = 0; leadingZeroCount < this.bitsAvailable; ++leadingZeroCount) { - if ((this.word & 0x80000000 >>> leadingZeroCount) !== 0) { - // the first bit of working word is 1 - this.word <<= leadingZeroCount; - this.bitsAvailable -= leadingZeroCount; - return leadingZeroCount; - } - } - // we exhausted word and still have not found a 1 - this.loadWord(); - return leadingZeroCount + this.skipLZ(); - } - - // ():void - ; - _proto.skipUEG = function skipUEG() { - this.skipBits(1 + this.skipLZ()); - } - - // ():void - ; - _proto.skipEG = function skipEG() { - this.skipBits(1 + this.skipLZ()); - } - - // ():uint - ; - _proto.readUEG = function readUEG() { - var clz = this.skipLZ(); // :uint - return this.readBits(clz + 1) - 1; - } - - // ():int - ; - _proto.readEG = function readEG() { - var valu = this.readUEG(); // :int - if (0x01 & valu) { - // the number is odd if the low order bit is set - return 1 + valu >>> 1; // add 1 to make it even, and divide by 2 - } else { - return -1 * (valu >>> 1); // divide by two then make it negative - } - } - - // Some convenience functions - // :Boolean - ; - _proto.readBoolean = function readBoolean() { - return this.readBits(1) === 1; - } - - // ():int - ; - _proto.readUByte = function readUByte() { - return this.readBits(8); - } - - // ():int - ; - _proto.readUShort = function readUShort() { - return this.readBits(16); - } - - // ():int - ; - _proto.readUInt = function readUInt() { - return this.readBits(32); - } - - /** - * Advance the ExpGolomb decoder past a scaling list. The scaling - * list is optionally transmitted as part of a sequence parameter - * set and is not relevant to transmuxing. - * @param count the number of entries in this scaling list - * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1 - */; - _proto.skipScalingList = function skipScalingList(count) { - var lastScale = 8; - var nextScale = 8; - var deltaScale; - for (var j = 0; j < count; j++) { - if (nextScale !== 0) { - deltaScale = this.readEG(); - nextScale = (lastScale + deltaScale + 256) % 256; - } - lastScale = nextScale === 0 ? lastScale : nextScale; - } - } - - /** - * Read a sequence parameter set and return some interesting video - * properties. A sequence parameter set is the H264 metadata that - * describes the properties of upcoming video frames. - * @returns an object with configuration parsed from the - * sequence parameter set, including the dimensions of the - * associated video frames. - */; - _proto.readSPS = function readSPS() { - var frameCropLeftOffset = 0; - var frameCropRightOffset = 0; - var frameCropTopOffset = 0; - var frameCropBottomOffset = 0; - var numRefFramesInPicOrderCntCycle; - var scalingListCount; - var i; - var readUByte = this.readUByte.bind(this); - var readBits = this.readBits.bind(this); - var readUEG = this.readUEG.bind(this); - var readBoolean = this.readBoolean.bind(this); - var skipBits = this.skipBits.bind(this); - var skipEG = this.skipEG.bind(this); - var skipUEG = this.skipUEG.bind(this); - var skipScalingList = this.skipScalingList.bind(this); - readUByte(); - var profileIdc = readUByte(); // profile_idc - readBits(5); // profileCompat constraint_set[0-4]_flag, u(5) - skipBits(3); // reserved_zero_3bits u(3), - readUByte(); // level_idc u(8) - skipUEG(); // seq_parameter_set_id - // some profiles have more optional data we don't need - if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) { - var chromaFormatIdc = readUEG(); - if (chromaFormatIdc === 3) { - skipBits(1); - } // separate_colour_plane_flag - - skipUEG(); // bit_depth_luma_minus8 - skipUEG(); // bit_depth_chroma_minus8 - skipBits(1); // qpprime_y_zero_transform_bypass_flag - if (readBoolean()) { - // seq_scaling_matrix_present_flag - scalingListCount = chromaFormatIdc !== 3 ? 8 : 12; - for (i = 0; i < scalingListCount; i++) { - if (readBoolean()) { - // seq_scaling_list_present_flag[ i ] - if (i < 6) { - skipScalingList(16); - } else { - skipScalingList(64); - } - } - } - } - } - skipUEG(); // log2_max_frame_num_minus4 - var picOrderCntType = readUEG(); - if (picOrderCntType === 0) { - readUEG(); // log2_max_pic_order_cnt_lsb_minus4 - } else if (picOrderCntType === 1) { - skipBits(1); // delta_pic_order_always_zero_flag - skipEG(); // offset_for_non_ref_pic - skipEG(); // offset_for_top_to_bottom_field - numRefFramesInPicOrderCntCycle = readUEG(); - for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) { - skipEG(); - } // offset_for_ref_frame[ i ] - } - skipUEG(); // max_num_ref_frames - skipBits(1); // gaps_in_frame_num_value_allowed_flag - var picWidthInMbsMinus1 = readUEG(); - var picHeightInMapUnitsMinus1 = readUEG(); - var frameMbsOnlyFlag = readBits(1); - if (frameMbsOnlyFlag === 0) { - skipBits(1); - } // mb_adaptive_frame_field_flag - - skipBits(1); // direct_8x8_inference_flag - if (readBoolean()) { - // frame_cropping_flag - frameCropLeftOffset = readUEG(); - frameCropRightOffset = readUEG(); - frameCropTopOffset = readUEG(); - frameCropBottomOffset = readUEG(); - } - var pixelRatio = [1, 1]; - if (readBoolean()) { - // vui_parameters_present_flag - if (readBoolean()) { - // aspect_ratio_info_present_flag - var aspectRatioIdc = readUByte(); - switch (aspectRatioIdc) { - case 1: - pixelRatio = [1, 1]; - break; - case 2: - pixelRatio = [12, 11]; - break; - case 3: - pixelRatio = [10, 11]; - break; - case 4: - pixelRatio = [16, 11]; - break; - case 5: - pixelRatio = [40, 33]; - break; - case 6: - pixelRatio = [24, 11]; - break; - case 7: - pixelRatio = [20, 11]; - break; - case 8: - pixelRatio = [32, 11]; - break; - case 9: - pixelRatio = [80, 33]; - break; - case 10: - pixelRatio = [18, 11]; - break; - case 11: - pixelRatio = [15, 11]; - break; - case 12: - pixelRatio = [64, 33]; - break; - case 13: - pixelRatio = [160, 99]; - break; - case 14: - pixelRatio = [4, 3]; - break; - case 15: - pixelRatio = [3, 2]; - break; - case 16: - pixelRatio = [2, 1]; - break; - case 255: - { - pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()]; - break; - } - } - } - } - return { - width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2), - height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset), - pixelRatio: pixelRatio - }; - }; - _proto.readSliceType = function readSliceType() { - // skip NALu type - this.readUByte(); - // discard first_mb_in_slice - this.readUEG(); - // return slice_type - return this.readUEG(); - }; - return ExpGolomb; - }(); - - var AvcVideoParser = /*#__PURE__*/function (_BaseVideoParser) { - _inheritsLoose(AvcVideoParser, _BaseVideoParser); - function AvcVideoParser() { - return _BaseVideoParser.apply(this, arguments) || this; - } - var _proto = AvcVideoParser.prototype; - _proto.parseAVCPES = function parseAVCPES(track, textTrack, pes, last, duration) { - var _this = this; - var units = this.parseAVCNALu(track, pes.data); - var VideoSample = this.VideoSample; - var push; - var spsfound = false; - // free pes.data to save up some memory - pes.data = null; - - // if new NAL units found and last sample still there, let's push ... - // this helps parsing streams with missing AUD (only do this if AUD never found) - if (VideoSample && units.length && !track.audFound) { - this.pushAccessUnit(VideoSample, track); - VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, ''); - } - units.forEach(function (unit) { - var _VideoSample2; - switch (unit.type) { - // NDR - case 1: - { - var iskey = false; - push = true; - var data = unit.data; - // only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...) - if (spsfound && data.length > 4) { - // retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR - var sliceType = new ExpGolomb(data).readSliceType(); - // 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice - // SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples. - // An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice. - // I slice: A slice that is not an SI slice that is decoded using intra prediction only. - // if (sliceType === 2 || sliceType === 7) { - if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) { - iskey = true; - } - } - if (iskey) { - var _VideoSample; - // if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push - if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) { - _this.pushAccessUnit(VideoSample, track); - VideoSample = _this.VideoSample = null; - } - } - if (!VideoSample) { - VideoSample = _this.VideoSample = _this.createVideoSample(true, pes.pts, pes.dts, ''); - } - VideoSample.frame = true; - VideoSample.key = iskey; - break; - // IDR - } - case 5: - push = true; - // handle PES not starting with AUD - // if we have frame data already, that cannot belong to the same frame, so force a push - if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) { - _this.pushAccessUnit(VideoSample, track); - VideoSample = _this.VideoSample = null; - } - if (!VideoSample) { - VideoSample = _this.VideoSample = _this.createVideoSample(true, pes.pts, pes.dts, ''); - } - VideoSample.key = true; - VideoSample.frame = true; - break; - // SEI - case 6: - { - push = true; - parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples); - break; - // SPS - } - case 7: - { - var _track$pixelRatio, _track$pixelRatio2; - push = true; - spsfound = true; - var sps = unit.data; - var expGolombDecoder = new ExpGolomb(sps); - var config = expGolombDecoder.readSPS(); - if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) { - track.width = config.width; - track.height = config.height; - track.pixelRatio = config.pixelRatio; - track.sps = [sps]; - track.duration = duration; - var codecarray = sps.subarray(1, 4); - var codecstring = 'avc1.'; - for (var i = 0; i < 3; i++) { - var h = codecarray[i].toString(16); - if (h.length < 2) { - h = '0' + h; - } - codecstring += h; - } - track.codec = codecstring; - } - break; - } - // PPS - case 8: - push = true; - track.pps = [unit.data]; - break; - // AUD - case 9: - push = true; - track.audFound = true; - if (VideoSample) { - _this.pushAccessUnit(VideoSample, track); - } - VideoSample = _this.VideoSample = _this.createVideoSample(false, pes.pts, pes.dts, ''); - break; - // Filler Data - case 12: - push = true; - break; - default: - push = false; - if (VideoSample) { - VideoSample.debug += 'unknown NAL ' + unit.type + ' '; - } - break; - } - if (VideoSample && push) { - var _units = VideoSample.units; - _units.push(unit); - } - }); - // if last PES packet, push samples - if (last && VideoSample) { - this.pushAccessUnit(VideoSample, track); - this.VideoSample = null; - } - }; - _proto.parseAVCNALu = function parseAVCNALu(track, array) { - var len = array.byteLength; - var state = track.naluState || 0; - var lastState = state; - var units = []; - var i = 0; - var value; - var overflow; - var unitType; - var lastUnitStart = -1; - var lastUnitType = 0; - // logger.log('PES:' + Hex.hexDump(array)); - - if (state === -1) { - // special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet - lastUnitStart = 0; - // NALu type is value read from offset 0 - lastUnitType = array[0] & 0x1f; - state = 0; - i = 1; - } - while (i < len) { - value = array[i++]; - // optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case - if (!state) { - state = value ? 0 : 1; - continue; - } - if (state === 1) { - state = value ? 0 : 2; - continue; - } - // here we have state either equal to 2 or 3 - if (!value) { - state = 3; - } else if (value === 1) { - overflow = i - state - 1; - if (lastUnitStart >= 0) { - var unit = { - data: array.subarray(lastUnitStart, overflow), - type: lastUnitType - }; - // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength); - units.push(unit); - } else { - // lastUnitStart is undefined => this is the first start code found in this PES packet - // first check if start code delimiter is overlapping between 2 PES packets, - // ie it started in last packet (lastState not zero) - // and ended at the beginning of this PES packet (i <= 4 - lastState) - var lastUnit = this.getLastNalUnit(track.samples); - if (lastUnit) { - if (lastState && i <= 4 - lastState) { - // start delimiter overlapping between PES packets - // strip start delimiter bytes from the end of last NAL unit - // check if lastUnit had a state different from zero - if (lastUnit.state) { - // strip last bytes - lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState); - } - } - // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit. - - if (overflow > 0) { - // logger.log('first NALU found with overflow:' + overflow); - lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow)); - lastUnit.state = 0; - } - } - } - // check if we can read unit type - if (i < len) { - unitType = array[i] & 0x1f; - // logger.log('find NALU @ offset:' + i + ',type:' + unitType); - lastUnitStart = i; - lastUnitType = unitType; - state = 0; - } else { - // not enough byte to read unit type. let's read it on next PES parsing - state = -1; - } - } else { - state = 0; - } - } - if (lastUnitStart >= 0 && state >= 0) { - var _unit = { - data: array.subarray(lastUnitStart, len), - type: lastUnitType, - state: state - }; - units.push(_unit); - // logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state); - } - // no NALu found - if (units.length === 0) { - // append pes.data to previous NAL unit - var _lastUnit = this.getLastNalUnit(track.samples); - if (_lastUnit) { - _lastUnit.data = appendUint8Array(_lastUnit.data, array); - } - } - track.naluState = state; - return units; - }; - return AvcVideoParser; - }(BaseVideoParser); - - /** - * SAMPLE-AES decrypter - */ - - var SampleAesDecrypter = /*#__PURE__*/function () { - function SampleAesDecrypter(observer, config, keyData) { - this.keyData = void 0; - this.decrypter = void 0; - this.keyData = keyData; - this.decrypter = new Decrypter(config, { - removePKCS7Padding: false - }); - } - var _proto = SampleAesDecrypter.prototype; - _proto.decryptBuffer = function decryptBuffer(encryptedData) { - return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer); - } - - // AAC - encrypt all full 16 bytes blocks starting from offset 16 - ; - _proto.decryptAacSample = function decryptAacSample(samples, sampleIndex, callback) { - var _this = this; - var curUnit = samples[sampleIndex].unit; - if (curUnit.length <= 16) { - // No encrypted portion in this sample (first 16 bytes is not - // encrypted, see https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/HLS_Sample_Encryption/Encryption/Encryption.html), - return; - } - var encryptedData = curUnit.subarray(16, curUnit.length - curUnit.length % 16); - var encryptedBuffer = encryptedData.buffer.slice(encryptedData.byteOffset, encryptedData.byteOffset + encryptedData.length); - this.decryptBuffer(encryptedBuffer).then(function (decryptedBuffer) { - var decryptedData = new Uint8Array(decryptedBuffer); - curUnit.set(decryptedData, 16); - if (!_this.decrypter.isSync()) { - _this.decryptAacSamples(samples, sampleIndex + 1, callback); - } - }); - }; - _proto.decryptAacSamples = function decryptAacSamples(samples, sampleIndex, callback) { - for (;; sampleIndex++) { - if (sampleIndex >= samples.length) { - callback(); - return; - } - if (samples[sampleIndex].unit.length < 32) { - continue; - } - this.decryptAacSample(samples, sampleIndex, callback); - if (!this.decrypter.isSync()) { - return; - } - } - } - - // AVC - encrypt one 16 bytes block out of ten, starting from offset 32 - ; - _proto.getAvcEncryptedData = function getAvcEncryptedData(decodedData) { - var encryptedDataLen = Math.floor((decodedData.length - 48) / 160) * 16 + 16; - var encryptedData = new Int8Array(encryptedDataLen); - var outputPos = 0; - for (var inputPos = 32; inputPos < decodedData.length - 16; inputPos += 160, outputPos += 16) { - encryptedData.set(decodedData.subarray(inputPos, inputPos + 16), outputPos); - } - return encryptedData; - }; - _proto.getAvcDecryptedUnit = function getAvcDecryptedUnit(decodedData, decryptedData) { - var uint8DecryptedData = new Uint8Array(decryptedData); - var inputPos = 0; - for (var outputPos = 32; outputPos < decodedData.length - 16; outputPos += 160, inputPos += 16) { - decodedData.set(uint8DecryptedData.subarray(inputPos, inputPos + 16), outputPos); - } - return decodedData; - }; - _proto.decryptAvcSample = function decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit) { - var _this2 = this; - var decodedData = discardEPB(curUnit.data); - var encryptedData = this.getAvcEncryptedData(decodedData); - this.decryptBuffer(encryptedData.buffer).then(function (decryptedBuffer) { - curUnit.data = _this2.getAvcDecryptedUnit(decodedData, decryptedBuffer); - if (!_this2.decrypter.isSync()) { - _this2.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback); - } - }); - }; - _proto.decryptAvcSamples = function decryptAvcSamples(samples, sampleIndex, unitIndex, callback) { - if (samples instanceof Uint8Array) { - throw new Error('Cannot decrypt samples of type Uint8Array'); - } - for (;; sampleIndex++, unitIndex = 0) { - if (sampleIndex >= samples.length) { - callback(); - return; - } - var curUnits = samples[sampleIndex].units; - for (;; unitIndex++) { - if (unitIndex >= curUnits.length) { - break; - } - var curUnit = curUnits[unitIndex]; - if (curUnit.data.length <= 48 || curUnit.type !== 1 && curUnit.type !== 5) { - continue; - } - this.decryptAvcSample(samples, sampleIndex, unitIndex, callback, curUnit); - if (!this.decrypter.isSync()) { - return; - } - } - } - }; - return SampleAesDecrypter; - }(); - - var PACKET_LENGTH = 188; - var TSDemuxer = /*#__PURE__*/function () { - function TSDemuxer(observer, config, typeSupported) { - this.observer = void 0; - this.config = void 0; - this.typeSupported = void 0; - this.sampleAes = null; - this.pmtParsed = false; - this.audioCodec = void 0; - this.videoCodec = void 0; - this._duration = 0; - this._pmtId = -1; - this._videoTrack = void 0; - this._audioTrack = void 0; - this._id3Track = void 0; - this._txtTrack = void 0; - this.aacOverFlow = null; - this.remainderData = null; - this.videoParser = void 0; - this.observer = observer; - this.config = config; - this.typeSupported = typeSupported; - this.videoParser = new AvcVideoParser(); - } - TSDemuxer.probe = function probe(data) { - var syncOffset = TSDemuxer.syncOffset(data); - if (syncOffset > 0) { - logger.warn("MPEG2-TS detected but first sync word found @ offset " + syncOffset); - } - return syncOffset !== -1; - }; - TSDemuxer.syncOffset = function syncOffset(data) { - var length = data.length; - var scanwindow = Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1; - var i = 0; - while (i < scanwindow) { - // a TS init segment should contain at least 2 TS packets: PAT and PMT, each starting with 0x47 - var foundPat = false; - var packetStart = -1; - var tsPackets = 0; - for (var j = i; j < length; j += PACKET_LENGTH) { - if (data[j] === 0x47 && (length - j === PACKET_LENGTH || data[j + PACKET_LENGTH] === 0x47)) { - tsPackets++; - if (packetStart === -1) { - packetStart = j; - // First sync word found at offset, increase scan length (#5251) - if (packetStart !== 0) { - scanwindow = Math.min(packetStart + PACKET_LENGTH * 99, data.length - PACKET_LENGTH) + 1; - } - } - if (!foundPat) { - foundPat = parsePID(data, j) === 0; - } - // Sync word found at 0 with 3 packets, or found at offset least 2 packets up to scanwindow (#5501) - if (foundPat && tsPackets > 1 && (packetStart === 0 && tsPackets > 2 || j + PACKET_LENGTH > scanwindow)) { - return packetStart; - } - } else if (tsPackets) { - // Exit if sync word found, but does not contain contiguous packets - return -1; - } else { - break; - } - } - i++; - } - return -1; - } - - /** - * Creates a track model internal to demuxer used to drive remuxing input - */; - TSDemuxer.createTrack = function createTrack(type, duration) { - return { - container: type === 'video' || type === 'audio' ? 'video/mp2t' : undefined, - type: type, - id: RemuxerTrackIdConfig[type], - pid: -1, - inputTimeScale: 90000, - sequenceNumber: 0, - samples: [], - dropped: 0, - duration: type === 'audio' ? duration : undefined - }; - } - - /** - * Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start) - * Resets all internal track instances of the demuxer. - */; - var _proto = TSDemuxer.prototype; - _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { - this.pmtParsed = false; - this._pmtId = -1; - this._videoTrack = TSDemuxer.createTrack('video'); - this._audioTrack = TSDemuxer.createTrack('audio', trackDuration); - this._id3Track = TSDemuxer.createTrack('id3'); - this._txtTrack = TSDemuxer.createTrack('text'); - this._audioTrack.segmentCodec = 'aac'; - - // flush any partial content - this.aacOverFlow = null; - this.remainderData = null; - this.audioCodec = audioCodec; - this.videoCodec = videoCodec; - this._duration = trackDuration; - }; - _proto.resetTimeStamp = function resetTimeStamp() {}; - _proto.resetContiguity = function resetContiguity() { - var _audioTrack = this._audioTrack, - _videoTrack = this._videoTrack, - _id3Track = this._id3Track; - if (_audioTrack) { - _audioTrack.pesData = null; - } - if (_videoTrack) { - _videoTrack.pesData = null; - } - if (_id3Track) { - _id3Track.pesData = null; - } - this.aacOverFlow = null; - this.remainderData = null; - }; - _proto.demux = function demux(data, timeOffset, isSampleAes, flush) { - if (isSampleAes === void 0) { - isSampleAes = false; - } - if (flush === void 0) { - flush = false; - } - if (!isSampleAes) { - this.sampleAes = null; - } - var pes; - var videoTrack = this._videoTrack; - var audioTrack = this._audioTrack; - var id3Track = this._id3Track; - var textTrack = this._txtTrack; - var videoPid = videoTrack.pid; - var videoData = videoTrack.pesData; - var audioPid = audioTrack.pid; - var id3Pid = id3Track.pid; - var audioData = audioTrack.pesData; - var id3Data = id3Track.pesData; - var unknownPID = null; - var pmtParsed = this.pmtParsed; - var pmtId = this._pmtId; - var len = data.length; - if (this.remainderData) { - data = appendUint8Array(this.remainderData, data); - len = data.length; - this.remainderData = null; - } - if (len < PACKET_LENGTH && !flush) { - this.remainderData = data; - return { - audioTrack: audioTrack, - videoTrack: videoTrack, - id3Track: id3Track, - textTrack: textTrack - }; - } - var syncOffset = Math.max(0, TSDemuxer.syncOffset(data)); - len -= (len - syncOffset) % PACKET_LENGTH; - if (len < data.byteLength && !flush) { - this.remainderData = new Uint8Array(data.buffer, len, data.buffer.byteLength - len); - } - - // loop through TS packets - var tsPacketErrors = 0; - for (var start = syncOffset; start < len; start += PACKET_LENGTH) { - if (data[start] === 0x47) { - var stt = !!(data[start + 1] & 0x40); - var pid = parsePID(data, start); - var atf = (data[start + 3] & 0x30) >> 4; - - // if an adaption field is present, its length is specified by the fifth byte of the TS packet header. - var offset = void 0; - if (atf > 1) { - offset = start + 5 + data[start + 4]; - // continue if there is only adaptation field - if (offset === start + PACKET_LENGTH) { - continue; - } - } else { - offset = start + 4; - } - switch (pid) { - case videoPid: - if (stt) { - if (videoData && (pes = parsePES(videoData))) { - this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration); - } - videoData = { - data: [], - size: 0 - }; - } - if (videoData) { - videoData.data.push(data.subarray(offset, start + PACKET_LENGTH)); - videoData.size += start + PACKET_LENGTH - offset; - } - break; - case audioPid: - if (stt) { - if (audioData && (pes = parsePES(audioData))) { - switch (audioTrack.segmentCodec) { - case 'aac': - this.parseAACPES(audioTrack, pes); - break; - case 'mp3': - this.parseMPEGPES(audioTrack, pes); - break; - case 'ac3': - { - this.parseAC3PES(audioTrack, pes); - } - break; - } - } - audioData = { - data: [], - size: 0 - }; - } - if (audioData) { - audioData.data.push(data.subarray(offset, start + PACKET_LENGTH)); - audioData.size += start + PACKET_LENGTH - offset; - } - break; - case id3Pid: - if (stt) { - if (id3Data && (pes = parsePES(id3Data))) { - this.parseID3PES(id3Track, pes); - } - id3Data = { - data: [], - size: 0 - }; - } - if (id3Data) { - id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH)); - id3Data.size += start + PACKET_LENGTH - offset; - } - break; - case 0: - if (stt) { - offset += data[offset] + 1; - } - pmtId = this._pmtId = parsePAT(data, offset); - // logger.log('PMT PID:' + this._pmtId); - break; - case pmtId: - { - if (stt) { - offset += data[offset] + 1; - } - var parsedPIDs = parsePMT(data, offset, this.typeSupported, isSampleAes, this.observer); - - // only update track id if track PID found while parsing PMT - // this is to avoid resetting the PID to -1 in case - // track PID transiently disappears from the stream - // this could happen in case of transient missing audio samples for example - // NOTE this is only the PID of the track as found in TS, - // but we are not using this for MP4 track IDs. - videoPid = parsedPIDs.videoPid; - if (videoPid > 0) { - videoTrack.pid = videoPid; - videoTrack.segmentCodec = parsedPIDs.segmentVideoCodec; - } - audioPid = parsedPIDs.audioPid; - if (audioPid > 0) { - audioTrack.pid = audioPid; - audioTrack.segmentCodec = parsedPIDs.segmentAudioCodec; - } - id3Pid = parsedPIDs.id3Pid; - if (id3Pid > 0) { - id3Track.pid = id3Pid; - } - if (unknownPID !== null && !pmtParsed) { - logger.warn("MPEG-TS PMT found at " + start + " after unknown PID '" + unknownPID + "'. Backtracking to sync byte @" + syncOffset + " to parse all TS packets."); - unknownPID = null; - // we set it to -188, the += 188 in the for loop will reset start to 0 - start = syncOffset - 188; - } - pmtParsed = this.pmtParsed = true; - break; - } - case 0x11: - case 0x1fff: - break; - default: - unknownPID = pid; - break; - } - } else { - tsPacketErrors++; - } - } - if (tsPacketErrors > 0) { - emitParsingError(this.observer, new Error("Found " + tsPacketErrors + " TS packet/s that do not start with 0x47")); - } - videoTrack.pesData = videoData; - audioTrack.pesData = audioData; - id3Track.pesData = id3Data; - var demuxResult = { - audioTrack: audioTrack, - videoTrack: videoTrack, - id3Track: id3Track, - textTrack: textTrack - }; - if (flush) { - this.extractRemainingSamples(demuxResult); - } - return demuxResult; - }; - _proto.flush = function flush() { - var remainderData = this.remainderData; - this.remainderData = null; - var result; - if (remainderData) { - result = this.demux(remainderData, -1, false, true); - } else { - result = { - videoTrack: this._videoTrack, - audioTrack: this._audioTrack, - id3Track: this._id3Track, - textTrack: this._txtTrack - }; - } - this.extractRemainingSamples(result); - if (this.sampleAes) { - return this.decrypt(result, this.sampleAes); - } - return result; - }; - _proto.extractRemainingSamples = function extractRemainingSamples(demuxResult) { - var audioTrack = demuxResult.audioTrack, - videoTrack = demuxResult.videoTrack, - id3Track = demuxResult.id3Track, - textTrack = demuxResult.textTrack; - var videoData = videoTrack.pesData; - var audioData = audioTrack.pesData; - var id3Data = id3Track.pesData; - // try to parse last PES packets - var pes; - if (videoData && (pes = parsePES(videoData))) { - this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration); - videoTrack.pesData = null; - } else { - // either avcData null or PES truncated, keep it for next frag parsing - videoTrack.pesData = videoData; - } - if (audioData && (pes = parsePES(audioData))) { - switch (audioTrack.segmentCodec) { - case 'aac': - this.parseAACPES(audioTrack, pes); - break; - case 'mp3': - this.parseMPEGPES(audioTrack, pes); - break; - case 'ac3': - { - this.parseAC3PES(audioTrack, pes); - } - break; - } - audioTrack.pesData = null; - } else { - if (audioData != null && audioData.size) { - logger.log('last AAC PES packet truncated,might overlap between fragments'); - } - - // either audioData null or PES truncated, keep it for next frag parsing - audioTrack.pesData = audioData; - } - if (id3Data && (pes = parsePES(id3Data))) { - this.parseID3PES(id3Track, pes); - id3Track.pesData = null; - } else { - // either id3Data null or PES truncated, keep it for next frag parsing - id3Track.pesData = id3Data; - } - }; - _proto.demuxSampleAes = function demuxSampleAes(data, keyData, timeOffset) { - var demuxResult = this.demux(data, timeOffset, true, !this.config.progressive); - var sampleAes = this.sampleAes = new SampleAesDecrypter(this.observer, this.config, keyData); - return this.decrypt(demuxResult, sampleAes); - }; - _proto.decrypt = function decrypt(demuxResult, sampleAes) { - return new Promise(function (resolve) { - var audioTrack = demuxResult.audioTrack, - videoTrack = demuxResult.videoTrack; - if (audioTrack.samples && audioTrack.segmentCodec === 'aac') { - sampleAes.decryptAacSamples(audioTrack.samples, 0, function () { - if (videoTrack.samples) { - sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, function () { - resolve(demuxResult); - }); - } else { - resolve(demuxResult); - } - }); - } else if (videoTrack.samples) { - sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, function () { - resolve(demuxResult); - }); - } - }); - }; - _proto.destroy = function destroy() { - this._duration = 0; - }; - _proto.parseAACPES = function parseAACPES(track, pes) { - var startOffset = 0; - var aacOverFlow = this.aacOverFlow; - var data = pes.data; - if (aacOverFlow) { - this.aacOverFlow = null; - var frameMissingBytes = aacOverFlow.missing; - var sampleLength = aacOverFlow.sample.unit.byteLength; - // logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`); - if (frameMissingBytes === -1) { - data = appendUint8Array(aacOverFlow.sample.unit, data); - } else { - var frameOverflowBytes = sampleLength - frameMissingBytes; - aacOverFlow.sample.unit.set(data.subarray(0, frameMissingBytes), frameOverflowBytes); - track.samples.push(aacOverFlow.sample); - startOffset = aacOverFlow.missing; - } - } - // look for ADTS header (0xFFFx) - var offset; - var len; - for (offset = startOffset, len = data.length; offset < len - 1; offset++) { - if (isHeader$1(data, offset)) { - break; - } - } - // if ADTS header does not start straight from the beginning of the PES payload, raise an error - if (offset !== startOffset) { - var reason; - var recoverable = offset < len - 1; - if (recoverable) { - reason = "AAC PES did not start with ADTS header,offset:" + offset; - } else { - reason = 'No ADTS header found in AAC PES'; - } - emitParsingError(this.observer, new Error(reason), recoverable); - if (!recoverable) { - return; - } - } - initTrackConfig(track, this.observer, data, offset, this.audioCodec); - var pts; - if (pes.pts !== undefined) { - pts = pes.pts; - } else if (aacOverFlow) { - // if last AAC frame is overflowing, we should ensure timestamps are contiguous: - // first sample PTS should be equal to last sample PTS + frameDuration - var frameDuration = getFrameDuration(track.samplerate); - pts = aacOverFlow.sample.pts + frameDuration; - } else { - logger.warn('[tsdemuxer]: AAC PES unknown PTS'); - return; - } - - // scan for aac samples - var frameIndex = 0; - var frame; - while (offset < len) { - frame = appendFrame$1(track, data, offset, pts, frameIndex); - offset += frame.length; - if (!frame.missing) { - frameIndex++; - for (; offset < len - 1; offset++) { - if (isHeader$1(data, offset)) { - break; - } - } - } else { - this.aacOverFlow = frame; - break; - } - } - }; - _proto.parseMPEGPES = function parseMPEGPES(track, pes) { - var data = pes.data; - var length = data.length; - var frameIndex = 0; - var offset = 0; - var pts = pes.pts; - if (pts === undefined) { - logger.warn('[tsdemuxer]: MPEG PES unknown PTS'); - return; - } - while (offset < length) { - if (isHeader(data, offset)) { - var frame = appendFrame(track, data, offset, pts, frameIndex); - if (frame) { - offset += frame.length; - frameIndex++; - } else { - // logger.log('Unable to parse Mpeg audio frame'); - break; - } - } else { - // nothing found, keep looking - offset++; - } - } - }; - _proto.parseAC3PES = function parseAC3PES(track, pes) { - { - var data = pes.data; - var pts = pes.pts; - if (pts === undefined) { - logger.warn('[tsdemuxer]: AC3 PES unknown PTS'); - return; - } - var length = data.length; - var frameIndex = 0; - var offset = 0; - var parsed; - while (offset < length && (parsed = _appendFrame(track, data, offset, pts, frameIndex++)) > 0) { - offset += parsed; - } - } - }; - _proto.parseID3PES = function parseID3PES(id3Track, pes) { - if (pes.pts === undefined) { - logger.warn('[tsdemuxer]: ID3 PES unknown PTS'); - return; - } - var id3Sample = _extends({}, pes, { - type: this._videoTrack ? MetadataSchema.emsg : MetadataSchema.audioId3, - duration: Number.POSITIVE_INFINITY - }); - id3Track.samples.push(id3Sample); - }; - return TSDemuxer; - }(); - function parsePID(data, offset) { - // pid is a 13-bit field starting at the last bit of TS[1] - return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2]; - } - function parsePAT(data, offset) { - // skip the PSI header and parse the first PMT entry - return (data[offset + 10] & 0x1f) << 8 | data[offset + 11]; - } - function parsePMT(data, offset, typeSupported, isSampleAes, observer) { - var result = { - audioPid: -1, - videoPid: -1, - id3Pid: -1, - segmentVideoCodec: 'avc', - segmentAudioCodec: 'aac' - }; - var sectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2]; - var tableEnd = offset + 3 + sectionLength - 4; - // to determine where the table is, we have to figure out how - // long the program info descriptors are - var programInfoLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11]; - // advance the offset to the first entry in the mapping table - offset += 12 + programInfoLength; - while (offset < tableEnd) { - var pid = parsePID(data, offset); - var esInfoLength = (data[offset + 3] & 0x0f) << 8 | data[offset + 4]; - switch (data[offset]) { - case 0xcf: - // SAMPLE-AES AAC - if (!isSampleAes) { - logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC'); - break; - } - /* falls through */ - case 0x0f: - // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio) - // logger.log('AAC PID:' + pid); - if (result.audioPid === -1) { - result.audioPid = pid; - } - break; - - // Packetized metadata (ID3) - case 0x15: - // logger.log('ID3 PID:' + pid); - if (result.id3Pid === -1) { - result.id3Pid = pid; - } - break; - case 0xdb: - // SAMPLE-AES AVC - if (!isSampleAes) { - logEncryptedSamplesFoundInUnencryptedStream('H.264'); - break; - } - /* falls through */ - case 0x1b: - // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video) - // logger.log('AVC PID:' + pid); - if (result.videoPid === -1) { - result.videoPid = pid; - result.segmentVideoCodec = 'avc'; - } - break; - - // ISO/IEC 11172-3 (MPEG-1 audio) - // or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio) - case 0x03: - case 0x04: - // logger.log('MPEG PID:' + pid); - if (!typeSupported.mpeg && !typeSupported.mp3) { - logger.log('MPEG audio found, not supported in this browser'); - } else if (result.audioPid === -1) { - result.audioPid = pid; - result.segmentAudioCodec = 'mp3'; - } - break; - case 0xc1: - // SAMPLE-AES AC3 - if (!isSampleAes) { - logEncryptedSamplesFoundInUnencryptedStream('AC-3'); - break; - } - /* falls through */ - case 0x81: - { - if (!typeSupported.ac3) { - logger.log('AC-3 audio found, not supported in this browser'); - } else if (result.audioPid === -1) { - result.audioPid = pid; - result.segmentAudioCodec = 'ac3'; - } - } - break; - case 0x06: - // stream_type 6 can mean a lot of different things in case of DVB. - // We need to look at the descriptors. Right now, we're only interested - // in AC-3 audio, so we do the descriptor parsing only when we don't have - // an audio PID yet. - if (result.audioPid === -1 && esInfoLength > 0) { - var parsePos = offset + 5; - var remaining = esInfoLength; - while (remaining > 2) { - var descriptorId = data[parsePos]; - switch (descriptorId) { - case 0x6a: - // DVB Descriptor for AC-3 - { - if (typeSupported.ac3 !== true) { - logger.log('AC-3 audio found, not supported in this browser for now'); - } else { - result.audioPid = pid; - result.segmentAudioCodec = 'ac3'; - } - } - break; - } - var descriptorLen = data[parsePos + 1] + 2; - parsePos += descriptorLen; - remaining -= descriptorLen; - } - } - break; - case 0xc2: // SAMPLE-AES EC3 - /* falls through */ - case 0x87: - emitParsingError(observer, new Error('Unsupported EC-3 in M2TS found')); - return result; - case 0x24: - emitParsingError(observer, new Error('Unsupported HEVC in M2TS found')); - return result; - } - // move to the next table entry - // skip past the elementary stream descriptors, if present - offset += esInfoLength + 5; - } - return result; - } - function emitParsingError(observer, error, levelRetry) { - logger.warn("parsing error: " + error.message); - observer.emit(Events.ERROR, Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_PARSING_ERROR, - fatal: false, - levelRetry: levelRetry, - error: error, - reason: error.message - }); - } - function logEncryptedSamplesFoundInUnencryptedStream(type) { - logger.log(type + " with AES-128-CBC encryption found in unencrypted stream"); - } - function parsePES(stream) { - var i = 0; - var frag; - var pesLen; - var pesHdrLen; - var pesPts; - var pesDts; - var data = stream.data; - // safety check - if (!stream || stream.size === 0) { - return null; - } - - // we might need up to 19 bytes to read PES header - // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes - // usually only one merge is needed (and this is rare ...) - while (data[0].length < 19 && data.length > 1) { - data[0] = appendUint8Array(data[0], data[1]); - data.splice(1, 1); - } - // retrieve PTS/DTS from first fragment - frag = data[0]; - var pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2]; - if (pesPrefix === 1) { - pesLen = (frag[4] << 8) + frag[5]; - // if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated - // minus 6 : PES header size - if (pesLen && pesLen > stream.size - 6) { - return null; - } - var pesFlags = frag[7]; - if (pesFlags & 0xc0) { - /* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html - as PTS / DTS is 33 bit we cannot use bitwise operator in JS, - as Bitwise operators treat their operands as a sequence of 32 bits */ - pesPts = (frag[9] & 0x0e) * 536870912 + - // 1 << 29 - (frag[10] & 0xff) * 4194304 + - // 1 << 22 - (frag[11] & 0xfe) * 16384 + - // 1 << 14 - (frag[12] & 0xff) * 128 + - // 1 << 7 - (frag[13] & 0xfe) / 2; - if (pesFlags & 0x40) { - pesDts = (frag[14] & 0x0e) * 536870912 + - // 1 << 29 - (frag[15] & 0xff) * 4194304 + - // 1 << 22 - (frag[16] & 0xfe) * 16384 + - // 1 << 14 - (frag[17] & 0xff) * 128 + - // 1 << 7 - (frag[18] & 0xfe) / 2; - if (pesPts - pesDts > 60 * 90000) { - logger.warn(Math.round((pesPts - pesDts) / 90000) + "s delta between PTS and DTS, align them"); - pesPts = pesDts; - } - } else { - pesDts = pesPts; - } - } - pesHdrLen = frag[8]; - // 9 bytes : 6 bytes for PES header + 3 bytes for PES extension - var payloadStartOffset = pesHdrLen + 9; - if (stream.size <= payloadStartOffset) { - return null; - } - stream.size -= payloadStartOffset; - // reassemble PES packet - var pesData = new Uint8Array(stream.size); - for (var j = 0, dataLen = data.length; j < dataLen; j++) { - frag = data[j]; - var len = frag.byteLength; - if (payloadStartOffset) { - if (payloadStartOffset > len) { - // trim full frag if PES header bigger than frag - payloadStartOffset -= len; - continue; - } else { - // trim partial frag if PES header smaller than frag - frag = frag.subarray(payloadStartOffset); - len -= payloadStartOffset; - payloadStartOffset = 0; - } - } - pesData.set(frag, i); - i += len; - } - if (pesLen) { - // payload size : remove PES header + PES extension - pesLen -= pesHdrLen + 3; - } - return { - data: pesData, - pts: pesPts, - dts: pesDts, - len: pesLen - }; - } - return null; - } - - var MP3Demuxer = /*#__PURE__*/function (_BaseAudioDemuxer) { - _inheritsLoose(MP3Demuxer, _BaseAudioDemuxer); - function MP3Demuxer() { - return _BaseAudioDemuxer.apply(this, arguments) || this; - } - var _proto = MP3Demuxer.prototype; - _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration) { - _BaseAudioDemuxer.prototype.resetInitSegment.call(this, initSegment, audioCodec, videoCodec, trackDuration); - this._audioTrack = { - container: 'audio/mpeg', - type: 'audio', - id: 2, - pid: -1, - sequenceNumber: 0, - segmentCodec: 'mp3', - samples: [], - manifestCodec: audioCodec, - duration: trackDuration, - inputTimeScale: 90000, - dropped: 0 - }; - }; - MP3Demuxer.probe = function probe$1(data) { - if (!data) { - return false; - } - - // check if data contains ID3 timestamp and MPEG sync word - // Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1 - // Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III) - // More info http://www.mp3-tech.org/programmer/frame_header.html - var id3Data = getID3Data(data, 0); - var offset = (id3Data == null ? void 0 : id3Data.length) || 0; - - // Check for ac-3|ec-3 sync bytes and return false if present - if (id3Data && data[offset] === 0x0b && data[offset + 1] === 0x77 && getTimeStamp(id3Data) !== undefined && - // check the bsid to confirm ac-3 or ec-3 (not mp3) - getAudioBSID(data, offset) <= 16) { - return false; - } - for (var length = data.length; offset < length; offset++) { - if (probe(data, offset)) { - logger.log('MPEG Audio sync word found !'); - return true; - } - } - return false; - }; - _proto.canParse = function canParse$1(data, offset) { - return canParse(data, offset); - }; - _proto.appendFrame = function appendFrame$1(track, data, offset) { - if (this.basePTS === null) { - return; - } - return appendFrame(track, data, offset, this.basePTS, this.frameIndex); - }; - return MP3Demuxer; - }(BaseAudioDemuxer); - - /** - * AAC helper - */ - var AAC = /*#__PURE__*/function () { - function AAC() {} - AAC.getSilentFrame = function getSilentFrame(codec, channelCount) { - switch (codec) { - case 'mp4a.40.2': - if (channelCount === 1) { - return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]); - } else if (channelCount === 2) { - return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]); - } else if (channelCount === 3) { - return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]); - } else if (channelCount === 4) { - return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]); - } else if (channelCount === 5) { - return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]); - } else if (channelCount === 6) { - return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]); - } - break; - // handle HE-AAC below (mp4a.40.5 / mp4a.40.29) - default: - if (channelCount === 1) { - // ffmpeg -y -f lavfi -i "aevalsrc=0:d=0.05" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac - return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); - } else if (channelCount === 2) { - // ffmpeg -y -f lavfi -i "aevalsrc=0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac - return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); - } else if (channelCount === 3) { - // ffmpeg -y -f lavfi -i "aevalsrc=0|0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac - return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); - } - break; - } - return undefined; - }; - return AAC; - }(); - - /** - * Generate MP4 Box - */ - - var UINT32_MAX = Math.pow(2, 32) - 1; - var MP4 = /*#__PURE__*/function () { - function MP4() {} - MP4.init = function init() { - MP4.types = { - avc1: [], - // codingname - avcC: [], - btrt: [], - dinf: [], - dref: [], - esds: [], - ftyp: [], - hdlr: [], - mdat: [], - mdhd: [], - mdia: [], - mfhd: [], - minf: [], - moof: [], - moov: [], - mp4a: [], - '.mp3': [], - dac3: [], - 'ac-3': [], - mvex: [], - mvhd: [], - pasp: [], - sdtp: [], - stbl: [], - stco: [], - stsc: [], - stsd: [], - stsz: [], - stts: [], - tfdt: [], - tfhd: [], - traf: [], - trak: [], - trun: [], - trex: [], - tkhd: [], - vmhd: [], - smhd: [] - }; - var i; - for (i in MP4.types) { - if (MP4.types.hasOwnProperty(i)) { - MP4.types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)]; - } - } - var videoHdlr = new Uint8Array([0x00, - // version 0 - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x00, - // pre_defined - 0x76, 0x69, 0x64, 0x65, - // handler_type: 'vide' - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler' - ]); - var audioHdlr = new Uint8Array([0x00, - // version 0 - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x00, - // pre_defined - 0x73, 0x6f, 0x75, 0x6e, - // handler_type: 'soun' - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler' - ]); - MP4.HDLR_TYPES = { - video: videoHdlr, - audio: audioHdlr - }; - var dref = new Uint8Array([0x00, - // version 0 - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x01, - // entry_count - 0x00, 0x00, 0x00, 0x0c, - // entry_size - 0x75, 0x72, 0x6c, 0x20, - // 'url' type - 0x00, - // version 0 - 0x00, 0x00, 0x01 // entry_flags - ]); - var stco = new Uint8Array([0x00, - // version - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x00 // entry_count - ]); - MP4.STTS = MP4.STSC = MP4.STCO = stco; - MP4.STSZ = new Uint8Array([0x00, - // version - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x00, - // sample_size - 0x00, 0x00, 0x00, 0x00 // sample_count - ]); - MP4.VMHD = new Uint8Array([0x00, - // version - 0x00, 0x00, 0x01, - // flags - 0x00, 0x00, - // graphicsmode - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor - ]); - MP4.SMHD = new Uint8Array([0x00, - // version - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, - // balance - 0x00, 0x00 // reserved - ]); - MP4.STSD = new Uint8Array([0x00, - // version 0 - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x01]); // entry_count - - var majorBrand = new Uint8Array([105, 115, 111, 109]); // isom - var avc1Brand = new Uint8Array([97, 118, 99, 49]); // avc1 - var minorVersion = new Uint8Array([0, 0, 0, 1]); - MP4.FTYP = MP4.box(MP4.types.ftyp, majorBrand, minorVersion, majorBrand, avc1Brand); - MP4.DINF = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, dref)); - }; - MP4.box = function box(type) { - var size = 8; - for (var _len = arguments.length, payload = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - payload[_key - 1] = arguments[_key]; - } - var i = payload.length; - var len = i; - // calculate the total size we need to allocate - while (i--) { - size += payload[i].byteLength; - } - var result = new Uint8Array(size); - result[0] = size >> 24 & 0xff; - result[1] = size >> 16 & 0xff; - result[2] = size >> 8 & 0xff; - result[3] = size & 0xff; - result.set(type, 4); - // copy the payload into the result - for (i = 0, size = 8; i < len; i++) { - // copy payload[i] array @ offset size - result.set(payload[i], size); - size += payload[i].byteLength; - } - return result; - }; - MP4.hdlr = function hdlr(type) { - return MP4.box(MP4.types.hdlr, MP4.HDLR_TYPES[type]); - }; - MP4.mdat = function mdat(data) { - return MP4.box(MP4.types.mdat, data); - }; - MP4.mdhd = function mdhd(timescale, duration) { - duration *= timescale; - var upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); - var lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); - return MP4.box(MP4.types.mdhd, new Uint8Array([0x01, - // version 1 - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - // creation_time - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - // modification_time - timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff, - // timescale - upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x55, 0xc4, - // 'und' language (undetermined) - 0x00, 0x00])); - }; - MP4.mdia = function mdia(track) { - return MP4.box(MP4.types.mdia, MP4.mdhd(track.timescale, track.duration), MP4.hdlr(track.type), MP4.minf(track)); - }; - MP4.mfhd = function mfhd(sequenceNumber) { - return MP4.box(MP4.types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, - // flags - sequenceNumber >> 24, sequenceNumber >> 16 & 0xff, sequenceNumber >> 8 & 0xff, sequenceNumber & 0xff // sequence_number - ])); - }; - MP4.minf = function minf(track) { - if (track.type === 'audio') { - return MP4.box(MP4.types.minf, MP4.box(MP4.types.smhd, MP4.SMHD), MP4.DINF, MP4.stbl(track)); - } else { - return MP4.box(MP4.types.minf, MP4.box(MP4.types.vmhd, MP4.VMHD), MP4.DINF, MP4.stbl(track)); - } - }; - MP4.moof = function moof(sn, baseMediaDecodeTime, track) { - return MP4.box(MP4.types.moof, MP4.mfhd(sn), MP4.traf(track, baseMediaDecodeTime)); - }; - MP4.moov = function moov(tracks) { - var i = tracks.length; - var boxes = []; - while (i--) { - boxes[i] = MP4.trak(tracks[i]); - } - return MP4.box.apply(null, [MP4.types.moov, MP4.mvhd(tracks[0].timescale, tracks[0].duration)].concat(boxes).concat(MP4.mvex(tracks))); - }; - MP4.mvex = function mvex(tracks) { - var i = tracks.length; - var boxes = []; - while (i--) { - boxes[i] = MP4.trex(tracks[i]); - } - return MP4.box.apply(null, [MP4.types.mvex].concat(boxes)); - }; - MP4.mvhd = function mvhd(timescale, duration) { - duration *= timescale; - var upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); - var lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); - var bytes = new Uint8Array([0x01, - // version 1 - 0x00, 0x00, 0x00, - // flags - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - // creation_time - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - // modification_time - timescale >> 24 & 0xff, timescale >> 16 & 0xff, timescale >> 8 & 0xff, timescale & 0xff, - // timescale - upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x01, 0x00, 0x00, - // 1.0 rate - 0x01, 0x00, - // 1.0 volume - 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - // transformation: unity matrix - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // pre_defined - 0xff, 0xff, 0xff, 0xff // next_track_ID - ]); - return MP4.box(MP4.types.mvhd, bytes); - }; - MP4.sdtp = function sdtp(track) { - var samples = track.samples || []; - var bytes = new Uint8Array(4 + samples.length); - var i; - var flags; - // leave the full box header (4 bytes) all zero - // write the sample table - for (i = 0; i < samples.length; i++) { - flags = samples[i].flags; - bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy; - } - return MP4.box(MP4.types.sdtp, bytes); - }; - MP4.stbl = function stbl(track) { - return MP4.box(MP4.types.stbl, MP4.stsd(track), MP4.box(MP4.types.stts, MP4.STTS), MP4.box(MP4.types.stsc, MP4.STSC), MP4.box(MP4.types.stsz, MP4.STSZ), MP4.box(MP4.types.stco, MP4.STCO)); - }; - MP4.avc1 = function avc1(track) { - var sps = []; - var pps = []; - var i; - var data; - var len; - // assemble the SPSs - - for (i = 0; i < track.sps.length; i++) { - data = track.sps[i]; - len = data.byteLength; - sps.push(len >>> 8 & 0xff); - sps.push(len & 0xff); - - // SPS - sps = sps.concat(Array.prototype.slice.call(data)); - } - - // assemble the PPSs - for (i = 0; i < track.pps.length; i++) { - data = track.pps[i]; - len = data.byteLength; - pps.push(len >>> 8 & 0xff); - pps.push(len & 0xff); - pps = pps.concat(Array.prototype.slice.call(data)); - } - var avcc = MP4.box(MP4.types.avcC, new Uint8Array([0x01, - // version - sps[3], - // profile - sps[4], - // profile compat - sps[5], - // level - 0xfc | 3, - // lengthSizeMinusOne, hard-coded to 4 bytes - 0xe0 | track.sps.length // 3bit reserved (111) + numOfSequenceParameterSets - ].concat(sps).concat([track.pps.length // numOfPictureParameterSets - ]).concat(pps))); // "PPS" - var width = track.width; - var height = track.height; - var hSpacing = track.pixelRatio[0]; - var vSpacing = track.pixelRatio[1]; - return MP4.box(MP4.types.avc1, new Uint8Array([0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, - // reserved - 0x00, 0x01, - // data_reference_index - 0x00, 0x00, - // pre_defined - 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // pre_defined - width >> 8 & 0xff, width & 0xff, - // width - height >> 8 & 0xff, height & 0xff, - // height - 0x00, 0x48, 0x00, 0x00, - // horizresolution - 0x00, 0x48, 0x00, 0x00, - // vertresolution - 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x01, - // frame_count - 0x12, 0x64, 0x61, 0x69, 0x6c, - // dailymotion/hls.js - 0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // compressorname - 0x00, 0x18, - // depth = 24 - 0x11, 0x11]), - // pre_defined = -1 - avcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, - // bufferSizeDB - 0x00, 0x2d, 0xc6, 0xc0, - // maxBitrate - 0x00, 0x2d, 0xc6, 0xc0])), - // avgBitrate - MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24, - // hSpacing - hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24, - // vSpacing - vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff]))); - }; - MP4.esds = function esds(track) { - var configlen = track.config.length; - return new Uint8Array([0x00, - // version 0 - 0x00, 0x00, 0x00, - // flags - - 0x03, - // descriptor_type - 0x17 + configlen, - // length - 0x00, 0x01, - // es_id - 0x00, - // stream_priority - - 0x04, - // descriptor_type - 0x0f + configlen, - // length - 0x40, - // codec : mpeg4_audio - 0x15, - // stream_type - 0x00, 0x00, 0x00, - // buffer_size - 0x00, 0x00, 0x00, 0x00, - // maxBitrate - 0x00, 0x00, 0x00, 0x00, - // avgBitrate - - 0x05 // descriptor_type - ].concat([configlen]).concat(track.config).concat([0x06, 0x01, 0x02])); // GASpecificConfig)); // length + audio config descriptor - }; - MP4.audioStsd = function audioStsd(track) { - var samplerate = track.samplerate; - return new Uint8Array([0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, 0x00, - // reserved - 0x00, 0x01, - // data_reference_index - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, track.channelCount, - // channelcount - 0x00, 0x10, - // sampleSize:16bits - 0x00, 0x00, 0x00, 0x00, - // reserved2 - samplerate >> 8 & 0xff, samplerate & 0xff, - // - 0x00, 0x00]); - }; - MP4.mp4a = function mp4a(track) { - return MP4.box(MP4.types.mp4a, MP4.audioStsd(track), MP4.box(MP4.types.esds, MP4.esds(track))); - }; - MP4.mp3 = function mp3(track) { - return MP4.box(MP4.types['.mp3'], MP4.audioStsd(track)); - }; - MP4.ac3 = function ac3(track) { - return MP4.box(MP4.types['ac-3'], MP4.audioStsd(track), MP4.box(MP4.types.dac3, track.config)); - }; - MP4.stsd = function stsd(track) { - if (track.type === 'audio') { - if (track.segmentCodec === 'mp3' && track.codec === 'mp3') { - return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp3(track)); - } - if (track.segmentCodec === 'ac3') { - return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track)); - } - return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track)); - } else { - return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track)); - } - }; - MP4.tkhd = function tkhd(track) { - var id = track.id; - var duration = track.duration * track.timescale; - var width = track.width; - var height = track.height; - var upperWordDuration = Math.floor(duration / (UINT32_MAX + 1)); - var lowerWordDuration = Math.floor(duration % (UINT32_MAX + 1)); - return MP4.box(MP4.types.tkhd, new Uint8Array([0x01, - // version 1 - 0x00, 0x00, 0x07, - // flags - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - // creation_time - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, - // modification_time - id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff, - // track_ID - 0x00, 0x00, 0x00, 0x00, - // reserved - upperWordDuration >> 24, upperWordDuration >> 16 & 0xff, upperWordDuration >> 8 & 0xff, upperWordDuration & 0xff, lowerWordDuration >> 24, lowerWordDuration >> 16 & 0xff, lowerWordDuration >> 8 & 0xff, lowerWordDuration & 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // reserved - 0x00, 0x00, - // layer - 0x00, 0x00, - // alternate_group - 0x00, 0x00, - // non-audio track volume - 0x00, 0x00, - // reserved - 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - // transformation: unity matrix - width >> 8 & 0xff, width & 0xff, 0x00, 0x00, - // width - height >> 8 & 0xff, height & 0xff, 0x00, 0x00 // height - ])); - }; - MP4.traf = function traf(track, baseMediaDecodeTime) { - var sampleDependencyTable = MP4.sdtp(track); - var id = track.id; - var upperWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1)); - var lowerWordBaseMediaDecodeTime = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1)); - return MP4.box(MP4.types.traf, MP4.box(MP4.types.tfhd, new Uint8Array([0x00, - // version 0 - 0x00, 0x00, 0x00, - // flags - id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff // track_ID - ])), MP4.box(MP4.types.tfdt, new Uint8Array([0x01, - // version 1 - 0x00, 0x00, 0x00, - // flags - upperWordBaseMediaDecodeTime >> 24, upperWordBaseMediaDecodeTime >> 16 & 0xff, upperWordBaseMediaDecodeTime >> 8 & 0xff, upperWordBaseMediaDecodeTime & 0xff, lowerWordBaseMediaDecodeTime >> 24, lowerWordBaseMediaDecodeTime >> 16 & 0xff, lowerWordBaseMediaDecodeTime >> 8 & 0xff, lowerWordBaseMediaDecodeTime & 0xff])), MP4.trun(track, sampleDependencyTable.length + 16 + - // tfhd - 20 + - // tfdt - 8 + - // traf header - 16 + - // mfhd - 8 + - // moof header - 8), - // mdat header - sampleDependencyTable); - } - - /** - * Generate a track box. - * @param track a track definition - */; - MP4.trak = function trak(track) { - track.duration = track.duration || 0xffffffff; - return MP4.box(MP4.types.trak, MP4.tkhd(track), MP4.mdia(track)); - }; - MP4.trex = function trex(track) { - var id = track.id; - return MP4.box(MP4.types.trex, new Uint8Array([0x00, - // version 0 - 0x00, 0x00, 0x00, - // flags - id >> 24, id >> 16 & 0xff, id >> 8 & 0xff, id & 0xff, - // track_ID - 0x00, 0x00, 0x00, 0x01, - // default_sample_description_index - 0x00, 0x00, 0x00, 0x00, - // default_sample_duration - 0x00, 0x00, 0x00, 0x00, - // default_sample_size - 0x00, 0x01, 0x00, 0x01 // default_sample_flags - ])); - }; - MP4.trun = function trun(track, offset) { - var samples = track.samples || []; - var len = samples.length; - var arraylen = 12 + 16 * len; - var array = new Uint8Array(arraylen); - var i; - var sample; - var duration; - var size; - var flags; - var cts; - offset += 8 + arraylen; - array.set([track.type === 'video' ? 0x01 : 0x00, - // version 1 for video with signed-int sample_composition_time_offset - 0x00, 0x0f, 0x01, - // flags - len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff, - // sample_count - offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset - ], 0); - for (i = 0; i < len; i++) { - sample = samples[i]; - duration = sample.duration; - size = sample.size; - flags = sample.flags; - cts = sample.cts; - array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff, - // sample_duration - size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff, - // sample_size - flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f, - // sample_flags - cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset - ], 12 + 16 * i); - } - return MP4.box(MP4.types.trun, array); - }; - MP4.initSegment = function initSegment(tracks) { - if (!MP4.types) { - MP4.init(); - } - var movie = MP4.moov(tracks); - var result = appendUint8Array(MP4.FTYP, movie); - return result; - }; - return MP4; - }(); - MP4.types = void 0; - MP4.HDLR_TYPES = void 0; - MP4.STTS = void 0; - MP4.STSC = void 0; - MP4.STCO = void 0; - MP4.STSZ = void 0; - MP4.VMHD = void 0; - MP4.SMHD = void 0; - MP4.STSD = void 0; - MP4.FTYP = void 0; - MP4.DINF = void 0; - - var MPEG_TS_CLOCK_FREQ_HZ = 90000; - function toTimescaleFromBase(baseTime, destScale, srcBase, round) { - if (srcBase === void 0) { - srcBase = 1; - } - if (round === void 0) { - round = false; - } - var result = baseTime * destScale * srcBase; // equivalent to `(value * scale) / (1 / base)` - return round ? Math.round(result) : result; - } - function toTimescaleFromScale(baseTime, destScale, srcScale, round) { - if (srcScale === void 0) { - srcScale = 1; - } - if (round === void 0) { - round = false; - } - return toTimescaleFromBase(baseTime, destScale, 1 / srcScale, round); - } - function toMsFromMpegTsClock(baseTime, round) { - if (round === void 0) { - round = false; - } - return toTimescaleFromBase(baseTime, 1000, 1 / MPEG_TS_CLOCK_FREQ_HZ, round); - } - function toMpegTsClockFromTimescale(baseTime, srcScale) { - if (srcScale === void 0) { - srcScale = 1; - } - return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale); - } - - var MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds - var AAC_SAMPLES_PER_FRAME = 1024; - var MPEG_AUDIO_SAMPLE_PER_FRAME = 1152; - var AC3_SAMPLES_PER_FRAME = 1536; - var chromeVersion = null; - var safariWebkitVersion = null; - var MP4Remuxer = /*#__PURE__*/function () { - function MP4Remuxer(observer, config, typeSupported, vendor) { - this.observer = void 0; - this.config = void 0; - this.typeSupported = void 0; - this.ISGenerated = false; - this._initPTS = null; - this._initDTS = null; - this.nextAvcDts = null; - this.nextAudioPts = null; - this.videoSampleDuration = null; - this.isAudioContiguous = false; - this.isVideoContiguous = false; - this.videoTrackConfig = void 0; - this.observer = observer; - this.config = config; - this.typeSupported = typeSupported; - this.ISGenerated = false; - if (chromeVersion === null) { - var userAgent = navigator.userAgent || ''; - var result = userAgent.match(/Chrome\/(\d+)/i); - chromeVersion = result ? parseInt(result[1]) : 0; - } - if (safariWebkitVersion === null) { - var _result = navigator.userAgent.match(/Safari\/(\d+)/i); - safariWebkitVersion = _result ? parseInt(_result[1]) : 0; - } - } - var _proto = MP4Remuxer.prototype; - _proto.destroy = function destroy() { - // @ts-ignore - this.config = this.videoTrackConfig = this._initPTS = this._initDTS = null; - }; - _proto.resetTimeStamp = function resetTimeStamp(defaultTimeStamp) { - logger.log('[mp4-remuxer]: initPTS & initDTS reset'); - this._initPTS = this._initDTS = defaultTimeStamp; - }; - _proto.resetNextTimestamp = function resetNextTimestamp() { - logger.log('[mp4-remuxer]: reset next timestamp'); - this.isVideoContiguous = false; - this.isAudioContiguous = false; - }; - _proto.resetInitSegment = function resetInitSegment() { - logger.log('[mp4-remuxer]: ISGenerated flag reset'); - this.ISGenerated = false; - this.videoTrackConfig = undefined; - }; - _proto.getVideoStartPts = function getVideoStartPts(videoSamples) { - var rolloverDetected = false; - var startPTS = videoSamples.reduce(function (minPTS, sample) { - var delta = sample.pts - minPTS; - if (delta < -4294967296) { - // 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation - rolloverDetected = true; - return normalizePts(minPTS, sample.pts); - } else if (delta > 0) { - return minPTS; - } else { - return sample.pts; - } - }, videoSamples[0].pts); - if (rolloverDetected) { - logger.debug('PTS rollover detected'); - } - return startPTS; - }; - _proto.remux = function remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, flush, playlistType) { - var video; - var audio; - var initSegment; - var text; - var id3; - var independent; - var audioTimeOffset = timeOffset; - var videoTimeOffset = timeOffset; - - // If we're remuxing audio and video progressively, wait until we've received enough samples for each track before proceeding. - // This is done to synchronize the audio and video streams. We know if the current segment will have samples if the "pid" - // parameter is greater than -1. The pid is set when the PMT is parsed, which contains the tracks list. - // However, if the initSegment has already been generated, or we've reached the end of a segment (flush), - // then we can remux one track without waiting for the other. - var hasAudio = audioTrack.pid > -1; - var hasVideo = videoTrack.pid > -1; - var length = videoTrack.samples.length; - var enoughAudioSamples = audioTrack.samples.length > 0; - var enoughVideoSamples = flush && length > 0 || length > 1; - var canRemuxAvc = (!hasAudio || enoughAudioSamples) && (!hasVideo || enoughVideoSamples) || this.ISGenerated || flush; - if (canRemuxAvc) { - if (this.ISGenerated) { - var _videoTrack$pixelRati, _config$pixelRatio, _videoTrack$pixelRati2, _config$pixelRatio2; - var config = this.videoTrackConfig; - if (config && (videoTrack.width !== config.width || videoTrack.height !== config.height || ((_videoTrack$pixelRati = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati[0]) !== ((_config$pixelRatio = config.pixelRatio) == null ? void 0 : _config$pixelRatio[0]) || ((_videoTrack$pixelRati2 = videoTrack.pixelRatio) == null ? void 0 : _videoTrack$pixelRati2[1]) !== ((_config$pixelRatio2 = config.pixelRatio) == null ? void 0 : _config$pixelRatio2[1]))) { - this.resetInitSegment(); - } - } else { - initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); - } - var isVideoContiguous = this.isVideoContiguous; - var firstKeyFrameIndex = -1; - var firstKeyFramePTS; - if (enoughVideoSamples) { - firstKeyFrameIndex = findKeyframeIndex(videoTrack.samples); - if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) { - independent = true; - if (firstKeyFrameIndex > 0) { - logger.warn("[mp4-remuxer]: Dropped " + firstKeyFrameIndex + " out of " + length + " video samples due to a missing keyframe"); - var startPTS = this.getVideoStartPts(videoTrack.samples); - videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex); - videoTrack.dropped += firstKeyFrameIndex; - videoTimeOffset += (videoTrack.samples[0].pts - startPTS) / videoTrack.inputTimeScale; - firstKeyFramePTS = videoTimeOffset; - } else if (firstKeyFrameIndex === -1) { - logger.warn("[mp4-remuxer]: No keyframe found out of " + length + " video samples"); - independent = false; - } - } - } - if (this.ISGenerated) { - if (enoughAudioSamples && enoughVideoSamples) { - // timeOffset is expected to be the offset of the first timestamp of this fragment (first DTS) - // if first audio DTS is not aligned with first video DTS then we need to take that into account - // when providing timeOffset to remuxAudio / remuxVideo. if we don't do that, there might be a permanent / small - // drift between audio and video streams - var _startPTS = this.getVideoStartPts(videoTrack.samples); - var tsDelta = normalizePts(audioTrack.samples[0].pts, _startPTS) - _startPTS; - var audiovideoTimestampDelta = tsDelta / videoTrack.inputTimeScale; - audioTimeOffset += Math.max(0, audiovideoTimestampDelta); - videoTimeOffset += Math.max(0, -audiovideoTimestampDelta); - } - - // Purposefully remuxing audio before video, so that remuxVideo can use nextAudioPts, which is calculated in remuxAudio. - if (enoughAudioSamples) { - // if initSegment was generated without audio samples, regenerate it again - if (!audioTrack.samplerate) { - logger.warn('[mp4-remuxer]: regenerate InitSegment as audio detected'); - initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); - } - audio = this.remuxAudio(audioTrack, audioTimeOffset, this.isAudioContiguous, accurateTimeOffset, hasVideo || enoughVideoSamples || playlistType === PlaylistLevelType.AUDIO ? videoTimeOffset : undefined); - if (enoughVideoSamples) { - var audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0; - // if initSegment was generated without video samples, regenerate it again - if (!videoTrack.inputTimeScale) { - logger.warn('[mp4-remuxer]: regenerate InitSegment as video detected'); - initSegment = this.generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset); - } - video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, audioTrackLength); - } - } else if (enoughVideoSamples) { - video = this.remuxVideo(videoTrack, videoTimeOffset, isVideoContiguous, 0); - } - if (video) { - video.firstKeyFrame = firstKeyFrameIndex; - video.independent = firstKeyFrameIndex !== -1; - video.firstKeyFramePTS = firstKeyFramePTS; - } - } - } - - // Allow ID3 and text to remux, even if more audio/video samples are required - if (this.ISGenerated && this._initPTS && this._initDTS) { - if (id3Track.samples.length) { - id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, this._initPTS, this._initDTS); - } - if (textTrack.samples.length) { - text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, this._initPTS); - } - } - return { - audio: audio, - video: video, - initSegment: initSegment, - independent: independent, - text: text, - id3: id3 - }; - }; - _proto.generateIS = function generateIS(audioTrack, videoTrack, timeOffset, accurateTimeOffset) { - var audioSamples = audioTrack.samples; - var videoSamples = videoTrack.samples; - var typeSupported = this.typeSupported; - var tracks = {}; - var _initPTS = this._initPTS; - var computePTSDTS = !_initPTS || accurateTimeOffset; - var container = 'audio/mp4'; - var initPTS; - var initDTS; - var timescale; - if (computePTSDTS) { - initPTS = initDTS = Infinity; - } - if (audioTrack.config && audioSamples.length) { - // let's use audio sampling rate as MP4 time scale. - // rationale is that there is a integer nb of audio frames per audio sample (1024 for AAC) - // using audio sampling rate here helps having an integer MP4 frame duration - // this avoids potential rounding issue and AV sync issue - audioTrack.timescale = audioTrack.samplerate; - switch (audioTrack.segmentCodec) { - case 'mp3': - if (typeSupported.mpeg) { - // Chrome and Safari - container = 'audio/mpeg'; - audioTrack.codec = ''; - } else if (typeSupported.mp3) { - // Firefox - audioTrack.codec = 'mp3'; - } - break; - case 'ac3': - audioTrack.codec = 'ac-3'; - break; - } - tracks.audio = { - id: 'audio', - container: container, - codec: audioTrack.codec, - initSegment: audioTrack.segmentCodec === 'mp3' && typeSupported.mpeg ? new Uint8Array(0) : MP4.initSegment([audioTrack]), - metadata: { - channelCount: audioTrack.channelCount - } - }; - if (computePTSDTS) { - timescale = audioTrack.inputTimeScale; - if (!_initPTS || timescale !== _initPTS.timescale) { - // remember first PTS of this demuxing context. for audio, PTS = DTS - initPTS = initDTS = audioSamples[0].pts - Math.round(timescale * timeOffset); - } else { - computePTSDTS = false; - } - } - } - if (videoTrack.sps && videoTrack.pps && videoSamples.length) { - // let's use input time scale as MP4 video timescale - // we use input time scale straight away to avoid rounding issues on frame duration / cts computation - videoTrack.timescale = videoTrack.inputTimeScale; - tracks.video = { - id: 'main', - container: 'video/mp4', - codec: videoTrack.codec, - initSegment: MP4.initSegment([videoTrack]), - metadata: { - width: videoTrack.width, - height: videoTrack.height - } - }; - if (computePTSDTS) { - timescale = videoTrack.inputTimeScale; - if (!_initPTS || timescale !== _initPTS.timescale) { - var startPTS = this.getVideoStartPts(videoSamples); - var startOffset = Math.round(timescale * timeOffset); - initDTS = Math.min(initDTS, normalizePts(videoSamples[0].dts, startPTS) - startOffset); - initPTS = Math.min(initPTS, startPTS - startOffset); - } else { - computePTSDTS = false; - } - } - this.videoTrackConfig = { - width: videoTrack.width, - height: videoTrack.height, - pixelRatio: videoTrack.pixelRatio - }; - } - if (Object.keys(tracks).length) { - this.ISGenerated = true; - if (computePTSDTS) { - this._initPTS = { - baseTime: initPTS, - timescale: timescale - }; - this._initDTS = { - baseTime: initDTS, - timescale: timescale - }; - } else { - initPTS = timescale = undefined; - } - return { - tracks: tracks, - initPTS: initPTS, - timescale: timescale - }; - } - }; - _proto.remuxVideo = function remuxVideo(track, timeOffset, contiguous, audioTrackLength) { - var timeScale = track.inputTimeScale; - var inputSamples = track.samples; - var outputSamples = []; - var nbSamples = inputSamples.length; - var initPTS = this._initPTS; - var nextAvcDts = this.nextAvcDts; - var offset = 8; - var mp4SampleDuration = this.videoSampleDuration; - var firstDTS; - var lastDTS; - var minPTS = Number.POSITIVE_INFINITY; - var maxPTS = Number.NEGATIVE_INFINITY; - var sortSamples = false; - - // if parsed fragment is contiguous with last one, let's use last DTS value as reference - if (!contiguous || nextAvcDts === null) { - var pts = timeOffset * timeScale; - var cts = inputSamples[0].pts - normalizePts(inputSamples[0].dts, inputSamples[0].pts); - if (chromeVersion && nextAvcDts !== null && Math.abs(pts - cts - nextAvcDts) < 15000) { - // treat as contigous to adjust samples that would otherwise produce video buffer gaps in Chrome - contiguous = true; - } else { - // if not contiguous, let's use target timeOffset - nextAvcDts = pts - cts; - } - } - - // PTS is coded on 33bits, and can loop from -2^32 to 2^32 - // PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value - var initTime = initPTS.baseTime * timeScale / initPTS.timescale; - for (var i = 0; i < nbSamples; i++) { - var sample = inputSamples[i]; - sample.pts = normalizePts(sample.pts - initTime, nextAvcDts); - sample.dts = normalizePts(sample.dts - initTime, nextAvcDts); - if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) { - sortSamples = true; - } - } - - // sort video samples by DTS then PTS then demux id order - if (sortSamples) { - inputSamples.sort(function (a, b) { - var deltadts = a.dts - b.dts; - var deltapts = a.pts - b.pts; - return deltadts || deltapts; - }); - } - - // Get first/last DTS - firstDTS = inputSamples[0].dts; - lastDTS = inputSamples[inputSamples.length - 1].dts; - - // Sample duration (as expected by trun MP4 boxes), should be the delta between sample DTS - // set this constant duration as being the avg delta between consecutive DTS. - var inputDuration = lastDTS - firstDTS; - var averageSampleDuration = inputDuration ? Math.round(inputDuration / (nbSamples - 1)) : mp4SampleDuration || track.inputTimeScale / 30; - - // if fragment are contiguous, detect hole/overlapping between fragments - if (contiguous) { - // check timestamp continuity across consecutive fragments (this is to remove inter-fragment gap/hole) - var delta = firstDTS - nextAvcDts; - var foundHole = delta > averageSampleDuration; - var foundOverlap = delta < -1; - if (foundHole || foundOverlap) { - if (foundHole) { - logger.warn("AVC: " + toMsFromMpegTsClock(delta, true) + " ms (" + delta + "dts) hole between fragments detected at " + timeOffset.toFixed(3)); - } else { - logger.warn("AVC: " + toMsFromMpegTsClock(-delta, true) + " ms (" + delta + "dts) overlapping between fragments detected at " + timeOffset.toFixed(3)); - } - if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) { - firstDTS = nextAvcDts; - var firstPTS = inputSamples[0].pts - delta; - if (foundHole) { - inputSamples[0].dts = firstDTS; - inputSamples[0].pts = firstPTS; - } else { - for (var _i = 0; _i < inputSamples.length; _i++) { - if (inputSamples[_i].dts > firstPTS) { - break; - } - inputSamples[_i].dts -= delta; - inputSamples[_i].pts -= delta; - } - } - logger.log("Video: Initial PTS/DTS adjusted: " + toMsFromMpegTsClock(firstPTS, true) + "/" + toMsFromMpegTsClock(firstDTS, true) + ", delta: " + toMsFromMpegTsClock(delta, true) + " ms"); - } - } - } - firstDTS = Math.max(0, firstDTS); - var nbNalu = 0; - var naluLen = 0; - var dtsStep = firstDTS; - for (var _i2 = 0; _i2 < nbSamples; _i2++) { - // compute total/avc sample length and nb of NAL units - var _sample = inputSamples[_i2]; - var units = _sample.units; - var nbUnits = units.length; - var sampleLen = 0; - for (var j = 0; j < nbUnits; j++) { - sampleLen += units[j].data.length; - } - naluLen += sampleLen; - nbNalu += nbUnits; - _sample.length = sampleLen; - - // ensure sample monotonic DTS - if (_sample.dts < dtsStep) { - _sample.dts = dtsStep; - dtsStep += averageSampleDuration / 4 | 0 || 1; - } else { - dtsStep = _sample.dts; - } - minPTS = Math.min(_sample.pts, minPTS); - maxPTS = Math.max(_sample.pts, maxPTS); - } - lastDTS = inputSamples[nbSamples - 1].dts; - - /* concatenate the video data and construct the mdat in place - (need 8 more bytes to fill length and mpdat type) */ - var mdatSize = naluLen + 4 * nbNalu + 8; - var mdat; - try { - mdat = new Uint8Array(mdatSize); - } catch (err) { - this.observer.emit(Events.ERROR, Events.ERROR, { - type: ErrorTypes.MUX_ERROR, - details: ErrorDetails.REMUX_ALLOC_ERROR, - fatal: false, - error: err, - bytes: mdatSize, - reason: "fail allocating video mdat " + mdatSize - }); - return; - } - var view = new DataView(mdat.buffer); - view.setUint32(0, mdatSize); - mdat.set(MP4.types.mdat, 4); - var stretchedLastFrame = false; - var minDtsDelta = Number.POSITIVE_INFINITY; - var minPtsDelta = Number.POSITIVE_INFINITY; - var maxDtsDelta = Number.NEGATIVE_INFINITY; - var maxPtsDelta = Number.NEGATIVE_INFINITY; - for (var _i3 = 0; _i3 < nbSamples; _i3++) { - var _VideoSample = inputSamples[_i3]; - var VideoSampleUnits = _VideoSample.units; - var mp4SampleLength = 0; - // convert NALU bitstream to MP4 format (prepend NALU with size field) - for (var _j = 0, _nbUnits = VideoSampleUnits.length; _j < _nbUnits; _j++) { - var unit = VideoSampleUnits[_j]; - var unitData = unit.data; - var unitDataLen = unit.data.byteLength; - view.setUint32(offset, unitDataLen); - offset += 4; - mdat.set(unitData, offset); - offset += unitDataLen; - mp4SampleLength += 4 + unitDataLen; - } - - // expected sample duration is the Decoding Timestamp diff of consecutive samples - var ptsDelta = void 0; - if (_i3 < nbSamples - 1) { - mp4SampleDuration = inputSamples[_i3 + 1].dts - _VideoSample.dts; - ptsDelta = inputSamples[_i3 + 1].pts - _VideoSample.pts; - } else { - var config = this.config; - var lastFrameDuration = _i3 > 0 ? _VideoSample.dts - inputSamples[_i3 - 1].dts : averageSampleDuration; - ptsDelta = _i3 > 0 ? _VideoSample.pts - inputSamples[_i3 - 1].pts : averageSampleDuration; - if (config.stretchShortVideoTrack && this.nextAudioPts !== null) { - // In some cases, a segment's audio track duration may exceed the video track duration. - // Since we've already remuxed audio, and we know how long the audio track is, we look to - // see if the delta to the next segment is longer than maxBufferHole. - // If so, playback would potentially get stuck, so we artificially inflate - // the duration of the last frame to minimize any potential gap between segments. - var gapTolerance = Math.floor(config.maxBufferHole * timeScale); - var deltaToFrameEnd = (audioTrackLength ? minPTS + audioTrackLength * timeScale : this.nextAudioPts) - _VideoSample.pts; - if (deltaToFrameEnd > gapTolerance) { - // We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video - // frame overlap. maxBufferHole should be >> lastFrameDuration anyway. - mp4SampleDuration = deltaToFrameEnd - lastFrameDuration; - if (mp4SampleDuration < 0) { - mp4SampleDuration = lastFrameDuration; - } else { - stretchedLastFrame = true; - } - logger.log("[mp4-remuxer]: It is approximately " + deltaToFrameEnd / 90 + " ms to the next segment; using duration " + mp4SampleDuration / 90 + " ms for the last video frame."); - } else { - mp4SampleDuration = lastFrameDuration; - } - } else { - mp4SampleDuration = lastFrameDuration; - } - } - var compositionTimeOffset = Math.round(_VideoSample.pts - _VideoSample.dts); - minDtsDelta = Math.min(minDtsDelta, mp4SampleDuration); - maxDtsDelta = Math.max(maxDtsDelta, mp4SampleDuration); - minPtsDelta = Math.min(minPtsDelta, ptsDelta); - maxPtsDelta = Math.max(maxPtsDelta, ptsDelta); - outputSamples.push(new Mp4Sample(_VideoSample.key, mp4SampleDuration, mp4SampleLength, compositionTimeOffset)); - } - if (outputSamples.length) { - if (chromeVersion) { - if (chromeVersion < 70) { - // Chrome workaround, mark first sample as being a Random Access Point (keyframe) to avoid sourcebuffer append issue - // https://code.google.com/p/chromium/issues/detail?id=229412 - var flags = outputSamples[0].flags; - flags.dependsOn = 2; - flags.isNonSync = 0; - } - } else if (safariWebkitVersion) { - // Fix for "CNN special report, with CC" in test-streams (Safari browser only) - // Ignore DTS when frame durations are irregular. Safari MSE does not handle this leading to gaps. - if (maxPtsDelta - minPtsDelta < maxDtsDelta - minDtsDelta && averageSampleDuration / maxDtsDelta < 0.025 && outputSamples[0].cts === 0) { - logger.warn('Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.'); - var dts = firstDTS; - for (var _i4 = 0, len = outputSamples.length; _i4 < len; _i4++) { - var nextDts = dts + outputSamples[_i4].duration; - var _pts = dts + outputSamples[_i4].cts; - if (_i4 < len - 1) { - var nextPts = nextDts + outputSamples[_i4 + 1].cts; - outputSamples[_i4].duration = nextPts - _pts; - } else { - outputSamples[_i4].duration = _i4 ? outputSamples[_i4 - 1].duration : averageSampleDuration; - } - outputSamples[_i4].cts = 0; - dts = nextDts; - } - } - } - } - // next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale) - mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration; - this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration; - this.videoSampleDuration = mp4SampleDuration; - this.isVideoContiguous = true; - var moof = MP4.moof(track.sequenceNumber++, firstDTS, _extends({}, track, { - samples: outputSamples - })); - var type = 'video'; - var data = { - data1: moof, - data2: mdat, - startPTS: minPTS / timeScale, - endPTS: (maxPTS + mp4SampleDuration) / timeScale, - startDTS: firstDTS / timeScale, - endDTS: nextAvcDts / timeScale, - type: type, - hasAudio: false, - hasVideo: true, - nb: outputSamples.length, - dropped: track.dropped - }; - track.samples = []; - track.dropped = 0; - return data; - }; - _proto.getSamplesPerFrame = function getSamplesPerFrame(track) { - switch (track.segmentCodec) { - case 'mp3': - return MPEG_AUDIO_SAMPLE_PER_FRAME; - case 'ac3': - return AC3_SAMPLES_PER_FRAME; - default: - return AAC_SAMPLES_PER_FRAME; - } - }; - _proto.remuxAudio = function remuxAudio(track, timeOffset, contiguous, accurateTimeOffset, videoTimeOffset) { - var inputTimeScale = track.inputTimeScale; - var mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale; - var scaleFactor = inputTimeScale / mp4timeScale; - var mp4SampleDuration = this.getSamplesPerFrame(track); - var inputSampleDuration = mp4SampleDuration * scaleFactor; - var initPTS = this._initPTS; - var rawMPEG = track.segmentCodec === 'mp3' && this.typeSupported.mpeg; - var outputSamples = []; - var alignedWithVideo = videoTimeOffset !== undefined; - var inputSamples = track.samples; - var offset = rawMPEG ? 0 : 8; - var nextAudioPts = this.nextAudioPts || -1; - - // window.audioSamples ? window.audioSamples.push(inputSamples.map(s => s.pts)) : (window.audioSamples = [inputSamples.map(s => s.pts)]); - - // for audio samples, also consider consecutive fragments as being contiguous (even if a level switch occurs), - // for sake of clarity: - // consecutive fragments are frags with - // - less than 100ms gaps between new time offset (if accurate) and next expected PTS OR - // - less than 20 audio frames distance - // contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1) - // this helps ensuring audio continuity - // and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame - var timeOffsetMpegTS = timeOffset * inputTimeScale; - var initTime = initPTS.baseTime * inputTimeScale / initPTS.timescale; - this.isAudioContiguous = contiguous = contiguous || inputSamples.length && nextAudioPts > 0 && (accurateTimeOffset && Math.abs(timeOffsetMpegTS - nextAudioPts) < 9000 || Math.abs(normalizePts(inputSamples[0].pts - initTime, timeOffsetMpegTS) - nextAudioPts) < 20 * inputSampleDuration); - - // compute normalized PTS - inputSamples.forEach(function (sample) { - sample.pts = normalizePts(sample.pts - initTime, timeOffsetMpegTS); - }); - if (!contiguous || nextAudioPts < 0) { - // filter out sample with negative PTS that are not playable anyway - // if we don't remove these negative samples, they will shift all audio samples forward. - // leading to audio overlap between current / next fragment - inputSamples = inputSamples.filter(function (sample) { - return sample.pts >= 0; - }); - - // in case all samples have negative PTS, and have been filtered out, return now - if (!inputSamples.length) { - return; - } - if (videoTimeOffset === 0) { - // Set the start to 0 to match video so that start gaps larger than inputSampleDuration are filled with silence - nextAudioPts = 0; - } else if (accurateTimeOffset && !alignedWithVideo) { - // When not seeking, not live, and LevelDetails.PTSKnown, use fragment start as predicted next audio PTS - nextAudioPts = Math.max(0, timeOffsetMpegTS); - } else { - // if frags are not contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS - nextAudioPts = inputSamples[0].pts; - } - } - - // If the audio track is missing samples, the frames seem to get "left-shifted" within the - // resulting mp4 segment, causing sync issues and leaving gaps at the end of the audio segment. - // In an effort to prevent this from happening, we inject frames here where there are gaps. - // When possible, we inject a silent frame; when that's not possible, we duplicate the last - // frame. - - if (track.segmentCodec === 'aac') { - var maxAudioFramesDrift = this.config.maxAudioFramesDrift; - for (var i = 0, nextPts = nextAudioPts; i < inputSamples.length; i++) { - // First, let's see how far off this frame is from where we expect it to be - var sample = inputSamples[i]; - var pts = sample.pts; - var delta = pts - nextPts; - var duration = Math.abs(1000 * delta / inputTimeScale); - - // When remuxing with video, if we're overlapping by more than a duration, drop this sample to stay in sync - if (delta <= -maxAudioFramesDrift * inputSampleDuration && alignedWithVideo) { - if (i === 0) { - logger.warn("Audio frame @ " + (pts / inputTimeScale).toFixed(3) + "s overlaps nextAudioPts by " + Math.round(1000 * delta / inputTimeScale) + " ms."); - this.nextAudioPts = nextAudioPts = nextPts = pts; - } - } // eslint-disable-line brace-style - - // Insert missing frames if: - // 1: We're more than maxAudioFramesDrift frame away - // 2: Not more than MAX_SILENT_FRAME_DURATION away - // 3: currentTime (aka nextPtsNorm) is not 0 - // 4: remuxing with video (videoTimeOffset !== undefined) - else if (delta >= maxAudioFramesDrift * inputSampleDuration && duration < MAX_SILENT_FRAME_DURATION && alignedWithVideo) { - var missing = Math.round(delta / inputSampleDuration); - // Adjust nextPts so that silent samples are aligned with media pts. This will prevent media samples from - // later being shifted if nextPts is based on timeOffset and delta is not a multiple of inputSampleDuration. - nextPts = pts - missing * inputSampleDuration; - if (nextPts < 0) { - missing--; - nextPts += inputSampleDuration; - } - if (i === 0) { - this.nextAudioPts = nextAudioPts = nextPts; - } - logger.warn("[mp4-remuxer]: Injecting " + missing + " audio frame @ " + (nextPts / inputTimeScale).toFixed(3) + "s due to " + Math.round(1000 * delta / inputTimeScale) + " ms gap."); - for (var j = 0; j < missing; j++) { - var newStamp = Math.max(nextPts, 0); - var fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount); - if (!fillFrame) { - logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.'); - fillFrame = sample.unit.subarray(); - } - inputSamples.splice(i, 0, { - unit: fillFrame, - pts: newStamp - }); - nextPts += inputSampleDuration; - i++; - } - } - sample.pts = nextPts; - nextPts += inputSampleDuration; - } - } - var firstPTS = null; - var lastPTS = null; - var mdat; - var mdatSize = 0; - var sampleLength = inputSamples.length; - while (sampleLength--) { - mdatSize += inputSamples[sampleLength].unit.byteLength; - } - for (var _j2 = 0, _nbSamples = inputSamples.length; _j2 < _nbSamples; _j2++) { - var audioSample = inputSamples[_j2]; - var unit = audioSample.unit; - var _pts2 = audioSample.pts; - if (lastPTS !== null) { - // If we have more than one sample, set the duration of the sample to the "real" duration; the PTS diff with - // the previous sample - var prevSample = outputSamples[_j2 - 1]; - prevSample.duration = Math.round((_pts2 - lastPTS) / scaleFactor); - } else { - if (contiguous && track.segmentCodec === 'aac') { - // set PTS/DTS to expected PTS/DTS - _pts2 = nextAudioPts; - } - // remember first PTS of our audioSamples - firstPTS = _pts2; - if (mdatSize > 0) { - /* concatenate the audio data and construct the mdat in place - (need 8 more bytes to fill length and mdat type) */ - mdatSize += offset; - try { - mdat = new Uint8Array(mdatSize); - } catch (err) { - this.observer.emit(Events.ERROR, Events.ERROR, { - type: ErrorTypes.MUX_ERROR, - details: ErrorDetails.REMUX_ALLOC_ERROR, - fatal: false, - error: err, - bytes: mdatSize, - reason: "fail allocating audio mdat " + mdatSize - }); - return; - } - if (!rawMPEG) { - var view = new DataView(mdat.buffer); - view.setUint32(0, mdatSize); - mdat.set(MP4.types.mdat, 4); - } - } else { - // no audio samples - return; - } - } - mdat.set(unit, offset); - var unitLen = unit.byteLength; - offset += unitLen; - // Default the sample's duration to the computed mp4SampleDuration, which will either be 1024 for AAC or 1152 for MPEG - // In the case that we have 1 sample, this will be the duration. If we have more than one sample, the duration - // becomes the PTS diff with the previous sample - outputSamples.push(new Mp4Sample(true, mp4SampleDuration, unitLen, 0)); - lastPTS = _pts2; - } - - // We could end up with no audio samples if all input samples were overlapping with the previously remuxed ones - var nbSamples = outputSamples.length; - if (!nbSamples) { - return; - } - - // The next audio sample PTS should be equal to last sample PTS + duration - var lastSample = outputSamples[outputSamples.length - 1]; - this.nextAudioPts = nextAudioPts = lastPTS + scaleFactor * lastSample.duration; - - // Set the track samples from inputSamples to outputSamples before remuxing - var moof = rawMPEG ? new Uint8Array(0) : MP4.moof(track.sequenceNumber++, firstPTS / scaleFactor, _extends({}, track, { - samples: outputSamples - })); - - // Clear the track samples. This also clears the samples array in the demuxer, since the reference is shared - track.samples = []; - var start = firstPTS / inputTimeScale; - var end = nextAudioPts / inputTimeScale; - var type = 'audio'; - var audioData = { - data1: moof, - data2: mdat, - startPTS: start, - endPTS: end, - startDTS: start, - endDTS: end, - type: type, - hasAudio: true, - hasVideo: false, - nb: nbSamples - }; - this.isAudioContiguous = true; - return audioData; - }; - _proto.remuxEmptyAudio = function remuxEmptyAudio(track, timeOffset, contiguous, videoData) { - var inputTimeScale = track.inputTimeScale; - var mp4timeScale = track.samplerate ? track.samplerate : inputTimeScale; - var scaleFactor = inputTimeScale / mp4timeScale; - var nextAudioPts = this.nextAudioPts; - // sync with video's timestamp - var initDTS = this._initDTS; - var init90kHz = initDTS.baseTime * 90000 / initDTS.timescale; - var startDTS = (nextAudioPts !== null ? nextAudioPts : videoData.startDTS * inputTimeScale) + init90kHz; - var endDTS = videoData.endDTS * inputTimeScale + init90kHz; - // one sample's duration value - var frameDuration = scaleFactor * AAC_SAMPLES_PER_FRAME; - // samples count of this segment's duration - var nbSamples = Math.ceil((endDTS - startDTS) / frameDuration); - // silent frame - var silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount); - logger.warn('[mp4-remuxer]: remux empty Audio'); - // Can't remux if we can't generate a silent frame... - if (!silentFrame) { - logger.trace('[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec'); - return; - } - var samples = []; - for (var i = 0; i < nbSamples; i++) { - var stamp = startDTS + i * frameDuration; - samples.push({ - unit: silentFrame, - pts: stamp, - dts: stamp - }); - } - track.samples = samples; - return this.remuxAudio(track, timeOffset, contiguous, false); - }; - return MP4Remuxer; - }(); - function normalizePts(value, reference) { - var offset; - if (reference === null) { - return value; - } - if (reference < value) { - // - 2^33 - offset = -8589934592; - } else { - // + 2^33 - offset = 8589934592; - } - /* PTS is 33bit (from 0 to 2^33 -1) - if diff between value and reference is bigger than half of the amplitude (2^32) then it means that - PTS looping occured. fill the gap */ - while (Math.abs(value - reference) > 4294967296) { - value += offset; - } - return value; - } - function findKeyframeIndex(samples) { - for (var i = 0; i < samples.length; i++) { - if (samples[i].key) { - return i; - } - } - return -1; - } - function flushTextTrackMetadataCueSamples(track, timeOffset, initPTS, initDTS) { - var length = track.samples.length; - if (!length) { - return; - } - var inputTimeScale = track.inputTimeScale; - for (var index = 0; index < length; index++) { - var sample = track.samples[index]; - // setting id3 pts, dts to relative time - // using this._initPTS and this._initDTS to calculate relative time - sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; - sample.dts = normalizePts(sample.dts - initDTS.baseTime * inputTimeScale / initDTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; - } - var samples = track.samples; - track.samples = []; - return { - samples: samples - }; - } - function flushTextTrackUserdataCueSamples(track, timeOffset, initPTS) { - var length = track.samples.length; - if (!length) { - return; - } - var inputTimeScale = track.inputTimeScale; - for (var index = 0; index < length; index++) { - var sample = track.samples[index]; - // setting text pts, dts to relative time - // using this._initPTS and this._initDTS to calculate relative time - sample.pts = normalizePts(sample.pts - initPTS.baseTime * inputTimeScale / initPTS.timescale, timeOffset * inputTimeScale) / inputTimeScale; - } - track.samples.sort(function (a, b) { - return a.pts - b.pts; - }); - var samples = track.samples; - track.samples = []; - return { - samples: samples - }; - } - var Mp4Sample = function Mp4Sample(isKeyframe, duration, size, cts) { - this.size = void 0; - this.duration = void 0; - this.cts = void 0; - this.flags = void 0; - this.duration = duration; - this.size = size; - this.cts = cts; - this.flags = { - isLeading: 0, - isDependedOn: 0, - hasRedundancy: 0, - degradPrio: 0, - dependsOn: isKeyframe ? 2 : 1, - isNonSync: isKeyframe ? 0 : 1 - }; - }; - - var PassThroughRemuxer = /*#__PURE__*/function () { - function PassThroughRemuxer() { - this.emitInitSegment = false; - this.audioCodec = void 0; - this.videoCodec = void 0; - this.initData = void 0; - this.initPTS = null; - this.initTracks = void 0; - this.lastEndTime = null; - } - var _proto = PassThroughRemuxer.prototype; - _proto.destroy = function destroy() {}; - _proto.resetTimeStamp = function resetTimeStamp(defaultInitPTS) { - this.initPTS = defaultInitPTS; - this.lastEndTime = null; - }; - _proto.resetNextTimestamp = function resetNextTimestamp() { - this.lastEndTime = null; - }; - _proto.resetInitSegment = function resetInitSegment(initSegment, audioCodec, videoCodec, decryptdata) { - this.audioCodec = audioCodec; - this.videoCodec = videoCodec; - this.generateInitSegment(patchEncyptionData(initSegment, decryptdata)); - this.emitInitSegment = true; - }; - _proto.generateInitSegment = function generateInitSegment(initSegment) { - var audioCodec = this.audioCodec, - videoCodec = this.videoCodec; - if (!(initSegment != null && initSegment.byteLength)) { - this.initTracks = undefined; - this.initData = undefined; - return; - } - var initData = this.initData = parseInitSegment(initSegment); - - // Get codec from initSegment or fallback to default - if (initData.audio) { - audioCodec = getParsedTrackCodec(initData.audio, ElementaryStreamTypes.AUDIO); - } - if (initData.video) { - videoCodec = getParsedTrackCodec(initData.video, ElementaryStreamTypes.VIDEO); - } - var tracks = {}; - if (initData.audio && initData.video) { - tracks.audiovideo = { - container: 'video/mp4', - codec: audioCodec + ',' + videoCodec, - initSegment: initSegment, - id: 'main' - }; - } else if (initData.audio) { - tracks.audio = { - container: 'audio/mp4', - codec: audioCodec, - initSegment: initSegment, - id: 'audio' - }; - } else if (initData.video) { - tracks.video = { - container: 'video/mp4', - codec: videoCodec, - initSegment: initSegment, - id: 'main' - }; - } else { - logger.warn('[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes.'); - } - this.initTracks = tracks; - }; - _proto.remux = function remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset) { - var _initData, _initData2; - var initPTS = this.initPTS, - lastEndTime = this.lastEndTime; - var result = { - audio: undefined, - video: undefined, - text: textTrack, - id3: id3Track, - initSegment: undefined - }; - - // If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the - // lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update - // the media duration (which is what timeOffset is provided as) before we need to process the next chunk. - if (!isFiniteNumber(lastEndTime)) { - lastEndTime = this.lastEndTime = timeOffset || 0; - } - - // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only - // audio or video (or both); adding it to video was an arbitrary choice. - var data = videoTrack.samples; - if (!(data != null && data.length)) { - return result; - } - var initSegment = { - initPTS: undefined, - timescale: 1 - }; - var initData = this.initData; - if (!((_initData = initData) != null && _initData.length)) { - this.generateInitSegment(data); - initData = this.initData; - } - if (!((_initData2 = initData) != null && _initData2.length)) { - // We can't remux if the initSegment could not be generated - logger.warn('[passthrough-remuxer.ts]: Failed to generate initSegment.'); - return result; - } - if (this.emitInitSegment) { - initSegment.tracks = this.initTracks; - this.emitInitSegment = false; - } - var duration = getDuration(data, initData); - var startDTS = getStartDTS(initData, data); - var decodeTime = startDTS === null ? timeOffset : startDTS; - if (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) || initSegment.timescale !== initPTS.timescale && accurateTimeOffset) { - initSegment.initPTS = decodeTime - timeOffset; - if (initPTS && initPTS.timescale === 1) { - logger.warn("Adjusting initPTS by " + (initSegment.initPTS - initPTS.baseTime)); - } - this.initPTS = initPTS = { - baseTime: initSegment.initPTS, - timescale: 1 - }; - } - var startTime = audioTrack ? decodeTime - initPTS.baseTime / initPTS.timescale : lastEndTime; - var endTime = startTime + duration; - offsetStartDTS(initData, data, initPTS.baseTime / initPTS.timescale); - if (duration > 0) { - this.lastEndTime = endTime; - } else { - logger.warn('Duration parsed from mp4 should be greater than zero'); - this.resetNextTimestamp(); - } - var hasAudio = !!initData.audio; - var hasVideo = !!initData.video; - var type = ''; - if (hasAudio) { - type += 'audio'; - } - if (hasVideo) { - type += 'video'; - } - var track = { - data1: data, - startPTS: startTime, - startDTS: startTime, - endPTS: endTime, - endDTS: endTime, - type: type, - hasAudio: hasAudio, - hasVideo: hasVideo, - nb: 1, - dropped: 0 - }; - result.audio = track.type === 'audio' ? track : undefined; - result.video = track.type !== 'audio' ? track : undefined; - result.initSegment = initSegment; - result.id3 = flushTextTrackMetadataCueSamples(id3Track, timeOffset, initPTS, initPTS); - if (textTrack.samples.length) { - result.text = flushTextTrackUserdataCueSamples(textTrack, timeOffset, initPTS); - } - return result; - }; - return PassThroughRemuxer; - }(); - function isInvalidInitPts(initPTS, startDTS, timeOffset, duration) { - if (initPTS === null) { - return true; - } - // InitPTS is invalid when distance from program would be more than segment duration or a minimum of one second - var minDuration = Math.max(duration, 1); - var startTime = startDTS - initPTS.baseTime / initPTS.timescale; - return Math.abs(startTime - timeOffset) > minDuration; - } - function getParsedTrackCodec(track, type) { - var parsedCodec = track == null ? void 0 : track.codec; - if (parsedCodec && parsedCodec.length > 4) { - return parsedCodec; - } - if (type === ElementaryStreamTypes.AUDIO) { - if (parsedCodec === 'ec-3' || parsedCodec === 'ac-3' || parsedCodec === 'alac') { - return parsedCodec; - } - if (parsedCodec === 'fLaC' || parsedCodec === 'Opus') { - // Opting not to get `preferManagedMediaSource` from player config for isSupported() check for simplicity - var preferManagedMediaSource = false; - return getCodecCompatibleName(parsedCodec, preferManagedMediaSource); - } - var result = 'mp4a.40.5'; - logger.info("Parsed audio codec \"" + parsedCodec + "\" or audio object type not handled. Using \"" + result + "\""); - return result; - } - // Provide defaults based on codec type - // This allows for some playback of some fmp4 playlists without CODECS defined in manifest - logger.warn("Unhandled video codec \"" + parsedCodec + "\""); - if (parsedCodec === 'hvc1' || parsedCodec === 'hev1') { - return 'hvc1.1.6.L120.90'; - } - if (parsedCodec === 'av01') { - return 'av01.0.04M.08'; - } - return 'avc1.42e01e'; - } - - var now; - // performance.now() not available on WebWorker, at least on Safari Desktop - try { - now = self.performance.now.bind(self.performance); - } catch (err) { - logger.debug('Unable to use Performance API on this environment'); - now = optionalSelf == null ? void 0 : optionalSelf.Date.now; - } - var muxConfig = [{ - demux: MP4Demuxer, - remux: PassThroughRemuxer - }, { - demux: TSDemuxer, - remux: MP4Remuxer - }, { - demux: AACDemuxer, - remux: MP4Remuxer - }, { - demux: MP3Demuxer, - remux: MP4Remuxer - }]; - { - muxConfig.splice(2, 0, { - demux: AC3Demuxer, - remux: MP4Remuxer - }); - } - var Transmuxer = /*#__PURE__*/function () { - function Transmuxer(observer, typeSupported, config, vendor, id) { - this.async = false; - this.observer = void 0; - this.typeSupported = void 0; - this.config = void 0; - this.vendor = void 0; - this.id = void 0; - this.demuxer = void 0; - this.remuxer = void 0; - this.decrypter = void 0; - this.probe = void 0; - this.decryptionPromise = null; - this.transmuxConfig = void 0; - this.currentTransmuxState = void 0; - this.observer = observer; - this.typeSupported = typeSupported; - this.config = config; - this.vendor = vendor; - this.id = id; - } - var _proto = Transmuxer.prototype; - _proto.configure = function configure(transmuxConfig) { - this.transmuxConfig = transmuxConfig; - if (this.decrypter) { - this.decrypter.reset(); - } - }; - _proto.push = function push(data, decryptdata, chunkMeta, state) { - var _this = this; - var stats = chunkMeta.transmuxing; - stats.executeStart = now(); - var uintData = new Uint8Array(data); - var currentTransmuxState = this.currentTransmuxState, - transmuxConfig = this.transmuxConfig; - if (state) { - this.currentTransmuxState = state; - } - var _ref = state || currentTransmuxState, - contiguous = _ref.contiguous, - discontinuity = _ref.discontinuity, - trackSwitch = _ref.trackSwitch, - accurateTimeOffset = _ref.accurateTimeOffset, - timeOffset = _ref.timeOffset, - initSegmentChange = _ref.initSegmentChange; - var audioCodec = transmuxConfig.audioCodec, - videoCodec = transmuxConfig.videoCodec, - defaultInitPts = transmuxConfig.defaultInitPts, - duration = transmuxConfig.duration, - initSegmentData = transmuxConfig.initSegmentData; - var keyData = getEncryptionType(uintData, decryptdata); - if (keyData && keyData.method === 'AES-128') { - var decrypter = this.getDecrypter(); - // Software decryption is synchronous; webCrypto is not - if (decrypter.isSync()) { - // Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached - // data is handled in the flush() call - var decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer); - // For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress - var loadingParts = chunkMeta.part > -1; - if (loadingParts) { - decryptedData = decrypter.flush(); - } - if (!decryptedData) { - stats.executeEnd = now(); - return emptyResult(chunkMeta); - } - uintData = new Uint8Array(decryptedData); - } else { - this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(function (decryptedData) { - // Calling push here is important; if flush() is called while this is still resolving, this ensures that - // the decrypted data has been transmuxed - var result = _this.push(decryptedData, null, chunkMeta); - _this.decryptionPromise = null; - return result; - }); - return this.decryptionPromise; - } - } - var resetMuxers = this.needsProbing(discontinuity, trackSwitch); - if (resetMuxers) { - var error = this.configureTransmuxer(uintData); - if (error) { - logger.warn("[transmuxer] " + error.message); - this.observer.emit(Events.ERROR, Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_PARSING_ERROR, - fatal: false, - error: error, - reason: error.message - }); - stats.executeEnd = now(); - return emptyResult(chunkMeta); - } - } - if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) { - this.resetInitSegment(initSegmentData, audioCodec, videoCodec, duration, decryptdata); - } - if (discontinuity || initSegmentChange || resetMuxers) { - this.resetInitialTimestamp(defaultInitPts); - } - if (!contiguous) { - this.resetContiguity(); - } - var result = this.transmux(uintData, keyData, timeOffset, accurateTimeOffset, chunkMeta); - var currentState = this.currentTransmuxState; - currentState.contiguous = true; - currentState.discontinuity = false; - currentState.trackSwitch = false; - stats.executeEnd = now(); - return result; - } - - // Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type) - ; - _proto.flush = function flush(chunkMeta) { - var _this2 = this; - var stats = chunkMeta.transmuxing; - stats.executeStart = now(); - var decrypter = this.decrypter, - currentTransmuxState = this.currentTransmuxState, - decryptionPromise = this.decryptionPromise; - if (decryptionPromise) { - // Upon resolution, the decryption promise calls push() and returns its TransmuxerResult up the stack. Therefore - // only flushing is required for async decryption - return decryptionPromise.then(function () { - return _this2.flush(chunkMeta); - }); - } - var transmuxResults = []; - var timeOffset = currentTransmuxState.timeOffset; - if (decrypter) { - // The decrypter may have data cached, which needs to be demuxed. In this case we'll have two TransmuxResults - // This happens in the case that we receive only 1 push call for a segment (either for non-progressive downloads, - // or for progressive downloads with small segments) - var decryptedData = decrypter.flush(); - if (decryptedData) { - // Push always returns a TransmuxerResult if decryptdata is null - transmuxResults.push(this.push(decryptedData, null, chunkMeta)); - } - } - var demuxer = this.demuxer, - remuxer = this.remuxer; - if (!demuxer || !remuxer) { - // If probing failed, then Hls.js has been given content its not able to handle - stats.executeEnd = now(); - return [emptyResult(chunkMeta)]; - } - var demuxResultOrPromise = demuxer.flush(timeOffset); - if (isPromise(demuxResultOrPromise)) { - // Decrypt final SAMPLE-AES samples - return demuxResultOrPromise.then(function (demuxResult) { - _this2.flushRemux(transmuxResults, demuxResult, chunkMeta); - return transmuxResults; - }); - } - this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta); - return transmuxResults; - }; - _proto.flushRemux = function flushRemux(transmuxResults, demuxResult, chunkMeta) { - var audioTrack = demuxResult.audioTrack, - videoTrack = demuxResult.videoTrack, - id3Track = demuxResult.id3Track, - textTrack = demuxResult.textTrack; - var _this$currentTransmux = this.currentTransmuxState, - accurateTimeOffset = _this$currentTransmux.accurateTimeOffset, - timeOffset = _this$currentTransmux.timeOffset; - logger.log("[transmuxer.ts]: Flushed fragment " + chunkMeta.sn + (chunkMeta.part > -1 ? ' p: ' + chunkMeta.part : '') + " of level " + chunkMeta.level); - var remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, true, this.id); - transmuxResults.push({ - remuxResult: remuxResult, - chunkMeta: chunkMeta - }); - chunkMeta.transmuxing.executeEnd = now(); - }; - _proto.resetInitialTimestamp = function resetInitialTimestamp(defaultInitPts) { - var demuxer = this.demuxer, - remuxer = this.remuxer; - if (!demuxer || !remuxer) { - return; - } - demuxer.resetTimeStamp(defaultInitPts); - remuxer.resetTimeStamp(defaultInitPts); - }; - _proto.resetContiguity = function resetContiguity() { - var demuxer = this.demuxer, - remuxer = this.remuxer; - if (!demuxer || !remuxer) { - return; - } - demuxer.resetContiguity(); - remuxer.resetNextTimestamp(); - }; - _proto.resetInitSegment = function resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration, decryptdata) { - var demuxer = this.demuxer, - remuxer = this.remuxer; - if (!demuxer || !remuxer) { - return; - } - demuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, trackDuration); - remuxer.resetInitSegment(initSegmentData, audioCodec, videoCodec, decryptdata); - }; - _proto.destroy = function destroy() { - if (this.demuxer) { - this.demuxer.destroy(); - this.demuxer = undefined; - } - if (this.remuxer) { - this.remuxer.destroy(); - this.remuxer = undefined; - } - }; - _proto.transmux = function transmux(data, keyData, timeOffset, accurateTimeOffset, chunkMeta) { - var result; - if (keyData && keyData.method === 'SAMPLE-AES') { - result = this.transmuxSampleAes(data, keyData, timeOffset, accurateTimeOffset, chunkMeta); - } else { - result = this.transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta); - } - return result; - }; - _proto.transmuxUnencrypted = function transmuxUnencrypted(data, timeOffset, accurateTimeOffset, chunkMeta) { - var _demux = this.demuxer.demux(data, timeOffset, false, !this.config.progressive), - audioTrack = _demux.audioTrack, - videoTrack = _demux.videoTrack, - id3Track = _demux.id3Track, - textTrack = _demux.textTrack; - var remuxResult = this.remuxer.remux(audioTrack, videoTrack, id3Track, textTrack, timeOffset, accurateTimeOffset, false, this.id); - return { - remuxResult: remuxResult, - chunkMeta: chunkMeta - }; - }; - _proto.transmuxSampleAes = function transmuxSampleAes(data, decryptData, timeOffset, accurateTimeOffset, chunkMeta) { - var _this3 = this; - return this.demuxer.demuxSampleAes(data, decryptData, timeOffset).then(function (demuxResult) { - var remuxResult = _this3.remuxer.remux(demuxResult.audioTrack, demuxResult.videoTrack, demuxResult.id3Track, demuxResult.textTrack, timeOffset, accurateTimeOffset, false, _this3.id); - return { - remuxResult: remuxResult, - chunkMeta: chunkMeta - }; - }); - }; - _proto.configureTransmuxer = function configureTransmuxer(data) { - var config = this.config, - observer = this.observer, - typeSupported = this.typeSupported, - vendor = this.vendor; - // probe for content type - var mux; - for (var i = 0, len = muxConfig.length; i < len; i++) { - var _muxConfig$i$demux; - if ((_muxConfig$i$demux = muxConfig[i].demux) != null && _muxConfig$i$demux.probe(data)) { - mux = muxConfig[i]; - break; - } - } - if (!mux) { - return new Error('Failed to find demuxer by probing fragment data'); - } - // so let's check that current remuxer and demuxer are still valid - var demuxer = this.demuxer; - var remuxer = this.remuxer; - var Remuxer = mux.remux; - var Demuxer = mux.demux; - if (!remuxer || !(remuxer instanceof Remuxer)) { - this.remuxer = new Remuxer(observer, config, typeSupported, vendor); - } - if (!demuxer || !(demuxer instanceof Demuxer)) { - this.demuxer = new Demuxer(observer, config, typeSupported); - this.probe = Demuxer.probe; - } - }; - _proto.needsProbing = function needsProbing(discontinuity, trackSwitch) { - // in case of continuity change, or track switch - // we might switch from content type (AAC container to TS container, or TS to fmp4 for example) - return !this.demuxer || !this.remuxer || discontinuity || trackSwitch; - }; - _proto.getDecrypter = function getDecrypter() { - var decrypter = this.decrypter; - if (!decrypter) { - decrypter = this.decrypter = new Decrypter(this.config); - } - return decrypter; - }; - return Transmuxer; - }(); - function getEncryptionType(data, decryptData) { - var encryptionType = null; - if (data.byteLength > 0 && (decryptData == null ? void 0 : decryptData.key) != null && decryptData.iv !== null && decryptData.method != null) { - encryptionType = decryptData; - } - return encryptionType; - } - var emptyResult = function emptyResult(chunkMeta) { - return { - remuxResult: {}, - chunkMeta: chunkMeta - }; - }; - function isPromise(p) { - return 'then' in p && p.then instanceof Function; - } - var TransmuxConfig = function TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPts) { - this.audioCodec = void 0; - this.videoCodec = void 0; - this.initSegmentData = void 0; - this.duration = void 0; - this.defaultInitPts = void 0; - this.audioCodec = audioCodec; - this.videoCodec = videoCodec; - this.initSegmentData = initSegmentData; - this.duration = duration; - this.defaultInitPts = defaultInitPts || null; - }; - var TransmuxState = function TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange) { - this.discontinuity = void 0; - this.contiguous = void 0; - this.accurateTimeOffset = void 0; - this.trackSwitch = void 0; - this.timeOffset = void 0; - this.initSegmentChange = void 0; - this.discontinuity = discontinuity; - this.contiguous = contiguous; - this.accurateTimeOffset = accurateTimeOffset; - this.trackSwitch = trackSwitch; - this.timeOffset = timeOffset; - this.initSegmentChange = initSegmentChange; - }; - - var eventemitter3 = {exports: {}}; - - (function (module) { - - var has = Object.prototype.hasOwnProperty - , prefix = '~'; - - /** - * Constructor to create a storage for our `EE` objects. - * An `Events` instance is a plain object whose properties are event names. - * - * @constructor - * @private - */ - function Events() {} - - // - // We try to not inherit from `Object.prototype`. In some engines creating an - // instance in this way is faster than calling `Object.create(null)` directly. - // If `Object.create(null)` is not supported we prefix the event names with a - // character to make sure that the built-in object properties are not - // overridden or used as an attack vector. - // - if (Object.create) { - Events.prototype = Object.create(null); - - // - // This hack is needed because the `__proto__` property is still inherited in - // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. - // - if (!new Events().__proto__) prefix = false; - } - - /** - * Representation of a single event listener. - * - * @param {Function} fn The listener function. - * @param {*} context The context to invoke the listener with. - * @param {Boolean} [once=false] Specify if the listener is a one-time listener. - * @constructor - * @private - */ - function EE(fn, context, once) { - this.fn = fn; - this.context = context; - this.once = once || false; - } - - /** - * Add a listener for a given event. - * - * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} context The context to invoke the listener with. - * @param {Boolean} once Specify if the listener is a one-time listener. - * @returns {EventEmitter} - * @private - */ - function addListener(emitter, event, fn, context, once) { - if (typeof fn !== 'function') { - throw new TypeError('The listener must be a function'); - } - - var listener = new EE(fn, context || emitter, once) - , evt = prefix ? prefix + event : event; - - if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; - else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); - else emitter._events[evt] = [emitter._events[evt], listener]; - - return emitter; - } - - /** - * Clear event by name. - * - * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. - * @param {(String|Symbol)} evt The Event name. - * @private - */ - function clearEvent(emitter, evt) { - if (--emitter._eventsCount === 0) emitter._events = new Events(); - else delete emitter._events[evt]; - } - - /** - * Minimal `EventEmitter` interface that is molded against the Node.js - * `EventEmitter` interface. - * - * @constructor - * @public - */ - function EventEmitter() { - this._events = new Events(); - this._eventsCount = 0; - } - - /** - * Return an array listing the events for which the emitter has registered - * listeners. - * - * @returns {Array} - * @public - */ - EventEmitter.prototype.eventNames = function eventNames() { - var names = [] - , events - , name; - - if (this._eventsCount === 0) return names; - - for (name in (events = this._events)) { - if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); - } - - if (Object.getOwnPropertySymbols) { - return names.concat(Object.getOwnPropertySymbols(events)); - } - - return names; - }; - - /** - * Return the listeners registered for a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Array} The registered listeners. - * @public - */ - EventEmitter.prototype.listeners = function listeners(event) { - var evt = prefix ? prefix + event : event - , handlers = this._events[evt]; - - if (!handlers) return []; - if (handlers.fn) return [handlers.fn]; - - for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { - ee[i] = handlers[i].fn; - } - - return ee; - }; - - /** - * Return the number of listeners listening to a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Number} The number of listeners. - * @public - */ - EventEmitter.prototype.listenerCount = function listenerCount(event) { - var evt = prefix ? prefix + event : event - , listeners = this._events[evt]; - - if (!listeners) return 0; - if (listeners.fn) return 1; - return listeners.length; - }; - - /** - * Calls each of the listeners registered for a given event. - * - * @param {(String|Symbol)} event The event name. - * @returns {Boolean} `true` if the event had listeners, else `false`. - * @public - */ - EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { - var evt = prefix ? prefix + event : event; - - if (!this._events[evt]) return false; - - var listeners = this._events[evt] - , len = arguments.length - , args - , i; - - if (listeners.fn) { - if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); - - switch (len) { - case 1: return listeners.fn.call(listeners.context), true; - case 2: return listeners.fn.call(listeners.context, a1), true; - case 3: return listeners.fn.call(listeners.context, a1, a2), true; - case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; - case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; - case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; - } - - for (i = 1, args = new Array(len -1); i < len; i++) { - args[i - 1] = arguments[i]; - } - - listeners.fn.apply(listeners.context, args); - } else { - var length = listeners.length - , j; - - for (i = 0; i < length; i++) { - if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); - - switch (len) { - case 1: listeners[i].fn.call(listeners[i].context); break; - case 2: listeners[i].fn.call(listeners[i].context, a1); break; - case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; - case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; - default: - if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { - args[j - 1] = arguments[j]; - } - - listeners[i].fn.apply(listeners[i].context, args); - } - } - } - - return true; - }; - - /** - * Add a listener for a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} [context=this] The context to invoke the listener with. - * @returns {EventEmitter} `this`. - * @public - */ - EventEmitter.prototype.on = function on(event, fn, context) { - return addListener(this, event, fn, context, false); - }; - - /** - * Add a one-time listener for a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn The listener function. - * @param {*} [context=this] The context to invoke the listener with. - * @returns {EventEmitter} `this`. - * @public - */ - EventEmitter.prototype.once = function once(event, fn, context) { - return addListener(this, event, fn, context, true); - }; - - /** - * Remove the listeners of a given event. - * - * @param {(String|Symbol)} event The event name. - * @param {Function} fn Only remove the listeners that match this function. - * @param {*} context Only remove the listeners that have this context. - * @param {Boolean} once Only remove one-time listeners. - * @returns {EventEmitter} `this`. - * @public - */ - EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { - var evt = prefix ? prefix + event : event; - - if (!this._events[evt]) return this; - if (!fn) { - clearEvent(this, evt); - return this; - } - - var listeners = this._events[evt]; - - if (listeners.fn) { - if ( - listeners.fn === fn && - (!once || listeners.once) && - (!context || listeners.context === context) - ) { - clearEvent(this, evt); - } - } else { - for (var i = 0, events = [], length = listeners.length; i < length; i++) { - if ( - listeners[i].fn !== fn || - (once && !listeners[i].once) || - (context && listeners[i].context !== context) - ) { - events.push(listeners[i]); - } - } - - // - // Reset the array, or remove it completely if we have no more listeners. - // - if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; - else clearEvent(this, evt); - } - - return this; - }; - - /** - * Remove all listeners, or those of the specified event. - * - * @param {(String|Symbol)} [event] The event name. - * @returns {EventEmitter} `this`. - * @public - */ - EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { - var evt; - - if (event) { - evt = prefix ? prefix + event : event; - if (this._events[evt]) clearEvent(this, evt); - } else { - this._events = new Events(); - this._eventsCount = 0; - } - - return this; - }; - - // - // Alias methods names because people roll like that. - // - EventEmitter.prototype.off = EventEmitter.prototype.removeListener; - EventEmitter.prototype.addListener = EventEmitter.prototype.on; - - // - // Expose the prefix. - // - EventEmitter.prefixed = prefix; - - // - // Allow `EventEmitter` to be imported as module namespace. - // - EventEmitter.EventEmitter = EventEmitter; - - // - // Expose the module. - // - { - module.exports = EventEmitter; - } - } (eventemitter3)); - - var eventemitter3Exports = eventemitter3.exports; - var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports); - - if (typeof __IN_WORKER__ !== 'undefined' && __IN_WORKER__) { - startWorker(self); - } - function startWorker(self) { - var observer = new EventEmitter(); - var forwardMessage = function forwardMessage(ev, data) { - self.postMessage({ - event: ev, - data: data - }); - }; - - // forward events to main thread - observer.on(Events.FRAG_DECRYPTED, forwardMessage); - observer.on(Events.ERROR, forwardMessage); - - // forward logger events to main thread - var forwardWorkerLogs = function forwardWorkerLogs() { - var _loop = function _loop(logFn) { - var func = function func(message) { - forwardMessage('workerLog', { - logType: logFn, - message: message - }); - }; - logger[logFn] = func; - }; - for (var logFn in logger) { - _loop(logFn); - } - }; - self.addEventListener('message', function (ev) { - var data = ev.data; - switch (data.cmd) { - case 'init': - { - var config = JSON.parse(data.config); - self.transmuxer = new Transmuxer(observer, data.typeSupported, config, '', data.id); - enableLogs(config.debug, data.id); - forwardWorkerLogs(); - forwardMessage('init', null); - break; - } - case 'configure': - { - self.transmuxer.configure(data.config); - break; - } - case 'demux': - { - var transmuxResult = self.transmuxer.push(data.data, data.decryptdata, data.chunkMeta, data.state); - if (isPromise(transmuxResult)) { - self.transmuxer.async = true; - transmuxResult.then(function (data) { - emitTransmuxComplete(self, data); - }).catch(function (error) { - forwardMessage(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_PARSING_ERROR, - chunkMeta: data.chunkMeta, - fatal: false, - error: error, - err: error, - reason: "transmuxer-worker push error" - }); - }); - } else { - self.transmuxer.async = false; - emitTransmuxComplete(self, transmuxResult); - } - break; - } - case 'flush': - { - var id = data.chunkMeta; - var _transmuxResult = self.transmuxer.flush(id); - var asyncFlush = isPromise(_transmuxResult); - if (asyncFlush || self.transmuxer.async) { - if (!isPromise(_transmuxResult)) { - _transmuxResult = Promise.resolve(_transmuxResult); - } - _transmuxResult.then(function (results) { - handleFlushResult(self, results, id); - }).catch(function (error) { - forwardMessage(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_PARSING_ERROR, - chunkMeta: data.chunkMeta, - fatal: false, - error: error, - err: error, - reason: "transmuxer-worker flush error" - }); - }); - } else { - handleFlushResult(self, _transmuxResult, id); - } - break; - } - } - }); - } - function emitTransmuxComplete(self, transmuxResult) { - if (isEmptyResult(transmuxResult.remuxResult)) { - return false; - } - var transferable = []; - var _transmuxResult$remux = transmuxResult.remuxResult, - audio = _transmuxResult$remux.audio, - video = _transmuxResult$remux.video; - if (audio) { - addToTransferable(transferable, audio); - } - if (video) { - addToTransferable(transferable, video); - } - self.postMessage({ - event: 'transmuxComplete', - data: transmuxResult - }, transferable); - return true; - } - - // Converts data to a transferable object https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast) - // in order to minimize message passing overhead - function addToTransferable(transferable, track) { - if (track.data1) { - transferable.push(track.data1.buffer); - } - if (track.data2) { - transferable.push(track.data2.buffer); - } - } - function handleFlushResult(self, results, chunkMeta) { - var parsed = results.reduce(function (parsed, result) { - return emitTransmuxComplete(self, result) || parsed; - }, false); - if (!parsed) { - // Emit at least one "transmuxComplete" message even if media is not found to update stream-controller state to PARSING - self.postMessage({ - event: 'transmuxComplete', - data: results[0] - }); - } - self.postMessage({ - event: 'flush', - data: chunkMeta - }); - } - function isEmptyResult(remuxResult) { - return !remuxResult.audio && !remuxResult.video && !remuxResult.text && !remuxResult.id3 && !remuxResult.initSegment; - } - - // ensure the worker ends up in the bundle - // If the worker should not be included this gets aliased to empty.js - function hasUMDWorker() { - return typeof __HLS_WORKER_BUNDLE__ === 'function'; - } - function injectWorker() { - var blob = new self.Blob(["var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(" + __HLS_WORKER_BUNDLE__.toString() + ")(true);"], { - type: 'text/javascript' - }); - var objectURL = self.URL.createObjectURL(blob); - var worker = new self.Worker(objectURL); - return { - worker: worker, - objectURL: objectURL - }; - } - function loadWorker(path) { - var scriptURL = new self.URL(path, self.location.href).href; - var worker = new self.Worker(scriptURL); - return { - worker: worker, - scriptURL: scriptURL - }; - } - - var TransmuxerInterface = /*#__PURE__*/function () { - function TransmuxerInterface(hls, id, onTransmuxComplete, onFlush) { - var _this = this; - this.error = null; - this.hls = void 0; - this.id = void 0; - this.observer = void 0; - this.frag = null; - this.part = null; - this.useWorker = void 0; - this.workerContext = null; - this.onwmsg = void 0; - this.transmuxer = null; - this.onTransmuxComplete = void 0; - this.onFlush = void 0; - var config = hls.config; - this.hls = hls; - this.id = id; - this.useWorker = !!config.enableWorker; - this.onTransmuxComplete = onTransmuxComplete; - this.onFlush = onFlush; - var forwardMessage = function forwardMessage(ev, data) { - data = data || {}; - data.frag = _this.frag; - data.id = _this.id; - if (ev === Events.ERROR) { - _this.error = data.error; - } - _this.hls.trigger(ev, data); - }; - - // forward events to main thread - this.observer = new EventEmitter(); - this.observer.on(Events.FRAG_DECRYPTED, forwardMessage); - this.observer.on(Events.ERROR, forwardMessage); - var MediaSource = getMediaSource(config.preferManagedMediaSource) || { - isTypeSupported: function isTypeSupported() { - return false; - } - }; - var m2tsTypeSupported = { - mpeg: MediaSource.isTypeSupported('audio/mpeg'), - mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'), - ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"') - }; - if (this.useWorker && typeof Worker !== 'undefined') { - var canCreateWorker = config.workerPath || hasUMDWorker(); - if (canCreateWorker) { - try { - if (config.workerPath) { - logger.log("loading Web Worker " + config.workerPath + " for \"" + id + "\""); - this.workerContext = loadWorker(config.workerPath); - } else { - logger.log("injecting Web Worker for \"" + id + "\""); - this.workerContext = injectWorker(); - } - this.onwmsg = function (event) { - return _this.onWorkerMessage(event); - }; - var worker = this.workerContext.worker; - worker.addEventListener('message', this.onwmsg); - worker.onerror = function (event) { - var error = new Error(event.message + " (" + event.filename + ":" + event.lineno + ")"); - config.enableWorker = false; - logger.warn("Error in \"" + id + "\" Web Worker, fallback to inline"); - _this.hls.trigger(Events.ERROR, { - type: ErrorTypes.OTHER_ERROR, - details: ErrorDetails.INTERNAL_EXCEPTION, - fatal: false, - event: 'demuxerWorker', - error: error - }); - }; - worker.postMessage({ - cmd: 'init', - typeSupported: m2tsTypeSupported, - vendor: '', - id: id, - config: JSON.stringify(config) - }); - } catch (err) { - logger.warn("Error setting up \"" + id + "\" Web Worker, fallback to inline", err); - this.resetWorker(); - this.error = null; - this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id); - } - return; - } - } - this.transmuxer = new Transmuxer(this.observer, m2tsTypeSupported, config, '', id); - } - var _proto = TransmuxerInterface.prototype; - _proto.resetWorker = function resetWorker() { - if (this.workerContext) { - var _this$workerContext = this.workerContext, - worker = _this$workerContext.worker, - objectURL = _this$workerContext.objectURL; - if (objectURL) { - // revoke the Object URL that was used to create transmuxer worker, so as not to leak it - self.URL.revokeObjectURL(objectURL); - } - worker.removeEventListener('message', this.onwmsg); - worker.onerror = null; - worker.terminate(); - this.workerContext = null; - } - }; - _proto.destroy = function destroy() { - if (this.workerContext) { - this.resetWorker(); - this.onwmsg = undefined; - } else { - var transmuxer = this.transmuxer; - if (transmuxer) { - transmuxer.destroy(); - this.transmuxer = null; - } - } - var observer = this.observer; - if (observer) { - observer.removeAllListeners(); - } - this.frag = null; - // @ts-ignore - this.observer = null; - // @ts-ignore - this.hls = null; - }; - _proto.push = function push(data, initSegmentData, audioCodec, videoCodec, frag, part, duration, accurateTimeOffset, chunkMeta, defaultInitPTS) { - var _frag$initSegment, - _lastFrag$initSegment, - _this2 = this; - chunkMeta.transmuxing.start = self.performance.now(); - var transmuxer = this.transmuxer; - var timeOffset = part ? part.start : frag.start; - // TODO: push "clear-lead" decrypt data for unencrypted fragments in streams with encrypted ones - var decryptdata = frag.decryptdata; - var lastFrag = this.frag; - var discontinuity = !(lastFrag && frag.cc === lastFrag.cc); - var trackSwitch = !(lastFrag && chunkMeta.level === lastFrag.level); - var snDiff = lastFrag ? chunkMeta.sn - lastFrag.sn : -1; - var partDiff = this.part ? chunkMeta.part - this.part.index : -1; - var progressive = snDiff === 0 && chunkMeta.id > 1 && chunkMeta.id === (lastFrag == null ? void 0 : lastFrag.stats.chunkCount); - var contiguous = !trackSwitch && (snDiff === 1 || snDiff === 0 && (partDiff === 1 || progressive && partDiff <= 0)); - var now = self.performance.now(); - if (trackSwitch || snDiff || frag.stats.parsing.start === 0) { - frag.stats.parsing.start = now; - } - if (part && (partDiff || !contiguous)) { - part.stats.parsing.start = now; - } - var initSegmentChange = !(lastFrag && ((_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.url) === ((_lastFrag$initSegment = lastFrag.initSegment) == null ? void 0 : _lastFrag$initSegment.url)); - var state = new TransmuxState(discontinuity, contiguous, accurateTimeOffset, trackSwitch, timeOffset, initSegmentChange); - if (!contiguous || discontinuity || initSegmentChange) { - logger.log("[transmuxer-interface, " + frag.type + "]: Starting new transmux session for sn: " + chunkMeta.sn + " p: " + chunkMeta.part + " level: " + chunkMeta.level + " id: " + chunkMeta.id + "\n discontinuity: " + discontinuity + "\n trackSwitch: " + trackSwitch + "\n contiguous: " + contiguous + "\n accurateTimeOffset: " + accurateTimeOffset + "\n timeOffset: " + timeOffset + "\n initSegmentChange: " + initSegmentChange); - var config = new TransmuxConfig(audioCodec, videoCodec, initSegmentData, duration, defaultInitPTS); - this.configureTransmuxer(config); - } - this.frag = frag; - this.part = part; - - // Frags with sn of 'initSegment' are not transmuxed - if (this.workerContext) { - // post fragment payload as transferable objects for ArrayBuffer (no copy) - this.workerContext.worker.postMessage({ - cmd: 'demux', - data: data, - decryptdata: decryptdata, - chunkMeta: chunkMeta, - state: state - }, data instanceof ArrayBuffer ? [data] : []); - } else if (transmuxer) { - var _transmuxResult = transmuxer.push(data, decryptdata, chunkMeta, state); - if (isPromise(_transmuxResult)) { - transmuxer.async = true; - _transmuxResult.then(function (data) { - _this2.handleTransmuxComplete(data); - }).catch(function (error) { - _this2.transmuxerError(error, chunkMeta, 'transmuxer-interface push error'); - }); - } else { - transmuxer.async = false; - this.handleTransmuxComplete(_transmuxResult); - } - } - }; - _proto.flush = function flush(chunkMeta) { - var _this3 = this; - chunkMeta.transmuxing.start = self.performance.now(); - var transmuxer = this.transmuxer; - if (this.workerContext) { - this.workerContext.worker.postMessage({ - cmd: 'flush', - chunkMeta: chunkMeta - }); - } else if (transmuxer) { - var _transmuxResult2 = transmuxer.flush(chunkMeta); - var asyncFlush = isPromise(_transmuxResult2); - if (asyncFlush || transmuxer.async) { - if (!isPromise(_transmuxResult2)) { - _transmuxResult2 = Promise.resolve(_transmuxResult2); - } - _transmuxResult2.then(function (data) { - _this3.handleFlushResult(data, chunkMeta); - }).catch(function (error) { - _this3.transmuxerError(error, chunkMeta, 'transmuxer-interface flush error'); - }); - } else { - this.handleFlushResult(_transmuxResult2, chunkMeta); - } - } - }; - _proto.transmuxerError = function transmuxerError(error, chunkMeta, reason) { - if (!this.hls) { - return; - } - this.error = error; - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_PARSING_ERROR, - chunkMeta: chunkMeta, - frag: this.frag || undefined, - fatal: false, - error: error, - err: error, - reason: reason - }); - }; - _proto.handleFlushResult = function handleFlushResult(results, chunkMeta) { - var _this4 = this; - results.forEach(function (result) { - _this4.handleTransmuxComplete(result); - }); - this.onFlush(chunkMeta); - }; - _proto.onWorkerMessage = function onWorkerMessage(event) { - var data = event.data; - if (!(data != null && data.event)) { - logger.warn("worker message received with no " + (data ? 'event name' : 'data')); - return; - } - var hls = this.hls; - if (!this.hls) { - return; - } - switch (data.event) { - case 'init': - { - var _this$workerContext2; - var objectURL = (_this$workerContext2 = this.workerContext) == null ? void 0 : _this$workerContext2.objectURL; - if (objectURL) { - // revoke the Object URL that was used to create transmuxer worker, so as not to leak it - self.URL.revokeObjectURL(objectURL); - } - break; - } - case 'transmuxComplete': - { - this.handleTransmuxComplete(data.data); - break; - } - case 'flush': - { - this.onFlush(data.data); - break; - } - - // pass logs from the worker thread to the main logger - case 'workerLog': - if (logger[data.data.logType]) { - logger[data.data.logType](data.data.message); - } - break; - default: - { - data.data = data.data || {}; - data.data.frag = this.frag; - data.data.id = this.id; - hls.trigger(data.event, data.data); - break; - } - } - }; - _proto.configureTransmuxer = function configureTransmuxer(config) { - var transmuxer = this.transmuxer; - if (this.workerContext) { - this.workerContext.worker.postMessage({ - cmd: 'configure', - config: config - }); - } else if (transmuxer) { - transmuxer.configure(config); - } - }; - _proto.handleTransmuxComplete = function handleTransmuxComplete(result) { - result.chunkMeta.transmuxing.end = self.performance.now(); - this.onTransmuxComplete(result); - }; - return TransmuxerInterface; - }(); - - function subtitleOptionsIdentical(trackList1, trackList2) { - if (trackList1.length !== trackList2.length) { - return false; - } - for (var i = 0; i < trackList1.length; i++) { - if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) { - return false; - } - } - return true; - } - function mediaAttributesIdentical(attrs1, attrs2, customAttributes) { - // Media options with the same rendition ID must be bit identical - var stableRenditionId = attrs1['STABLE-RENDITION-ID']; - if (stableRenditionId && !customAttributes) { - return stableRenditionId === attrs2['STABLE-RENDITION-ID']; - } - // When rendition ID is not present, compare attributes - return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(function (subtitleAttribute) { - return attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]; - }); - } - function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) { - return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase()); - } - - var TICK_INTERVAL$2 = 100; // how often to tick in ms - var AudioStreamController = /*#__PURE__*/function (_BaseStreamController) { - _inheritsLoose(AudioStreamController, _BaseStreamController); - function AudioStreamController(hls, fragmentTracker, keyLoader) { - var _this; - _this = _BaseStreamController.call(this, hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO) || this; - _this.videoBuffer = null; - _this.videoTrackCC = -1; - _this.waitingVideoCC = -1; - _this.bufferedTrack = null; - _this.switchingTrack = null; - _this.trackId = -1; - _this.waitingData = null; - _this.mainDetails = null; - _this.flushing = false; - _this.bufferFlushed = false; - _this.cachedTrackLoadedData = null; - _this._registerListeners(); - return _this; - } - var _proto = AudioStreamController.prototype; - _proto.onHandlerDestroying = function onHandlerDestroying() { - this._unregisterListeners(); - _BaseStreamController.prototype.onHandlerDestroying.call(this); - this.mainDetails = null; - this.bufferedTrack = null; - this.switchingTrack = null; - }; - _proto._registerListeners = function _registerListeners() { - var hls = this.hls; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); - hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); - hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); - hls.on(Events.ERROR, this.onError, this); - hls.on(Events.BUFFER_RESET, this.onBufferReset, this); - hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); - hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); - hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); - hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - }; - _proto._unregisterListeners = function _unregisterListeners() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this); - hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); - hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); - hls.off(Events.ERROR, this.onError, this); - hls.off(Events.BUFFER_RESET, this.onBufferReset, this); - hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); - hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); - hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); - hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - } - - // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value - ; - _proto.onInitPtsFound = function onInitPtsFound(event, _ref) { - var frag = _ref.frag, - id = _ref.id, - initPTS = _ref.initPTS, - timescale = _ref.timescale; - // Always update the new INIT PTS - // Can change due level switch - if (id === 'main') { - var cc = frag.cc; - this.initPTS[frag.cc] = { - baseTime: initPTS, - timescale: timescale - }; - this.log("InitPTS for cc: " + cc + " found from main: " + initPTS); - this.videoTrackCC = cc; - // If we are waiting, tick immediately to unblock audio fragment transmuxing - if (this.state === State.WAITING_INIT_PTS) { - this.tick(); - } - } - }; - _proto.startLoad = function startLoad(startPosition) { - if (!this.levels) { - this.startPosition = startPosition; - this.state = State.STOPPED; - return; - } - var lastCurrentTime = this.lastCurrentTime; - this.stopLoad(); - this.setInterval(TICK_INTERVAL$2); - if (lastCurrentTime > 0 && startPosition === -1) { - this.log("Override startPosition with lastCurrentTime @" + lastCurrentTime.toFixed(3)); - startPosition = lastCurrentTime; - this.state = State.IDLE; - } else { - this.loadedmetadata = false; - this.state = State.WAITING_TRACK; - } - this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; - this.tick(); - }; - _proto.doTick = function doTick() { - switch (this.state) { - case State.IDLE: - this.doTickIdle(); - break; - case State.WAITING_TRACK: - { - var _levels$trackId; - var levels = this.levels, - trackId = this.trackId; - var details = levels == null ? void 0 : (_levels$trackId = levels[trackId]) == null ? void 0 : _levels$trackId.details; - if (details) { - if (this.waitForCdnTuneIn(details)) { - break; - } - this.state = State.WAITING_INIT_PTS; - } - break; - } - case State.FRAG_LOADING_WAITING_RETRY: - { - var _this$media; - var now = performance.now(); - var retryDate = this.retryDate; - // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading - if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) { - var _levels = this.levels, - _trackId = this.trackId; - this.log('RetryDate reached, switch back to IDLE state'); - this.resetStartWhenNotLoaded((_levels == null ? void 0 : _levels[_trackId]) || null); - this.state = State.IDLE; - } - break; - } - case State.WAITING_INIT_PTS: - { - // Ensure we don't get stuck in the WAITING_INIT_PTS state if the waiting frag CC doesn't match any initPTS - var waitingData = this.waitingData; - if (waitingData) { - var frag = waitingData.frag, - part = waitingData.part, - cache = waitingData.cache, - complete = waitingData.complete; - if (this.initPTS[frag.cc] !== undefined) { - this.waitingData = null; - this.waitingVideoCC = -1; - this.state = State.FRAG_LOADING; - var payload = cache.flush(); - var data = { - frag: frag, - part: part, - payload: payload, - networkDetails: null - }; - this._handleFragmentLoadProgress(data); - if (complete) { - _BaseStreamController.prototype._handleFragmentLoadComplete.call(this, data); - } - } else if (this.videoTrackCC !== this.waitingVideoCC) { - // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found - this.log("Waiting fragment cc (" + frag.cc + ") cancelled because video is at cc " + this.videoTrackCC); - this.clearWaitingFragment(); - } else { - // Drop waiting fragment if an earlier fragment is needed - var pos = this.getLoadPosition(); - var bufferInfo = BufferHelper.bufferInfo(this.mediaBuffer, pos, this.config.maxBufferHole); - var waitingFragmentAtPosition = fragmentWithinToleranceTest(bufferInfo.end, this.config.maxFragLookUpTolerance, frag); - if (waitingFragmentAtPosition < 0) { - this.log("Waiting fragment cc (" + frag.cc + ") @ " + frag.start + " cancelled because another fragment at " + bufferInfo.end + " is needed"); - this.clearWaitingFragment(); - } - } - } else { - this.state = State.IDLE; - } - } - } - this.onTickEnd(); - }; - _proto.clearWaitingFragment = function clearWaitingFragment() { - var waitingData = this.waitingData; - if (waitingData) { - this.fragmentTracker.removeFragment(waitingData.frag); - this.waitingData = null; - this.waitingVideoCC = -1; - this.state = State.IDLE; - } - }; - _proto.resetLoadingState = function resetLoadingState() { - this.clearWaitingFragment(); - _BaseStreamController.prototype.resetLoadingState.call(this); - }; - _proto.onTickEnd = function onTickEnd() { - var media = this.media; - if (!(media != null && media.readyState)) { - // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0) - return; - } - this.lastCurrentTime = media.currentTime; - }; - _proto.doTickIdle = function doTickIdle() { - var hls = this.hls, - levels = this.levels, - media = this.media, - trackId = this.trackId; - var config = hls.config; - - // 1. if video not attached AND - // start fragment already requested OR start frag prefetch not enabled - // 2. if tracks or track not loaded and selected - // then exit loop - // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop - if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) { - return; - } - var levelInfo = levels[trackId]; - var trackDetails = levelInfo.details; - if (!trackDetails || trackDetails.live && this.levelLastLoaded !== levelInfo || this.waitForCdnTuneIn(trackDetails)) { - this.state = State.WAITING_TRACK; - return; - } - var bufferable = this.mediaBuffer ? this.mediaBuffer : this.media; - if (this.bufferFlushed && bufferable) { - this.bufferFlushed = false; - this.afterBufferFlushed(bufferable, ElementaryStreamTypes.AUDIO, PlaylistLevelType.AUDIO); - } - var bufferInfo = this.getFwdBufferInfo(bufferable, PlaylistLevelType.AUDIO); - if (bufferInfo === null) { - return; - } - var bufferedTrack = this.bufferedTrack, - switchingTrack = this.switchingTrack; - if (!switchingTrack && this._streamEnded(bufferInfo, trackDetails)) { - hls.trigger(Events.BUFFER_EOS, { - type: 'audio' - }); - this.state = State.ENDED; - return; - } - var mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN); - var bufferLen = bufferInfo.len; - var maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len); - var fragments = trackDetails.fragments; - var start = fragments[0].start; - var targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end; - if (switchingTrack && media) { - var pos = this.getLoadPosition(); - // STABLE - if (bufferedTrack && !mediaAttributesIdentical(switchingTrack.attrs, bufferedTrack.attrs)) { - targetBufferTime = pos; - } - // if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime - if (trackDetails.PTSKnown && pos < start) { - // if everything is buffered from pos to start or if audio buffer upfront, let's seek to start - if (bufferInfo.end > start || bufferInfo.nextStart) { - this.log('Alt audio track ahead of main track, seek to start of alt audio track'); - media.currentTime = start + 0.05; - } - } - } - - // if buffer length is less than maxBufLen, or near the end, find a fragment to load - if (bufferLen >= maxBufLen && !switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) { - return; - } - var frag = this.getNextFragment(targetBufferTime, trackDetails); - var atGap = false; - // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags - if (frag && this.isLoopLoading(frag, targetBufferTime)) { - atGap = !!frag.gap; - frag = this.getNextFragmentLoopLoading(frag, trackDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen); - } - if (!frag) { - this.bufferFlushed = true; - return; - } - - // Buffer audio up to one target duration ahead of main buffer - var atBufferSyncLimit = mainBufferInfo && frag.start > mainBufferInfo.end + trackDetails.targetduration; - if (atBufferSyncLimit || - // Or wait for main buffer after buffing some audio - !(mainBufferInfo != null && mainBufferInfo.len) && bufferInfo.len) { - // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo - var mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN); - if (mainFrag === null) { - return; - } - // Bridge gaps in main buffer - atGap || (atGap = !!mainFrag.gap || !!atBufferSyncLimit && mainBufferInfo.len === 0); - if (atBufferSyncLimit && !atGap || atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) { - return; - } - } - this.loadFragment(frag, levelInfo, targetBufferTime); - }; - _proto.getMaxBufferLength = function getMaxBufferLength(mainBufferLength) { - var maxConfigBuffer = _BaseStreamController.prototype.getMaxBufferLength.call(this); - if (!mainBufferLength) { - return maxConfigBuffer; - } - return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength); - }; - _proto.onMediaDetaching = function onMediaDetaching() { - this.videoBuffer = null; - this.bufferFlushed = this.flushing = false; - _BaseStreamController.prototype.onMediaDetaching.call(this); - }; - _proto.onAudioTracksUpdated = function onAudioTracksUpdated(event, _ref2) { - var audioTracks = _ref2.audioTracks; - // Reset tranxmuxer is essential for large context switches (Content Steering) - this.resetTransmuxer(); - this.levels = audioTracks.map(function (mediaPlaylist) { - return new Level(mediaPlaylist); - }); - }; - _proto.onAudioTrackSwitching = function onAudioTrackSwitching(event, data) { - // if any URL found on new audio track, it is an alternate audio track - var altAudio = !!data.url; - this.trackId = data.id; - var fragCurrent = this.fragCurrent; - if (fragCurrent) { - fragCurrent.abortRequests(); - this.removeUnbufferedFrags(fragCurrent.start); - } - this.resetLoadingState(); - // destroy useless transmuxer when switching audio to main - if (!altAudio) { - this.resetTransmuxer(); - } else { - // switching to audio track, start timer if not already started - this.setInterval(TICK_INTERVAL$2); - } - - // should we switch tracks ? - if (altAudio) { - this.switchingTrack = data; - // main audio track are handled by stream-controller, just do something if switching to alt audio track - this.state = State.IDLE; - this.flushAudioIfNeeded(data); - } else { - this.switchingTrack = null; - this.bufferedTrack = data; - this.state = State.STOPPED; - } - this.tick(); - }; - _proto.onManifestLoading = function onManifestLoading() { - this.fragmentTracker.removeAllFragments(); - this.startPosition = this.lastCurrentTime = 0; - this.bufferFlushed = this.flushing = false; - this.levels = this.mainDetails = this.waitingData = this.bufferedTrack = this.cachedTrackLoadedData = this.switchingTrack = null; - this.startFragRequested = false; - this.trackId = this.videoTrackCC = this.waitingVideoCC = -1; - }; - _proto.onLevelLoaded = function onLevelLoaded(event, data) { - this.mainDetails = data.details; - if (this.cachedTrackLoadedData !== null) { - this.hls.trigger(Events.AUDIO_TRACK_LOADED, this.cachedTrackLoadedData); - this.cachedTrackLoadedData = null; - } - }; - _proto.onAudioTrackLoaded = function onAudioTrackLoaded(event, data) { - var _track$details; - if (this.mainDetails == null) { - this.cachedTrackLoadedData = data; - return; - } - var levels = this.levels; - var newDetails = data.details, - trackId = data.id; - if (!levels) { - this.warn("Audio tracks were reset while loading level " + trackId); - return; - } - this.log("Audio track " + trackId + " loaded [" + newDetails.startSN + "," + newDetails.endSN + "]" + (newDetails.lastPartSn ? "[part-" + newDetails.lastPartSn + "-" + newDetails.lastPartIndex + "]" : '') + ",duration:" + newDetails.totalduration); - var track = levels[trackId]; - var sliding = 0; - if (newDetails.live || (_track$details = track.details) != null && _track$details.live) { - this.checkLiveUpdate(newDetails); - var mainDetails = this.mainDetails; - if (newDetails.deltaUpdateFailed || !mainDetails) { - return; - } - if (!track.details && newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) { - // Make sure our audio rendition is aligned with the "main" rendition, using - // pdt as our reference times. - alignMediaPlaylistByPDT(newDetails, mainDetails); - sliding = newDetails.fragments[0].start; - } else { - var _this$levelLastLoaded; - sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details); - } - } - track.details = newDetails; - this.levelLastLoaded = track; - - // compute start position if we are aligned with the main playlist - if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) { - this.setStartPosition(this.mainDetails || newDetails, sliding); - } - // only switch back to IDLE state if we were waiting for track to start downloading a new fragment - if (this.state === State.WAITING_TRACK && !this.waitForCdnTuneIn(newDetails)) { - this.state = State.IDLE; - } - - // trigger handler right now - this.tick(); - }; - _proto._handleFragmentLoadProgress = function _handleFragmentLoadProgress(data) { - var _frag$initSegment; - var frag = data.frag, - part = data.part, - payload = data.payload; - var config = this.config, - trackId = this.trackId, - levels = this.levels; - if (!levels) { - this.warn("Audio tracks were reset while fragment load was in progress. Fragment " + frag.sn + " of level " + frag.level + " will not be buffered"); - return; - } - var track = levels[trackId]; - if (!track) { - this.warn('Audio track is undefined on fragment load progress'); - return; - } - var details = track.details; - if (!details) { - this.warn('Audio track details undefined on fragment load progress'); - this.removeUnbufferedFrags(frag.start); - return; - } - var audioCodec = config.defaultAudioCodec || track.audioCodec || 'mp4a.40.2'; - var transmuxer = this.transmuxer; - if (!transmuxer) { - transmuxer = this.transmuxer = new TransmuxerInterface(this.hls, PlaylistLevelType.AUDIO, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)); - } - - // Check if we have video initPTS - // If not we need to wait for it - var initPTS = this.initPTS[frag.cc]; - var initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data; - if (initPTS !== undefined) { - // this.log(`Transmuxing ${sn} of [${details.startSN} ,${details.endSN}],track ${trackId}`); - // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) - var accurateTimeOffset = false; // details.PTSKnown || !details.live; - var partIndex = part ? part.index : -1; - var partial = partIndex !== -1; - var chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial); - transmuxer.push(payload, initSegmentData, audioCodec, '', frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS); - } else { - this.log("Unknown video PTS for cc " + frag.cc + ", waiting for video PTS before demuxing audio frag " + frag.sn + " of [" + details.startSN + " ," + details.endSN + "],track " + trackId); - var _this$waitingData = this.waitingData = this.waitingData || { - frag: frag, - part: part, - cache: new ChunkCache(), - complete: false - }, - cache = _this$waitingData.cache; - cache.push(new Uint8Array(payload)); - this.waitingVideoCC = this.videoTrackCC; - this.state = State.WAITING_INIT_PTS; - } - }; - _proto._handleFragmentLoadComplete = function _handleFragmentLoadComplete(fragLoadedData) { - if (this.waitingData) { - this.waitingData.complete = true; - return; - } - _BaseStreamController.prototype._handleFragmentLoadComplete.call(this, fragLoadedData); - }; - _proto.onBufferReset = function onBufferReset( /* event: Events.BUFFER_RESET */ - ) { - // reset reference to sourcebuffers - this.mediaBuffer = this.videoBuffer = null; - this.loadedmetadata = false; - }; - _proto.onBufferCreated = function onBufferCreated(event, data) { - var audioTrack = data.tracks.audio; - if (audioTrack) { - this.mediaBuffer = audioTrack.buffer || null; - } - if (data.tracks.video) { - this.videoBuffer = data.tracks.video.buffer || null; - } - }; - _proto.onFragBuffered = function onFragBuffered(event, data) { - var frag = data.frag, - part = data.part; - if (frag.type !== PlaylistLevelType.AUDIO) { - if (!this.loadedmetadata && frag.type === PlaylistLevelType.MAIN) { - var bufferable = this.videoBuffer || this.media; - if (bufferable) { - var bufferedTimeRanges = BufferHelper.getBuffered(bufferable); - if (bufferedTimeRanges.length) { - this.loadedmetadata = true; - } - } - } - return; - } - if (this.fragContextChanged(frag)) { - // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion - // Avoid setting state back to IDLE or concluding the audio switch; otherwise, the switched-to track will not buffer - this.warn("Fragment " + frag.sn + (part ? ' p: ' + part.index : '') + " of level " + frag.level + " finished buffering, but was aborted. state: " + this.state + ", audioSwitch: " + (this.switchingTrack ? this.switchingTrack.name : 'false')); - return; - } - if (frag.sn !== 'initSegment') { - this.fragPrevious = frag; - var track = this.switchingTrack; - if (track) { - this.bufferedTrack = track; - this.switchingTrack = null; - this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, track)); - } - } - this.fragBufferedComplete(frag, part); - }; - _proto.onError = function onError(event, data) { - var _data$context; - if (data.fatal) { - this.state = State.ERROR; - return; - } - switch (data.details) { - case ErrorDetails.FRAG_GAP: - case ErrorDetails.FRAG_PARSING_ERROR: - case ErrorDetails.FRAG_DECRYPT_ERROR: - case ErrorDetails.FRAG_LOAD_ERROR: - case ErrorDetails.FRAG_LOAD_TIMEOUT: - case ErrorDetails.KEY_LOAD_ERROR: - case ErrorDetails.KEY_LOAD_TIMEOUT: - this.onFragmentOrKeyLoadError(PlaylistLevelType.AUDIO, data); - break; - case ErrorDetails.AUDIO_TRACK_LOAD_ERROR: - case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT: - case ErrorDetails.LEVEL_PARSING_ERROR: - // in case of non fatal error while loading track, if not retrying to load track, switch back to IDLE - if (!data.levelRetry && this.state === State.WAITING_TRACK && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.AUDIO_TRACK) { - this.state = State.IDLE; - } - break; - case ErrorDetails.BUFFER_APPEND_ERROR: - case ErrorDetails.BUFFER_FULL_ERROR: - if (!data.parent || data.parent !== 'audio') { - return; - } - if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) { - this.resetLoadingState(); - return; - } - if (this.reduceLengthAndFlushBuffer(data)) { - this.bufferedTrack = null; - _BaseStreamController.prototype.flushMainBuffer.call(this, 0, Number.POSITIVE_INFINITY, 'audio'); - } - break; - case ErrorDetails.INTERNAL_EXCEPTION: - this.recoverWorkerError(data); - break; - } - }; - _proto.onBufferFlushing = function onBufferFlushing(event, _ref3) { - var type = _ref3.type; - if (type !== ElementaryStreamTypes.VIDEO) { - this.flushing = true; - } - }; - _proto.onBufferFlushed = function onBufferFlushed(event, _ref4) { - var type = _ref4.type; - if (type !== ElementaryStreamTypes.VIDEO) { - this.flushing = false; - this.bufferFlushed = true; - if (this.state === State.ENDED) { - this.state = State.IDLE; - } - var mediaBuffer = this.mediaBuffer || this.media; - if (mediaBuffer) { - this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.AUDIO); - this.tick(); - } - } - }; - _proto._handleTransmuxComplete = function _handleTransmuxComplete(transmuxResult) { - var _id3$samples; - var id = 'audio'; - var hls = this.hls; - var remuxResult = transmuxResult.remuxResult, - chunkMeta = transmuxResult.chunkMeta; - var context = this.getCurrentContext(chunkMeta); - if (!context) { - this.resetWhenMissingContext(chunkMeta); - return; - } - var frag = context.frag, - part = context.part, - level = context.level; - var details = level.details; - var audio = remuxResult.audio, - text = remuxResult.text, - id3 = remuxResult.id3, - initSegment = remuxResult.initSegment; - - // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level. - // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed. - if (this.fragContextChanged(frag) || !details) { - this.fragmentTracker.removeFragment(frag); - return; - } - this.state = State.PARSING; - if (this.switchingTrack && audio) { - this.completeAudioSwitch(this.switchingTrack); - } - if (initSegment != null && initSegment.tracks) { - var mapFragment = frag.initSegment || frag; - this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta); - hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { - frag: mapFragment, - id: id, - tracks: initSegment.tracks - }); - // Only flush audio from old audio tracks when PTS is known on new audio track - } - if (audio) { - var startPTS = audio.startPTS, - endPTS = audio.endPTS, - startDTS = audio.startDTS, - endDTS = audio.endDTS; - if (part) { - part.elementaryStreams[ElementaryStreamTypes.AUDIO] = { - startPTS: startPTS, - endPTS: endPTS, - startDTS: startDTS, - endDTS: endDTS - }; - } - frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, startPTS, endPTS, startDTS, endDTS); - this.bufferFragmentData(audio, frag, part, chunkMeta); - } - if (id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) { - var emittedID3 = _extends({ - id: id, - frag: frag, - details: details - }, id3); - hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3); - } - if (text) { - var emittedText = _extends({ - id: id, - frag: frag, - details: details - }, text); - hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText); - } - }; - _proto._bufferInitSegment = function _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) { - if (this.state !== State.PARSING) { - return; - } - // delete any video track found on audio transmuxer - if (tracks.video) { - delete tracks.video; - } - - // include levelCodec in audio and video tracks - var track = tracks.audio; - if (!track) { - return; - } - track.id = 'audio'; - var variantAudioCodecs = currentLevel.audioCodec; - this.log("Init audio buffer, container:" + track.container + ", codecs[level/parsed]=[" + variantAudioCodecs + "/" + track.codec + "]"); - // SourceBuffer will use track.levelCodec if defined - if (variantAudioCodecs && variantAudioCodecs.split(',').length === 1) { - track.levelCodec = variantAudioCodecs; - } - this.hls.trigger(Events.BUFFER_CODECS, tracks); - var initSegment = track.initSegment; - if (initSegment != null && initSegment.byteLength) { - var segment = { - type: 'audio', - frag: frag, - part: null, - chunkMeta: chunkMeta, - parent: frag.type, - data: initSegment - }; - this.hls.trigger(Events.BUFFER_APPENDING, segment); - } - // trigger handler right now - this.tickImmediate(); - }; - _proto.loadFragment = function loadFragment(frag, track, targetBufferTime) { - // only load if fragment is not loaded or if in audio switch - var fragState = this.fragmentTracker.getState(frag); - this.fragCurrent = frag; - - // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch - if (this.switchingTrack || fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) { - var _track$details2; - if (frag.sn === 'initSegment') { - this._loadInitSegment(frag, track); - } else if ((_track$details2 = track.details) != null && _track$details2.live && !this.initPTS[frag.cc]) { - this.log("Waiting for video PTS in continuity counter " + frag.cc + " of live stream before loading audio fragment " + frag.sn + " of level " + this.trackId); - this.state = State.WAITING_INIT_PTS; - var mainDetails = this.mainDetails; - if (mainDetails && mainDetails.fragments[0].start !== track.details.fragments[0].start) { - alignMediaPlaylistByPDT(track.details, mainDetails); - } - } else { - this.startFragRequested = true; - _BaseStreamController.prototype.loadFragment.call(this, frag, track, targetBufferTime); - } - } else { - this.clearTrackerIfNeeded(frag); - } - }; - _proto.flushAudioIfNeeded = function flushAudioIfNeeded(switchingTrack) { - var media = this.media, - bufferedTrack = this.bufferedTrack; - var bufferedAttributes = bufferedTrack == null ? void 0 : bufferedTrack.attrs; - var switchAttributes = switchingTrack.attrs; - if (media && bufferedAttributes && (bufferedAttributes.CHANNELS !== switchAttributes.CHANNELS || bufferedTrack.name !== switchingTrack.name || bufferedTrack.lang !== switchingTrack.lang)) { - this.log('Switching audio track : flushing all audio'); - _BaseStreamController.prototype.flushMainBuffer.call(this, 0, Number.POSITIVE_INFINITY, 'audio'); - this.bufferedTrack = null; - } - }; - _proto.completeAudioSwitch = function completeAudioSwitch(switchingTrack) { - var hls = this.hls; - this.flushAudioIfNeeded(switchingTrack); - this.bufferedTrack = switchingTrack; - this.switchingTrack = null; - hls.trigger(Events.AUDIO_TRACK_SWITCHED, _objectSpread2({}, switchingTrack)); - }; - return AudioStreamController; - }(BaseStreamController); - - var AudioTrackController = /*#__PURE__*/function (_BasePlaylistControll) { - _inheritsLoose(AudioTrackController, _BasePlaylistControll); - function AudioTrackController(hls) { - var _this; - _this = _BasePlaylistControll.call(this, hls, '[audio-track-controller]') || this; - _this.tracks = []; - _this.groupIds = null; - _this.tracksInGroup = []; - _this.trackId = -1; - _this.currentTrack = null; - _this.selectDefaultTrack = true; - _this.registerListeners(); - return _this; - } - var _proto = AudioTrackController.prototype; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); - hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); - hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); - hls.on(Events.ERROR, this.onError, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); - hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); - hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this); - hls.off(Events.ERROR, this.onError, this); - }; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.tracks.length = 0; - this.tracksInGroup.length = 0; - this.currentTrack = null; - _BasePlaylistControll.prototype.destroy.call(this); - }; - _proto.onManifestLoading = function onManifestLoading() { - this.tracks = []; - this.tracksInGroup = []; - this.groupIds = null; - this.currentTrack = null; - this.trackId = -1; - this.selectDefaultTrack = true; - }; - _proto.onManifestParsed = function onManifestParsed(event, data) { - this.tracks = data.audioTracks || []; - }; - _proto.onAudioTrackLoaded = function onAudioTrackLoaded(event, data) { - var id = data.id, - groupId = data.groupId, - details = data.details; - var trackInActiveGroup = this.tracksInGroup[id]; - if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) { - this.warn("Audio track with id:" + id + " and group:" + groupId + " not found in active group " + (trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId)); - return; - } - var curDetails = trackInActiveGroup.details; - trackInActiveGroup.details = data.details; - this.log("Audio track " + id + " \"" + trackInActiveGroup.name + "\" lang:" + trackInActiveGroup.lang + " group:" + groupId + " loaded [" + details.startSN + "-" + details.endSN + "]"); - if (id === this.trackId) { - this.playlistLoaded(id, data, curDetails); - } - }; - _proto.onLevelLoading = function onLevelLoading(event, data) { - this.switchLevel(data.level); - }; - _proto.onLevelSwitching = function onLevelSwitching(event, data) { - this.switchLevel(data.level); - }; - _proto.switchLevel = function switchLevel(levelIndex) { - var levelInfo = this.hls.levels[levelIndex]; - if (!levelInfo) { - return; - } - var audioGroups = levelInfo.audioGroups || null; - var currentGroups = this.groupIds; - var currentTrack = this.currentTrack; - if (!audioGroups || (currentGroups == null ? void 0 : currentGroups.length) !== (audioGroups == null ? void 0 : audioGroups.length) || audioGroups != null && audioGroups.some(function (groupId) { - return (currentGroups == null ? void 0 : currentGroups.indexOf(groupId)) === -1; - })) { - this.groupIds = audioGroups; - this.trackId = -1; - this.currentTrack = null; - var audioTracks = this.tracks.filter(function (track) { - return !audioGroups || audioGroups.indexOf(track.groupId) !== -1; - }); - if (audioTracks.length) { - // Disable selectDefaultTrack if there are no default tracks - if (this.selectDefaultTrack && !audioTracks.some(function (track) { - return track.default; - })) { - this.selectDefaultTrack = false; - } - // track.id should match hls.audioTracks index - audioTracks.forEach(function (track, i) { - track.id = i; - }); - } else if (!currentTrack && !this.tracksInGroup.length) { - // Do not dispatch AUDIO_TRACKS_UPDATED when there were and are no tracks - return; - } - this.tracksInGroup = audioTracks; - - // Find preferred track - var audioPreference = this.hls.config.audioPreference; - if (!currentTrack && audioPreference) { - var groupIndex = findMatchingOption(audioPreference, audioTracks, audioMatchPredicate); - if (groupIndex > -1) { - currentTrack = audioTracks[groupIndex]; - } else { - var allIndex = findMatchingOption(audioPreference, this.tracks); - currentTrack = this.tracks[allIndex]; - } - } - - // Select initial track - var trackId = this.findTrackId(currentTrack); - if (trackId === -1 && currentTrack) { - trackId = this.findTrackId(null); - } - - // Dispatch events and load track if needed - var audioTracksUpdated = { - audioTracks: audioTracks - }; - this.log("Updating audio tracks, " + audioTracks.length + " track(s) found in group(s): " + (audioGroups == null ? void 0 : audioGroups.join(','))); - this.hls.trigger(Events.AUDIO_TRACKS_UPDATED, audioTracksUpdated); - var selectedTrackId = this.trackId; - if (trackId !== -1 && selectedTrackId === -1) { - this.setAudioTrack(trackId); - } else if (audioTracks.length && selectedTrackId === -1) { - var _this$groupIds; - var error = new Error("No audio track selected for current audio group-ID(s): " + ((_this$groupIds = this.groupIds) == null ? void 0 : _this$groupIds.join(',')) + " track count: " + audioTracks.length); - this.warn(error.message); - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.AUDIO_TRACK_LOAD_ERROR, - fatal: true, - error: error - }); - } - } else if (this.shouldReloadPlaylist(currentTrack)) { - // Retry playlist loading if no playlist is or has been loaded yet - this.setAudioTrack(this.trackId); - } - }; - _proto.onError = function onError(event, data) { - if (data.fatal || !data.context) { - return; - } - if (data.context.type === PlaylistContextType.AUDIO_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) { - this.requestScheduled = -1; - this.checkRetry(data); - } - }; - _proto.setAudioOption = function setAudioOption(audioOption) { - var hls = this.hls; - hls.config.audioPreference = audioOption; - if (audioOption) { - var allAudioTracks = this.allAudioTracks; - this.selectDefaultTrack = false; - if (allAudioTracks.length) { - // First see if current option matches (no switch op) - var currentTrack = this.currentTrack; - if (currentTrack && matchesOption(audioOption, currentTrack, audioMatchPredicate)) { - return currentTrack; - } - // Find option in available tracks (tracksInGroup) - var groupIndex = findMatchingOption(audioOption, this.tracksInGroup, audioMatchPredicate); - if (groupIndex > -1) { - var track = this.tracksInGroup[groupIndex]; - this.setAudioTrack(groupIndex); - return track; - } else if (currentTrack) { - // Find option in nearest level audio group - var searchIndex = hls.loadLevel; - if (searchIndex === -1) { - searchIndex = hls.firstAutoLevel; - } - var switchIndex = findClosestLevelWithAudioGroup(audioOption, hls.levels, allAudioTracks, searchIndex, audioMatchPredicate); - if (switchIndex === -1) { - // could not find matching variant - return null; - } - // and switch level to acheive the audio group switch - hls.nextLoadLevel = switchIndex; - } - if (audioOption.channels || audioOption.audioCodec) { - // Could not find a match with codec / channels predicate - // Find a match without channels or codec - var withoutCodecAndChannelsMatch = findMatchingOption(audioOption, allAudioTracks); - if (withoutCodecAndChannelsMatch > -1) { - return allAudioTracks[withoutCodecAndChannelsMatch]; - } - } - } - } - return null; - }; - _proto.setAudioTrack = function setAudioTrack(newId) { - var tracks = this.tracksInGroup; - - // check if level idx is valid - if (newId < 0 || newId >= tracks.length) { - this.warn("Invalid audio track id: " + newId); - return; - } - - // stopping live reloading timer if any - this.clearTimer(); - this.selectDefaultTrack = false; - var lastTrack = this.currentTrack; - var track = tracks[newId]; - var trackLoaded = track.details && !track.details.live; - if (newId === this.trackId && track === lastTrack && trackLoaded) { - return; - } - this.log("Switching to audio-track " + newId + " \"" + track.name + "\" lang:" + track.lang + " group:" + track.groupId + " channels:" + track.channels); - this.trackId = newId; - this.currentTrack = track; - this.hls.trigger(Events.AUDIO_TRACK_SWITCHING, _objectSpread2({}, track)); - // Do not reload track unless live - if (trackLoaded) { - return; - } - var hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details); - this.loadPlaylist(hlsUrlParameters); - }; - _proto.findTrackId = function findTrackId(currentTrack) { - var audioTracks = this.tracksInGroup; - for (var i = 0; i < audioTracks.length; i++) { - var track = audioTracks[i]; - if (this.selectDefaultTrack && !track.default) { - continue; - } - if (!currentTrack || matchesOption(currentTrack, track, audioMatchPredicate)) { - return i; - } - } - if (currentTrack) { - var name = currentTrack.name, - lang = currentTrack.lang, - assocLang = currentTrack.assocLang, - characteristics = currentTrack.characteristics, - audioCodec = currentTrack.audioCodec, - channels = currentTrack.channels; - for (var _i = 0; _i < audioTracks.length; _i++) { - var _track = audioTracks[_i]; - if (matchesOption({ - name: name, - lang: lang, - assocLang: assocLang, - characteristics: characteristics, - audioCodec: audioCodec, - channels: channels - }, _track, audioMatchPredicate)) { - return _i; - } - } - for (var _i2 = 0; _i2 < audioTracks.length; _i2++) { - var _track2 = audioTracks[_i2]; - if (mediaAttributesIdentical(currentTrack.attrs, _track2.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) { - return _i2; - } - } - for (var _i3 = 0; _i3 < audioTracks.length; _i3++) { - var _track3 = audioTracks[_i3]; - if (mediaAttributesIdentical(currentTrack.attrs, _track3.attrs, ['LANGUAGE'])) { - return _i3; - } - } - } - return -1; - }; - _proto.loadPlaylist = function loadPlaylist(hlsUrlParameters) { - var audioTrack = this.currentTrack; - if (this.shouldLoadPlaylist(audioTrack) && audioTrack) { - _BasePlaylistControll.prototype.loadPlaylist.call(this); - var id = audioTrack.id; - var groupId = audioTrack.groupId; - var url = audioTrack.url; - if (hlsUrlParameters) { - try { - url = hlsUrlParameters.addDirectives(url); - } catch (error) { - this.warn("Could not construct new URL with HLS Delivery Directives: " + error); - } - } - // track not retrieved yet, or live playlist we need to (re)load it - this.log("loading audio-track playlist " + id + " \"" + audioTrack.name + "\" lang:" + audioTrack.lang + " group:" + groupId); - this.clearTimer(); - this.hls.trigger(Events.AUDIO_TRACK_LOADING, { - url: url, - id: id, - groupId: groupId, - deliveryDirectives: hlsUrlParameters || null - }); - } - }; - _createClass(AudioTrackController, [{ - key: "allAudioTracks", - get: function get() { - return this.tracks; - } - }, { - key: "audioTracks", - get: function get() { - return this.tracksInGroup; - } - }, { - key: "audioTrack", - get: function get() { - return this.trackId; - }, - set: function set(newId) { - // If audio track is selected from API then don't choose from the manifest default track - this.selectDefaultTrack = false; - this.setAudioTrack(newId); - } - }]); - return AudioTrackController; - }(BasePlaylistController); - - var TICK_INTERVAL$1 = 500; // how often to tick in ms - - var SubtitleStreamController = /*#__PURE__*/function (_BaseStreamController) { - _inheritsLoose(SubtitleStreamController, _BaseStreamController); - function SubtitleStreamController(hls, fragmentTracker, keyLoader) { - var _this; - _this = _BaseStreamController.call(this, hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE) || this; - _this.currentTrackId = -1; - _this.tracksBuffered = []; - _this.mainDetails = null; - _this._registerListeners(); - return _this; - } - var _proto = SubtitleStreamController.prototype; - _proto.onHandlerDestroying = function onHandlerDestroying() { - this._unregisterListeners(); - _BaseStreamController.prototype.onHandlerDestroying.call(this); - this.mainDetails = null; - }; - _proto._registerListeners = function _registerListeners() { - var hls = this.hls; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.on(Events.ERROR, this.onError, this); - hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); - hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); - hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); - hls.on(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); - hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - }; - _proto._unregisterListeners = function _unregisterListeners() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.off(Events.ERROR, this.onError, this); - hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); - hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); - hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); - hls.off(Events.SUBTITLE_FRAG_PROCESSED, this.onSubtitleFragProcessed, this); - hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - }; - _proto.startLoad = function startLoad(startPosition) { - this.stopLoad(); - this.state = State.IDLE; - this.setInterval(TICK_INTERVAL$1); - this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; - this.tick(); - }; - _proto.onManifestLoading = function onManifestLoading() { - this.mainDetails = null; - this.fragmentTracker.removeAllFragments(); - }; - _proto.onMediaDetaching = function onMediaDetaching() { - this.tracksBuffered = []; - _BaseStreamController.prototype.onMediaDetaching.call(this); - }; - _proto.onLevelLoaded = function onLevelLoaded(event, data) { - this.mainDetails = data.details; - }; - _proto.onSubtitleFragProcessed = function onSubtitleFragProcessed(event, data) { - var frag = data.frag, - success = data.success; - this.fragPrevious = frag; - this.state = State.IDLE; - if (!success) { - return; - } - var buffered = this.tracksBuffered[this.currentTrackId]; - if (!buffered) { - return; - } - - // Create/update a buffered array matching the interface used by BufferHelper.bufferedInfo - // so we can re-use the logic used to detect how much has been buffered - var timeRange; - var fragStart = frag.start; - for (var i = 0; i < buffered.length; i++) { - if (fragStart >= buffered[i].start && fragStart <= buffered[i].end) { - timeRange = buffered[i]; - break; - } - } - var fragEnd = frag.start + frag.duration; - if (timeRange) { - timeRange.end = fragEnd; - } else { - timeRange = { - start: fragStart, - end: fragEnd - }; - buffered.push(timeRange); - } - this.fragmentTracker.fragBuffered(frag); - this.fragBufferedComplete(frag, null); - }; - _proto.onBufferFlushing = function onBufferFlushing(event, data) { - var startOffset = data.startOffset, - endOffset = data.endOffset; - if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) { - var endOffsetSubtitles = endOffset - 1; - if (endOffsetSubtitles <= 0) { - return; - } - data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles); - this.tracksBuffered.forEach(function (buffered) { - for (var i = 0; i < buffered.length;) { - if (buffered[i].end <= endOffsetSubtitles) { - buffered.shift(); - continue; - } else if (buffered[i].start < endOffsetSubtitles) { - buffered[i].start = endOffsetSubtitles; - } else { - break; - } - i++; - } - }); - this.fragmentTracker.removeFragmentsInRange(startOffset, endOffsetSubtitles, PlaylistLevelType.SUBTITLE); - } - }; - _proto.onFragBuffered = function onFragBuffered(event, data) { - if (!this.loadedmetadata && data.frag.type === PlaylistLevelType.MAIN) { - var _this$media; - if ((_this$media = this.media) != null && _this$media.buffered.length) { - this.loadedmetadata = true; - } - } - } - - // If something goes wrong, proceed to next frag, if we were processing one. - ; - _proto.onError = function onError(event, data) { - var frag = data.frag; - if ((frag == null ? void 0 : frag.type) === PlaylistLevelType.SUBTITLE) { - if (data.details === ErrorDetails.FRAG_GAP) { - this.fragmentTracker.fragBuffered(frag, true); - } - if (this.fragCurrent) { - this.fragCurrent.abortRequests(); - } - if (this.state !== State.STOPPED) { - this.state = State.IDLE; - } - } - } - - // Got all new subtitle levels. - ; - _proto.onSubtitleTracksUpdated = function onSubtitleTracksUpdated(event, _ref) { - var _this2 = this; - var subtitleTracks = _ref.subtitleTracks; - if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) { - this.levels = subtitleTracks.map(function (mediaPlaylist) { - return new Level(mediaPlaylist); - }); - return; - } - this.tracksBuffered = []; - this.levels = subtitleTracks.map(function (mediaPlaylist) { - var level = new Level(mediaPlaylist); - _this2.tracksBuffered[level.id] = []; - return level; - }); - this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE); - this.fragPrevious = null; - this.mediaBuffer = null; - }; - _proto.onSubtitleTrackSwitch = function onSubtitleTrackSwitch(event, data) { - var _this$levels; - this.currentTrackId = data.id; - if (!((_this$levels = this.levels) != null && _this$levels.length) || this.currentTrackId === -1) { - this.clearInterval(); - return; - } - - // Check if track has the necessary details to load fragments - var currentTrack = this.levels[this.currentTrackId]; - if (currentTrack != null && currentTrack.details) { - this.mediaBuffer = this.mediaBufferTimeRanges; - } else { - this.mediaBuffer = null; - } - if (currentTrack) { - this.setInterval(TICK_INTERVAL$1); - } - } - - // Got a new set of subtitle fragments. - ; - _proto.onSubtitleTrackLoaded = function onSubtitleTrackLoaded(event, data) { - var _track$details; - var currentTrackId = this.currentTrackId, - levels = this.levels; - var newDetails = data.details, - trackId = data.id; - if (!levels) { - this.warn("Subtitle tracks were reset while loading level " + trackId); - return; - } - var track = levels[trackId]; - if (trackId >= levels.length || !track) { - return; - } - this.log("Subtitle track " + trackId + " loaded [" + newDetails.startSN + "," + newDetails.endSN + "]" + (newDetails.lastPartSn ? "[part-" + newDetails.lastPartSn + "-" + newDetails.lastPartIndex + "]" : '') + ",duration:" + newDetails.totalduration); - this.mediaBuffer = this.mediaBufferTimeRanges; - var sliding = 0; - if (newDetails.live || (_track$details = track.details) != null && _track$details.live) { - var mainDetails = this.mainDetails; - if (newDetails.deltaUpdateFailed || !mainDetails) { - return; - } - var mainSlidingStartFragment = mainDetails.fragments[0]; - if (!track.details) { - if (newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) { - alignMediaPlaylistByPDT(newDetails, mainDetails); - sliding = newDetails.fragments[0].start; - } else if (mainSlidingStartFragment) { - // line up live playlist with main so that fragments in range are loaded - sliding = mainSlidingStartFragment.start; - addSliding(newDetails, sliding); - } - } else { - var _this$levelLastLoaded; - sliding = this.alignPlaylists(newDetails, track.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details); - if (sliding === 0 && mainSlidingStartFragment) { - // realign with main when there is no overlap with last refresh - sliding = mainSlidingStartFragment.start; - addSliding(newDetails, sliding); - } - } - } - track.details = newDetails; - this.levelLastLoaded = track; - if (trackId !== currentTrackId) { - return; - } - if (!this.startFragRequested && (this.mainDetails || !newDetails.live)) { - this.setStartPosition(this.mainDetails || newDetails, sliding); - } - - // trigger handler right now - this.tick(); - - // If playlist is misaligned because of bad PDT or drift, delete details to resync with main on reload - if (newDetails.live && !this.fragCurrent && this.media && this.state === State.IDLE) { - var foundFrag = findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0); - if (!foundFrag) { - this.warn('Subtitle playlist not aligned with playback'); - track.details = undefined; - } - } - }; - _proto._handleFragmentLoadComplete = function _handleFragmentLoadComplete(fragLoadedData) { - var _this3 = this; - var frag = fragLoadedData.frag, - payload = fragLoadedData.payload; - var decryptData = frag.decryptdata; - var hls = this.hls; - if (this.fragContextChanged(frag)) { - return; - } - // check to see if the payload needs to be decrypted - if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') { - var startTime = performance.now(); - // decrypt the subtitles - this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(function (err) { - hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.FRAG_DECRYPT_ERROR, - fatal: false, - error: err, - reason: err.message, - frag: frag - }); - throw err; - }).then(function (decryptedData) { - var endTime = performance.now(); - hls.trigger(Events.FRAG_DECRYPTED, { - frag: frag, - payload: decryptedData, - stats: { - tstart: startTime, - tdecrypt: endTime - } - }); - }).catch(function (err) { - _this3.warn(err.name + ": " + err.message); - _this3.state = State.IDLE; - }); - } - }; - _proto.doTick = function doTick() { - if (!this.media) { - this.state = State.IDLE; - return; - } - if (this.state === State.IDLE) { - var currentTrackId = this.currentTrackId, - levels = this.levels; - var track = levels == null ? void 0 : levels[currentTrackId]; - if (!track || !levels.length || !track.details) { - return; - } - var config = this.config; - var currentTime = this.getLoadPosition(); - var bufferedInfo = BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId] || [], currentTime, config.maxBufferHole); - var targetBufferTime = bufferedInfo.end, - bufferLen = bufferedInfo.len; - var mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN); - var trackDetails = track.details; - var maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len) + trackDetails.levelTargetDuration; - if (bufferLen > maxBufLen) { - return; - } - var fragments = trackDetails.fragments; - var fragLen = fragments.length; - var end = trackDetails.edge; - var foundFrag = null; - var fragPrevious = this.fragPrevious; - if (targetBufferTime < end) { - var tolerance = config.maxFragLookUpTolerance; - var lookupTolerance = targetBufferTime > end - tolerance ? 0 : tolerance; - foundFrag = findFragmentByPTS(fragPrevious, fragments, Math.max(fragments[0].start, targetBufferTime), lookupTolerance); - if (!foundFrag && fragPrevious && fragPrevious.start < fragments[0].start) { - foundFrag = fragments[0]; - } - } else { - foundFrag = fragments[fragLen - 1]; - } - if (!foundFrag) { - return; - } - foundFrag = this.mapToInitFragWhenRequired(foundFrag); - if (foundFrag.sn !== 'initSegment') { - // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment - var curSNIdx = foundFrag.sn - trackDetails.startSN; - var prevFrag = fragments[curSNIdx - 1]; - if (prevFrag && prevFrag.cc === foundFrag.cc && this.fragmentTracker.getState(prevFrag) === FragmentState.NOT_LOADED) { - foundFrag = prevFrag; - } - } - if (this.fragmentTracker.getState(foundFrag) === FragmentState.NOT_LOADED) { - // only load if fragment is not loaded - this.loadFragment(foundFrag, track, targetBufferTime); - } - } - }; - _proto.getMaxBufferLength = function getMaxBufferLength(mainBufferLength) { - var maxConfigBuffer = _BaseStreamController.prototype.getMaxBufferLength.call(this); - if (!mainBufferLength) { - return maxConfigBuffer; - } - return Math.max(maxConfigBuffer, mainBufferLength); - }; - _proto.loadFragment = function loadFragment(frag, level, targetBufferTime) { - this.fragCurrent = frag; - if (frag.sn === 'initSegment') { - this._loadInitSegment(frag, level); - } else { - this.startFragRequested = true; - _BaseStreamController.prototype.loadFragment.call(this, frag, level, targetBufferTime); - } - }; - _createClass(SubtitleStreamController, [{ - key: "mediaBufferTimeRanges", - get: function get() { - return new BufferableInstance(this.tracksBuffered[this.currentTrackId] || []); - } - }]); - return SubtitleStreamController; - }(BaseStreamController); - var BufferableInstance = function BufferableInstance(timeranges) { - this.buffered = void 0; - var getRange = function getRange(name, index, length) { - index = index >>> 0; - if (index > length - 1) { - throw new DOMException("Failed to execute '" + name + "' on 'TimeRanges': The index provided (" + index + ") is greater than the maximum bound (" + length + ")"); - } - return timeranges[index][name]; - }; - this.buffered = { - get length() { - return timeranges.length; - }, - end: function end(index) { - return getRange('end', index, timeranges.length); - }, - start: function start(index) { - return getRange('start', index, timeranges.length); - } - }; - }; - - var SubtitleTrackController = /*#__PURE__*/function (_BasePlaylistControll) { - _inheritsLoose(SubtitleTrackController, _BasePlaylistControll); - function SubtitleTrackController(hls) { - var _this; - _this = _BasePlaylistControll.call(this, hls, '[subtitle-track-controller]') || this; - _this.media = null; - _this.tracks = []; - _this.groupIds = null; - _this.tracksInGroup = []; - _this.trackId = -1; - _this.currentTrack = null; - _this.selectDefaultTrack = true; - _this.queuedDefaultTrack = -1; - _this.asyncPollTrackChange = function () { - return _this.pollTrackChange(0); - }; - _this.useTextTrackPolling = false; - _this.subtitlePollingInterval = -1; - _this._subtitleDisplay = true; - _this.onTextTracksChanged = function () { - if (!_this.useTextTrackPolling) { - self.clearInterval(_this.subtitlePollingInterval); - } - // Media is undefined when switching streams via loadSource() - if (!_this.media || !_this.hls.config.renderTextTracksNatively) { - return; - } - var textTrack = null; - var tracks = filterSubtitleTracks(_this.media.textTracks); - for (var i = 0; i < tracks.length; i++) { - if (tracks[i].mode === 'hidden') { - // Do not break in case there is a following track with showing. - textTrack = tracks[i]; - } else if (tracks[i].mode === 'showing') { - textTrack = tracks[i]; - break; - } - } - - // Find internal track index for TextTrack - var trackId = _this.findTrackForTextTrack(textTrack); - if (_this.subtitleTrack !== trackId) { - _this.setSubtitleTrack(trackId); - } - }; - _this.registerListeners(); - return _this; - } - var _proto = SubtitleTrackController.prototype; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.tracks.length = 0; - this.tracksInGroup.length = 0; - this.currentTrack = null; - this.onTextTracksChanged = this.asyncPollTrackChange = null; - _BasePlaylistControll.prototype.destroy.call(this); - }; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); - hls.on(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); - hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); - hls.on(Events.ERROR, this.onError, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this); - hls.off(Events.LEVEL_SWITCHING, this.onLevelSwitching, this); - hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this); - hls.off(Events.ERROR, this.onError, this); - } - - // Listen for subtitle track change, then extract the current track ID. - ; - _proto.onMediaAttached = function onMediaAttached(event, data) { - this.media = data.media; - if (!this.media) { - return; - } - if (this.queuedDefaultTrack > -1) { - this.subtitleTrack = this.queuedDefaultTrack; - this.queuedDefaultTrack = -1; - } - this.useTextTrackPolling = !(this.media.textTracks && 'onchange' in this.media.textTracks); - if (this.useTextTrackPolling) { - this.pollTrackChange(500); - } else { - this.media.textTracks.addEventListener('change', this.asyncPollTrackChange); - } - }; - _proto.pollTrackChange = function pollTrackChange(timeout) { - self.clearInterval(this.subtitlePollingInterval); - this.subtitlePollingInterval = self.setInterval(this.onTextTracksChanged, timeout); - }; - _proto.onMediaDetaching = function onMediaDetaching() { - if (!this.media) { - return; - } - self.clearInterval(this.subtitlePollingInterval); - if (!this.useTextTrackPolling) { - this.media.textTracks.removeEventListener('change', this.asyncPollTrackChange); - } - if (this.trackId > -1) { - this.queuedDefaultTrack = this.trackId; - } - var textTracks = filterSubtitleTracks(this.media.textTracks); - // Clear loaded cues on media detachment from tracks - textTracks.forEach(function (track) { - clearCurrentCues(track); - }); - // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled. - this.subtitleTrack = -1; - this.media = null; - }; - _proto.onManifestLoading = function onManifestLoading() { - this.tracks = []; - this.groupIds = null; - this.tracksInGroup = []; - this.trackId = -1; - this.currentTrack = null; - this.selectDefaultTrack = true; - } - - // Fired whenever a new manifest is loaded. - ; - _proto.onManifestParsed = function onManifestParsed(event, data) { - this.tracks = data.subtitleTracks; - }; - _proto.onSubtitleTrackLoaded = function onSubtitleTrackLoaded(event, data) { - var id = data.id, - groupId = data.groupId, - details = data.details; - var trackInActiveGroup = this.tracksInGroup[id]; - if (!trackInActiveGroup || trackInActiveGroup.groupId !== groupId) { - this.warn("Subtitle track with id:" + id + " and group:" + groupId + " not found in active group " + (trackInActiveGroup == null ? void 0 : trackInActiveGroup.groupId)); - return; - } - var curDetails = trackInActiveGroup.details; - trackInActiveGroup.details = data.details; - this.log("Subtitle track " + id + " \"" + trackInActiveGroup.name + "\" lang:" + trackInActiveGroup.lang + " group:" + groupId + " loaded [" + details.startSN + "-" + details.endSN + "]"); - if (id === this.trackId) { - this.playlistLoaded(id, data, curDetails); - } - }; - _proto.onLevelLoading = function onLevelLoading(event, data) { - this.switchLevel(data.level); - }; - _proto.onLevelSwitching = function onLevelSwitching(event, data) { - this.switchLevel(data.level); - }; - _proto.switchLevel = function switchLevel(levelIndex) { - var levelInfo = this.hls.levels[levelIndex]; - if (!levelInfo) { - return; - } - var subtitleGroups = levelInfo.subtitleGroups || null; - var currentGroups = this.groupIds; - var currentTrack = this.currentTrack; - if (!subtitleGroups || (currentGroups == null ? void 0 : currentGroups.length) !== (subtitleGroups == null ? void 0 : subtitleGroups.length) || subtitleGroups != null && subtitleGroups.some(function (groupId) { - return (currentGroups == null ? void 0 : currentGroups.indexOf(groupId)) === -1; - })) { - this.groupIds = subtitleGroups; - this.trackId = -1; - this.currentTrack = null; - var subtitleTracks = this.tracks.filter(function (track) { - return !subtitleGroups || subtitleGroups.indexOf(track.groupId) !== -1; - }); - if (subtitleTracks.length) { - // Disable selectDefaultTrack if there are no default tracks - if (this.selectDefaultTrack && !subtitleTracks.some(function (track) { - return track.default; - })) { - this.selectDefaultTrack = false; - } - // track.id should match hls.audioTracks index - subtitleTracks.forEach(function (track, i) { - track.id = i; - }); - } else if (!currentTrack && !this.tracksInGroup.length) { - // Do not dispatch SUBTITLE_TRACKS_UPDATED when there were and are no tracks - return; - } - this.tracksInGroup = subtitleTracks; - - // Find preferred track - var subtitlePreference = this.hls.config.subtitlePreference; - if (!currentTrack && subtitlePreference) { - this.selectDefaultTrack = false; - var groupIndex = findMatchingOption(subtitlePreference, subtitleTracks); - if (groupIndex > -1) { - currentTrack = subtitleTracks[groupIndex]; - } else { - var allIndex = findMatchingOption(subtitlePreference, this.tracks); - currentTrack = this.tracks[allIndex]; - } - } - - // Select initial track - var trackId = this.findTrackId(currentTrack); - if (trackId === -1 && currentTrack) { - trackId = this.findTrackId(null); - } - - // Dispatch events and load track if needed - var subtitleTracksUpdated = { - subtitleTracks: subtitleTracks - }; - this.log("Updating subtitle tracks, " + subtitleTracks.length + " track(s) found in \"" + (subtitleGroups == null ? void 0 : subtitleGroups.join(',')) + "\" group-id"); - this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated); - if (trackId !== -1 && this.trackId === -1) { - this.setSubtitleTrack(trackId); - } - } else if (this.shouldReloadPlaylist(currentTrack)) { - // Retry playlist loading if no playlist is or has been loaded yet - this.setSubtitleTrack(this.trackId); - } - }; - _proto.findTrackId = function findTrackId(currentTrack) { - var tracks = this.tracksInGroup; - var selectDefault = this.selectDefaultTrack; - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - if (selectDefault && !track.default || !selectDefault && !currentTrack) { - continue; - } - if (!currentTrack || matchesOption(track, currentTrack)) { - return i; - } - } - if (currentTrack) { - for (var _i = 0; _i < tracks.length; _i++) { - var _track = tracks[_i]; - if (mediaAttributesIdentical(currentTrack.attrs, _track.attrs, ['LANGUAGE', 'ASSOC-LANGUAGE', 'CHARACTERISTICS'])) { - return _i; - } - } - for (var _i2 = 0; _i2 < tracks.length; _i2++) { - var _track2 = tracks[_i2]; - if (mediaAttributesIdentical(currentTrack.attrs, _track2.attrs, ['LANGUAGE'])) { - return _i2; - } - } - } - return -1; - }; - _proto.findTrackForTextTrack = function findTrackForTextTrack(textTrack) { - if (textTrack) { - var tracks = this.tracksInGroup; - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - if (subtitleTrackMatchesTextTrack(track, textTrack)) { - return i; - } - } - } - return -1; - }; - _proto.onError = function onError(event, data) { - if (data.fatal || !data.context) { - return; - } - if (data.context.type === PlaylistContextType.SUBTITLE_TRACK && data.context.id === this.trackId && (!this.groupIds || this.groupIds.indexOf(data.context.groupId) !== -1)) { - this.checkRetry(data); - } - }; - _proto.setSubtitleOption = function setSubtitleOption(subtitleOption) { - this.hls.config.subtitlePreference = subtitleOption; - if (subtitleOption) { - var allSubtitleTracks = this.allSubtitleTracks; - this.selectDefaultTrack = false; - if (allSubtitleTracks.length) { - // First see if current option matches (no switch op) - var currentTrack = this.currentTrack; - if (currentTrack && matchesOption(subtitleOption, currentTrack)) { - return currentTrack; - } - // Find option in current group - var groupIndex = findMatchingOption(subtitleOption, this.tracksInGroup); - if (groupIndex > -1) { - var track = this.tracksInGroup[groupIndex]; - this.setSubtitleTrack(groupIndex); - return track; - } else if (currentTrack) { - // If this is not the initial selection return null - // option should have matched one in active group - return null; - } else { - // Find the option in all tracks for initial selection - var allIndex = findMatchingOption(subtitleOption, allSubtitleTracks); - if (allIndex > -1) { - return allSubtitleTracks[allIndex]; - } - } - } - } - return null; - }; - _proto.loadPlaylist = function loadPlaylist(hlsUrlParameters) { - _BasePlaylistControll.prototype.loadPlaylist.call(this); - var currentTrack = this.currentTrack; - if (this.shouldLoadPlaylist(currentTrack) && currentTrack) { - var id = currentTrack.id; - var groupId = currentTrack.groupId; - var url = currentTrack.url; - if (hlsUrlParameters) { - try { - url = hlsUrlParameters.addDirectives(url); - } catch (error) { - this.warn("Could not construct new URL with HLS Delivery Directives: " + error); - } - } - this.log("Loading subtitle playlist for id " + id); - this.hls.trigger(Events.SUBTITLE_TRACK_LOADING, { - url: url, - id: id, - groupId: groupId, - deliveryDirectives: hlsUrlParameters || null - }); - } - } - - /** - * Disables the old subtitleTrack and sets current mode on the next subtitleTrack. - * This operates on the DOM textTracks. - * A value of -1 will disable all subtitle tracks. - */; - _proto.toggleTrackModes = function toggleTrackModes() { - var media = this.media; - if (!media) { - return; - } - var textTracks = filterSubtitleTracks(media.textTracks); - var currentTrack = this.currentTrack; - var nextTrack; - if (currentTrack) { - nextTrack = textTracks.filter(function (textTrack) { - return subtitleTrackMatchesTextTrack(currentTrack, textTrack); - })[0]; - if (!nextTrack) { - this.warn("Unable to find subtitle TextTrack with name \"" + currentTrack.name + "\" and language \"" + currentTrack.lang + "\""); - } - } - [].slice.call(textTracks).forEach(function (track) { - if (track.mode !== 'disabled' && track !== nextTrack) { - track.mode = 'disabled'; - } - }); - if (nextTrack) { - var mode = this.subtitleDisplay ? 'showing' : 'hidden'; - if (nextTrack.mode !== mode) { - nextTrack.mode = mode; - } - } - } - - /** - * This method is responsible for validating the subtitle index and periodically reloading if live. - * Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track. - */; - _proto.setSubtitleTrack = function setSubtitleTrack(newId) { - var tracks = this.tracksInGroup; - - // setting this.subtitleTrack will trigger internal logic - // if media has not been attached yet, it will fail - // we keep a reference to the default track id - // and we'll set subtitleTrack when onMediaAttached is triggered - if (!this.media) { - this.queuedDefaultTrack = newId; - return; - } - - // exit if track id as already set or invalid - if (newId < -1 || newId >= tracks.length || !isFiniteNumber(newId)) { - this.warn("Invalid subtitle track id: " + newId); - return; - } - - // stopping live reloading timer if any - this.clearTimer(); - this.selectDefaultTrack = false; - var lastTrack = this.currentTrack; - var track = tracks[newId] || null; - this.trackId = newId; - this.currentTrack = track; - this.toggleTrackModes(); - if (!track) { - // switch to -1 - this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { - id: newId - }); - return; - } - var trackLoaded = !!track.details && !track.details.live; - if (newId === this.trackId && track === lastTrack && trackLoaded) { - return; - } - this.log("Switching to subtitle-track " + newId + (track ? " \"" + track.name + "\" lang:" + track.lang + " group:" + track.groupId : '')); - var id = track.id, - _track$groupId = track.groupId, - groupId = _track$groupId === void 0 ? '' : _track$groupId, - name = track.name, - type = track.type, - url = track.url; - this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { - id: id, - groupId: groupId, - name: name, - type: type, - url: url - }); - var hlsUrlParameters = this.switchParams(track.url, lastTrack == null ? void 0 : lastTrack.details, track.details); - this.loadPlaylist(hlsUrlParameters); - }; - _createClass(SubtitleTrackController, [{ - key: "subtitleDisplay", - get: function get() { - return this._subtitleDisplay; - }, - set: function set(value) { - this._subtitleDisplay = value; - if (this.trackId > -1) { - this.toggleTrackModes(); - } - } - }, { - key: "allSubtitleTracks", - get: function get() { - return this.tracks; - } - - /** get alternate subtitle tracks list from playlist **/ - }, { - key: "subtitleTracks", - get: function get() { - return this.tracksInGroup; - } - - /** get/set index of the selected subtitle track (based on index in subtitle track lists) **/ - }, { - key: "subtitleTrack", - get: function get() { - return this.trackId; - }, - set: function set(newId) { - this.selectDefaultTrack = false; - this.setSubtitleTrack(newId); - } - }]); - return SubtitleTrackController; - }(BasePlaylistController); - - var BufferOperationQueue = /*#__PURE__*/function () { - function BufferOperationQueue(sourceBufferReference) { - this.buffers = void 0; - this.queues = { - video: [], - audio: [], - audiovideo: [] - }; - this.buffers = sourceBufferReference; - } - var _proto = BufferOperationQueue.prototype; - _proto.append = function append(operation, type, pending) { - var queue = this.queues[type]; - queue.push(operation); - if (queue.length === 1 && !pending) { - this.executeNext(type); - } - }; - _proto.insertAbort = function insertAbort(operation, type) { - var queue = this.queues[type]; - queue.unshift(operation); - this.executeNext(type); - }; - _proto.appendBlocker = function appendBlocker(type) { - var execute; - var promise = new Promise(function (resolve) { - execute = resolve; - }); - var operation = { - execute: execute, - onStart: function onStart() {}, - onComplete: function onComplete() {}, - onError: function onError() {} - }; - this.append(operation, type); - return promise; - }; - _proto.executeNext = function executeNext(type) { - var queue = this.queues[type]; - if (queue.length) { - var operation = queue[0]; - try { - // Operations are expected to result in an 'updateend' event being fired. If not, the queue will lock. Operations - // which do not end with this event must call _onSBUpdateEnd manually - operation.execute(); - } catch (error) { - logger.warn("[buffer-operation-queue]: Exception executing \"" + type + "\" SourceBuffer operation: " + error); - operation.onError(error); - - // Only shift the current operation off, otherwise the updateend handler will do this for us - var sb = this.buffers[type]; - if (!(sb != null && sb.updating)) { - this.shiftAndExecuteNext(type); - } - } - } - }; - _proto.shiftAndExecuteNext = function shiftAndExecuteNext(type) { - this.queues[type].shift(); - this.executeNext(type); - }; - _proto.current = function current(type) { - return this.queues[type][0]; - }; - return BufferOperationQueue; - }(); - - var VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/; - var BufferController = /*#__PURE__*/function () { - function BufferController(hls) { - var _this = this; - // The level details used to determine duration, target-duration and live - this.details = null; - // cache the self generated object url to detect hijack of video tag - this._objectUrl = null; - // A queue of buffer operations which require the SourceBuffer to not be updating upon execution - this.operationQueue = void 0; - // References to event listeners for each SourceBuffer, so that they can be referenced for event removal - this.listeners = void 0; - this.hls = void 0; - // The number of BUFFER_CODEC events received before any sourceBuffers are created - this.bufferCodecEventsExpected = 0; - // The total number of BUFFER_CODEC events received - this._bufferCodecEventsTotal = 0; - // A reference to the attached media element - this.media = null; - // A reference to the active media source - this.mediaSource = null; - // Last MP3 audio chunk appended - this.lastMpegAudioChunk = null; - this.appendSource = void 0; - // counters - this.appendErrors = { - audio: 0, - video: 0, - audiovideo: 0 - }; - this.tracks = {}; - this.pendingTracks = {}; - this.sourceBuffer = void 0; - this.log = void 0; - this.warn = void 0; - this.error = void 0; - this._onEndStreaming = function (event) { - if (!_this.hls) { - return; - } - _this.hls.pauseBuffering(); - }; - this._onStartStreaming = function (event) { - if (!_this.hls) { - return; - } - _this.hls.resumeBuffering(); - }; - // Keep as arrow functions so that we can directly reference these functions directly as event listeners - this._onMediaSourceOpen = function () { - var media = _this.media, - mediaSource = _this.mediaSource; - _this.log('Media source opened'); - if (media) { - media.removeEventListener('emptied', _this._onMediaEmptied); - _this.updateMediaElementDuration(); - _this.hls.trigger(Events.MEDIA_ATTACHED, { - media: media, - mediaSource: mediaSource - }); - } - if (mediaSource) { - // once received, don't listen anymore to sourceopen event - mediaSource.removeEventListener('sourceopen', _this._onMediaSourceOpen); - } - _this.checkPendingTracks(); - }; - this._onMediaSourceClose = function () { - _this.log('Media source closed'); - }; - this._onMediaSourceEnded = function () { - _this.log('Media source ended'); - }; - this._onMediaEmptied = function () { - var mediaSrc = _this.mediaSrc, - _objectUrl = _this._objectUrl; - if (mediaSrc !== _objectUrl) { - logger.error("Media element src was set while attaching MediaSource (" + _objectUrl + " > " + mediaSrc + ")"); - } - }; - this.hls = hls; - var logPrefix = '[buffer-controller]'; - this.appendSource = isManagedMediaSource(getMediaSource(hls.config.preferManagedMediaSource)); - this.log = logger.log.bind(logger, logPrefix); - this.warn = logger.warn.bind(logger, logPrefix); - this.error = logger.error.bind(logger, logPrefix); - this._initSourceBuffer(); - this.registerListeners(); - } - var _proto = BufferController.prototype; - _proto.hasSourceTypes = function hasSourceTypes() { - return this.getSourceBufferTypes().length > 0 || Object.keys(this.pendingTracks).length > 0; - }; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.details = null; - this.lastMpegAudioChunk = null; - // @ts-ignore - this.hls = null; - }; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.BUFFER_RESET, this.onBufferReset, this); - hls.on(Events.BUFFER_APPENDING, this.onBufferAppending, this); - hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); - hls.on(Events.BUFFER_EOS, this.onBufferEos, this); - hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - hls.on(Events.FRAG_PARSED, this.onFragParsed, this); - hls.on(Events.FRAG_CHANGED, this.onFragChanged, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.BUFFER_RESET, this.onBufferReset, this); - hls.off(Events.BUFFER_APPENDING, this.onBufferAppending, this); - hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); - hls.off(Events.BUFFER_EOS, this.onBufferEos, this); - hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - hls.off(Events.FRAG_PARSED, this.onFragParsed, this); - hls.off(Events.FRAG_CHANGED, this.onFragChanged, this); - }; - _proto._initSourceBuffer = function _initSourceBuffer() { - this.sourceBuffer = {}; - this.operationQueue = new BufferOperationQueue(this.sourceBuffer); - this.listeners = { - audio: [], - video: [], - audiovideo: [] - }; - this.appendErrors = { - audio: 0, - video: 0, - audiovideo: 0 - }; - this.lastMpegAudioChunk = null; - }; - _proto.onManifestLoading = function onManifestLoading() { - this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0; - this.details = null; - }; - _proto.onManifestParsed = function onManifestParsed(event, data) { - // in case of alt audio 2 BUFFER_CODECS events will be triggered, one per stream controller - // sourcebuffers will be created all at once when the expected nb of tracks will be reached - // in case alt audio is not used, only one BUFFER_CODEC event will be fired from main stream controller - // it will contain the expected nb of source buffers, no need to compute it - var codecEvents = 2; - if (data.audio && !data.video || !data.altAudio || !true) { - codecEvents = 1; - } - this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = codecEvents; - this.log(this.bufferCodecEventsExpected + " bufferCodec event(s) expected"); - }; - _proto.onMediaAttaching = function onMediaAttaching(event, data) { - var media = this.media = data.media; - var MediaSource = getMediaSource(this.appendSource); - if (media && MediaSource) { - var _ms$constructor; - var ms = this.mediaSource = new MediaSource(); - this.log("created media source: " + ((_ms$constructor = ms.constructor) == null ? void 0 : _ms$constructor.name)); - // MediaSource listeners are arrow functions with a lexical scope, and do not need to be bound - ms.addEventListener('sourceopen', this._onMediaSourceOpen); - ms.addEventListener('sourceended', this._onMediaSourceEnded); - ms.addEventListener('sourceclose', this._onMediaSourceClose); - if (this.appendSource) { - ms.addEventListener('startstreaming', this._onStartStreaming); - ms.addEventListener('endstreaming', this._onEndStreaming); - } - - // cache the locally generated object url - var objectUrl = this._objectUrl = self.URL.createObjectURL(ms); - // link video and media Source - if (this.appendSource) { - try { - media.removeAttribute('src'); - // ManagedMediaSource will not open without disableRemotePlayback set to false or source alternatives - var MMS = self.ManagedMediaSource; - media.disableRemotePlayback = media.disableRemotePlayback || MMS && ms instanceof MMS; - removeSourceChildren(media); - addSource(media, objectUrl); - media.load(); - } catch (error) { - media.src = objectUrl; - } - } else { - media.src = objectUrl; - } - media.addEventListener('emptied', this._onMediaEmptied); - } - }; - _proto.onMediaDetaching = function onMediaDetaching() { - var media = this.media, - mediaSource = this.mediaSource, - _objectUrl = this._objectUrl; - if (mediaSource) { - this.log('media source detaching'); - if (mediaSource.readyState === 'open') { - try { - // endOfStream could trigger exception if any sourcebuffer is in updating state - // we don't really care about checking sourcebuffer state here, - // as we are anyway detaching the MediaSource - // let's just avoid this exception to propagate - mediaSource.endOfStream(); - } catch (err) { - this.warn("onMediaDetaching: " + err.message + " while calling endOfStream"); - } - } - // Clean up the SourceBuffers by invoking onBufferReset - this.onBufferReset(); - mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen); - mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded); - mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose); - if (this.appendSource) { - mediaSource.removeEventListener('startstreaming', this._onStartStreaming); - mediaSource.removeEventListener('endstreaming', this._onEndStreaming); - } - - // Detach properly the MediaSource from the HTMLMediaElement as - // suggested in https://github.com/w3c/media-source/issues/53. - if (media) { - media.removeEventListener('emptied', this._onMediaEmptied); - if (_objectUrl) { - self.URL.revokeObjectURL(_objectUrl); - } - - // clean up video tag src only if it's our own url. some external libraries might - // hijack the video tag and change its 'src' without destroying the Hls instance first - if (this.mediaSrc === _objectUrl) { - media.removeAttribute('src'); - if (this.appendSource) { - removeSourceChildren(media); - } - media.load(); - } else { - this.warn('media|source.src was changed by a third party - skip cleanup'); - } - } - this.mediaSource = null; - this.media = null; - this._objectUrl = null; - this.bufferCodecEventsExpected = this._bufferCodecEventsTotal; - this.pendingTracks = {}; - this.tracks = {}; - } - this.hls.trigger(Events.MEDIA_DETACHED, undefined); - }; - _proto.onBufferReset = function onBufferReset() { - var _this2 = this; - this.getSourceBufferTypes().forEach(function (type) { - _this2.resetBuffer(type); - }); - this._initSourceBuffer(); - }; - _proto.resetBuffer = function resetBuffer(type) { - var sb = this.sourceBuffer[type]; - try { - if (sb) { - var _this$mediaSource; - this.removeBufferListeners(type); - // Synchronously remove the SB from the map before the next call in order to prevent an async function from - // accessing it - this.sourceBuffer[type] = undefined; - if ((_this$mediaSource = this.mediaSource) != null && _this$mediaSource.sourceBuffers.length) { - this.mediaSource.removeSourceBuffer(sb); - } - } - } catch (err) { - this.warn("onBufferReset " + type, err); - } - }; - _proto.onBufferCodecs = function onBufferCodecs(event, data) { - var _this3 = this; - var sourceBufferCount = this.getSourceBufferTypes().length; - var trackNames = Object.keys(data); - trackNames.forEach(function (trackName) { - if (sourceBufferCount) { - // check if SourceBuffer codec needs to change - var track = _this3.tracks[trackName]; - if (track && typeof track.buffer.changeType === 'function') { - var _trackCodec; - var _data$trackName = data[trackName], - id = _data$trackName.id, - codec = _data$trackName.codec, - levelCodec = _data$trackName.levelCodec, - container = _data$trackName.container, - metadata = _data$trackName.metadata; - var currentCodecFull = pickMostCompleteCodecName(track.codec, track.levelCodec); - var currentCodec = currentCodecFull == null ? void 0 : currentCodecFull.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1'); - var trackCodec = pickMostCompleteCodecName(codec, levelCodec); - var nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1'); - if (trackCodec && currentCodec !== nextCodec) { - if (trackName.slice(0, 5) === 'audio') { - trackCodec = getCodecCompatibleName(trackCodec, _this3.appendSource); - } - var mimeType = container + ";codecs=" + trackCodec; - _this3.appendChangeType(trackName, mimeType); - _this3.log("switching codec " + currentCodecFull + " to " + trackCodec); - _this3.tracks[trackName] = { - buffer: track.buffer, - codec: codec, - container: container, - levelCodec: levelCodec, - metadata: metadata, - id: id - }; - } - } - } else { - // if source buffer(s) not created yet, appended buffer tracks in this.pendingTracks - _this3.pendingTracks[trackName] = data[trackName]; - } - }); - - // if sourcebuffers already created, do nothing ... - if (sourceBufferCount) { - return; - } - var bufferCodecEventsExpected = Math.max(this.bufferCodecEventsExpected - 1, 0); - if (this.bufferCodecEventsExpected !== bufferCodecEventsExpected) { - this.log(bufferCodecEventsExpected + " bufferCodec event(s) expected " + trackNames.join(',')); - this.bufferCodecEventsExpected = bufferCodecEventsExpected; - } - if (this.mediaSource && this.mediaSource.readyState === 'open') { - this.checkPendingTracks(); - } - }; - _proto.appendChangeType = function appendChangeType(type, mimeType) { - var _this4 = this; - var operationQueue = this.operationQueue; - var operation = { - execute: function execute() { - var sb = _this4.sourceBuffer[type]; - if (sb) { - _this4.log("changing " + type + " sourceBuffer type to " + mimeType); - sb.changeType(mimeType); - } - operationQueue.shiftAndExecuteNext(type); - }, - onStart: function onStart() {}, - onComplete: function onComplete() {}, - onError: function onError(error) { - _this4.warn("Failed to change " + type + " SourceBuffer type", error); - } - }; - operationQueue.append(operation, type, !!this.pendingTracks[type]); - }; - _proto.onBufferAppending = function onBufferAppending(event, eventData) { - var _this5 = this; - var hls = this.hls, - operationQueue = this.operationQueue, - tracks = this.tracks; - var data = eventData.data, - type = eventData.type, - frag = eventData.frag, - part = eventData.part, - chunkMeta = eventData.chunkMeta; - var chunkStats = chunkMeta.buffering[type]; - var bufferAppendingStart = self.performance.now(); - chunkStats.start = bufferAppendingStart; - var fragBuffering = frag.stats.buffering; - var partBuffering = part ? part.stats.buffering : null; - if (fragBuffering.start === 0) { - fragBuffering.start = bufferAppendingStart; - } - if (partBuffering && partBuffering.start === 0) { - partBuffering.start = bufferAppendingStart; - } - - // TODO: Only update timestampOffset when audio/mpeg fragment or part is not contiguous with previously appended - // Adjusting `SourceBuffer.timestampOffset` (desired point in the timeline where the next frames should be appended) - // in Chrome browser when we detect MPEG audio container and time delta between level PTS and `SourceBuffer.timestampOffset` - // is greater than 100ms (this is enough to handle seek for VOD or level change for LIVE videos). - // More info here: https://github.com/video-dev/hls.js/issues/332#issuecomment-257986486 - var audioTrack = tracks.audio; - var checkTimestampOffset = false; - if (type === 'audio' && (audioTrack == null ? void 0 : audioTrack.container) === 'audio/mpeg') { - checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn; - this.lastMpegAudioChunk = chunkMeta; - } - var fragStart = frag.start; - var operation = { - execute: function execute() { - chunkStats.executeStart = self.performance.now(); - if (checkTimestampOffset) { - var sb = _this5.sourceBuffer[type]; - if (sb) { - var delta = fragStart - sb.timestampOffset; - if (Math.abs(delta) >= 0.1) { - _this5.log("Updating audio SourceBuffer timestampOffset to " + fragStart + " (delta: " + delta + ") sn: " + frag.sn + ")"); - sb.timestampOffset = fragStart; - } - } - } - _this5.appendExecutor(data, type); - }, - onStart: function onStart() { - // logger.debug(`[buffer-controller]: ${type} SourceBuffer updatestart`); - }, - onComplete: function onComplete() { - // logger.debug(`[buffer-controller]: ${type} SourceBuffer updateend`); - var end = self.performance.now(); - chunkStats.executeEnd = chunkStats.end = end; - if (fragBuffering.first === 0) { - fragBuffering.first = end; - } - if (partBuffering && partBuffering.first === 0) { - partBuffering.first = end; - } - var sourceBuffer = _this5.sourceBuffer; - var timeRanges = {}; - for (var _type in sourceBuffer) { - timeRanges[_type] = BufferHelper.getBuffered(sourceBuffer[_type]); - } - _this5.appendErrors[type] = 0; - if (type === 'audio' || type === 'video') { - _this5.appendErrors.audiovideo = 0; - } else { - _this5.appendErrors.audio = 0; - _this5.appendErrors.video = 0; - } - _this5.hls.trigger(Events.BUFFER_APPENDED, { - type: type, - frag: frag, - part: part, - chunkMeta: chunkMeta, - parent: frag.type, - timeRanges: timeRanges - }); - }, - onError: function onError(error) { - // in case any error occured while appending, put back segment in segments table - var event = { - type: ErrorTypes.MEDIA_ERROR, - parent: frag.type, - details: ErrorDetails.BUFFER_APPEND_ERROR, - sourceBufferName: type, - frag: frag, - part: part, - chunkMeta: chunkMeta, - error: error, - err: error, - fatal: false - }; - if (error.code === DOMException.QUOTA_EXCEEDED_ERR) { - // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror - // let's stop appending any segments, and report BUFFER_FULL_ERROR error - event.details = ErrorDetails.BUFFER_FULL_ERROR; - } else { - var appendErrorCount = ++_this5.appendErrors[type]; - event.details = ErrorDetails.BUFFER_APPEND_ERROR; - /* with UHD content, we could get loop of quota exceeded error until - browser is able to evict some data from sourcebuffer. Retrying can help recover. - */ - _this5.warn("Failed " + appendErrorCount + "/" + hls.config.appendErrorMaxRetry + " times to append segment in \"" + type + "\" sourceBuffer"); - if (appendErrorCount >= hls.config.appendErrorMaxRetry) { - event.fatal = true; - } - } - hls.trigger(Events.ERROR, event); - } - }; - operationQueue.append(operation, type, !!this.pendingTracks[type]); - }; - _proto.onBufferFlushing = function onBufferFlushing(event, data) { - var _this6 = this; - var operationQueue = this.operationQueue; - var flushOperation = function flushOperation(type) { - return { - execute: _this6.removeExecutor.bind(_this6, type, data.startOffset, data.endOffset), - onStart: function onStart() { - // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`); - }, - onComplete: function onComplete() { - // logger.debug(`[buffer-controller]: Finished flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`); - _this6.hls.trigger(Events.BUFFER_FLUSHED, { - type: type - }); - }, - onError: function onError(error) { - _this6.warn("Failed to remove from " + type + " SourceBuffer", error); - } - }; - }; - if (data.type) { - operationQueue.append(flushOperation(data.type), data.type); - } else { - this.getSourceBufferTypes().forEach(function (type) { - operationQueue.append(flushOperation(type), type); - }); - } - }; - _proto.onFragParsed = function onFragParsed(event, data) { - var _this7 = this; - var frag = data.frag, - part = data.part; - var buffersAppendedTo = []; - var elementaryStreams = part ? part.elementaryStreams : frag.elementaryStreams; - if (elementaryStreams[ElementaryStreamTypes.AUDIOVIDEO]) { - buffersAppendedTo.push('audiovideo'); - } else { - if (elementaryStreams[ElementaryStreamTypes.AUDIO]) { - buffersAppendedTo.push('audio'); - } - if (elementaryStreams[ElementaryStreamTypes.VIDEO]) { - buffersAppendedTo.push('video'); - } - } - var onUnblocked = function onUnblocked() { - var now = self.performance.now(); - frag.stats.buffering.end = now; - if (part) { - part.stats.buffering.end = now; - } - var stats = part ? part.stats : frag.stats; - _this7.hls.trigger(Events.FRAG_BUFFERED, { - frag: frag, - part: part, - stats: stats, - id: frag.type - }); - }; - if (buffersAppendedTo.length === 0) { - this.warn("Fragments must have at least one ElementaryStreamType set. type: " + frag.type + " level: " + frag.level + " sn: " + frag.sn); - } - this.blockBuffers(onUnblocked, buffersAppendedTo); - }; - _proto.onFragChanged = function onFragChanged(event, data) { - this.trimBuffers(); - } - - // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos() - // an undefined data.type will mark all buffers as EOS. - ; - _proto.onBufferEos = function onBufferEos(event, data) { - var _this8 = this; - var ended = this.getSourceBufferTypes().reduce(function (acc, type) { - var sb = _this8.sourceBuffer[type]; - if (sb && (!data.type || data.type === type)) { - sb.ending = true; - if (!sb.ended) { - sb.ended = true; - _this8.log(type + " sourceBuffer now EOS"); - } - } - return acc && !!(!sb || sb.ended); - }, true); - if (ended) { - this.log("Queueing mediaSource.endOfStream()"); - this.blockBuffers(function () { - _this8.getSourceBufferTypes().forEach(function (type) { - var sb = _this8.sourceBuffer[type]; - if (sb) { - sb.ending = false; - } - }); - var mediaSource = _this8.mediaSource; - if (!mediaSource || mediaSource.readyState !== 'open') { - if (mediaSource) { - _this8.log("Could not call mediaSource.endOfStream(). mediaSource.readyState: " + mediaSource.readyState); - } - return; - } - _this8.log("Calling mediaSource.endOfStream()"); - // Allow this to throw and be caught by the enqueueing function - mediaSource.endOfStream(); - }); - } - }; - _proto.onLevelUpdated = function onLevelUpdated(event, _ref) { - var details = _ref.details; - if (!details.fragments.length) { - return; - } - this.details = details; - if (this.getSourceBufferTypes().length) { - this.blockBuffers(this.updateMediaElementDuration.bind(this)); - } else { - this.updateMediaElementDuration(); - } - }; - _proto.trimBuffers = function trimBuffers() { - var hls = this.hls, - details = this.details, - media = this.media; - if (!media || details === null) { - return; - } - var sourceBufferTypes = this.getSourceBufferTypes(); - if (!sourceBufferTypes.length) { - return; - } - var config = hls.config; - var currentTime = media.currentTime; - var targetDuration = details.levelTargetDuration; - - // Support for deprecated liveBackBufferLength - var backBufferLength = details.live && config.liveBackBufferLength !== null ? config.liveBackBufferLength : config.backBufferLength; - if (isFiniteNumber(backBufferLength) && backBufferLength > 0) { - var maxBackBufferLength = Math.max(backBufferLength, targetDuration); - var targetBackBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration - maxBackBufferLength; - this.flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition); - } - if (isFiniteNumber(config.frontBufferFlushThreshold) && config.frontBufferFlushThreshold > 0) { - var frontBufferLength = Math.max(config.maxBufferLength, config.frontBufferFlushThreshold); - var maxFrontBufferLength = Math.max(frontBufferLength, targetDuration); - var targetFrontBufferPosition = Math.floor(currentTime / targetDuration) * targetDuration + maxFrontBufferLength; - this.flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition); - } - }; - _proto.flushBackBuffer = function flushBackBuffer(currentTime, targetDuration, targetBackBufferPosition) { - var _this9 = this; - var details = this.details, - sourceBuffer = this.sourceBuffer; - var sourceBufferTypes = this.getSourceBufferTypes(); - sourceBufferTypes.forEach(function (type) { - var sb = sourceBuffer[type]; - if (sb) { - var buffered = BufferHelper.getBuffered(sb); - // when target buffer start exceeds actual buffer start - if (buffered.length > 0 && targetBackBufferPosition > buffered.start(0)) { - _this9.hls.trigger(Events.BACK_BUFFER_REACHED, { - bufferEnd: targetBackBufferPosition - }); - - // Support for deprecated event: - if (details != null && details.live) { - _this9.hls.trigger(Events.LIVE_BACK_BUFFER_REACHED, { - bufferEnd: targetBackBufferPosition - }); - } else if (sb.ended && buffered.end(buffered.length - 1) - currentTime < targetDuration * 2) { - _this9.log("Cannot flush " + type + " back buffer while SourceBuffer is in ended state"); - return; - } - _this9.hls.trigger(Events.BUFFER_FLUSHING, { - startOffset: 0, - endOffset: targetBackBufferPosition, - type: type - }); - } - } - }); - }; - _proto.flushFrontBuffer = function flushFrontBuffer(currentTime, targetDuration, targetFrontBufferPosition) { - var _this10 = this; - var sourceBuffer = this.sourceBuffer; - var sourceBufferTypes = this.getSourceBufferTypes(); - sourceBufferTypes.forEach(function (type) { - var sb = sourceBuffer[type]; - if (sb) { - var buffered = BufferHelper.getBuffered(sb); - var numBufferedRanges = buffered.length; - // The buffer is either empty or contiguous - if (numBufferedRanges < 2) { - return; - } - var bufferStart = buffered.start(numBufferedRanges - 1); - var bufferEnd = buffered.end(numBufferedRanges - 1); - // No flush if we can tolerate the current buffer length or the current buffer range we would flush is contiguous with current position - if (targetFrontBufferPosition > bufferStart || currentTime >= bufferStart && currentTime <= bufferEnd) { - return; - } else if (sb.ended && currentTime - bufferEnd < 2 * targetDuration) { - _this10.log("Cannot flush " + type + " front buffer while SourceBuffer is in ended state"); - return; - } - _this10.hls.trigger(Events.BUFFER_FLUSHING, { - startOffset: bufferStart, - endOffset: Infinity, - type: type - }); - } - }); - } - - /** - * Update Media Source duration to current level duration or override to Infinity if configuration parameter - * 'liveDurationInfinity` is set to `true` - * More details: https://github.com/video-dev/hls.js/issues/355 - */; - _proto.updateMediaElementDuration = function updateMediaElementDuration() { - if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') { - return; - } - var details = this.details, - hls = this.hls, - media = this.media, - mediaSource = this.mediaSource; - var levelDuration = details.fragments[0].start + details.totalduration; - var mediaDuration = media.duration; - var msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : 0; - if (details.live && hls.config.liveDurationInfinity) { - // Override duration to Infinity - mediaSource.duration = Infinity; - this.updateSeekableRange(details); - } else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) { - // levelDuration was the last value we set. - // not using mediaSource.duration as the browser may tweak this value - // only update Media Source duration if its value increase, this is to avoid - // flushing already buffered portion when switching between quality level - this.log("Updating Media Source duration to " + levelDuration.toFixed(3)); - mediaSource.duration = levelDuration; - } - }; - _proto.updateSeekableRange = function updateSeekableRange(levelDetails) { - var mediaSource = this.mediaSource; - var fragments = levelDetails.fragments; - var len = fragments.length; - if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) { - var start = Math.max(0, fragments[0].start); - var end = Math.max(start, start + levelDetails.totalduration); - this.log("Media Source duration is set to " + mediaSource.duration + ". Setting seekable range to " + start + "-" + end + "."); - mediaSource.setLiveSeekableRange(start, end); - } - }; - _proto.checkPendingTracks = function checkPendingTracks() { - var bufferCodecEventsExpected = this.bufferCodecEventsExpected, - operationQueue = this.operationQueue, - pendingTracks = this.pendingTracks; - - // Check if we've received all of the expected bufferCodec events. When none remain, create all the sourceBuffers at once. - // This is important because the MSE spec allows implementations to throw QuotaExceededErrors if creating new sourceBuffers after - // data has been appended to existing ones. - // 2 tracks is the max (one for audio, one for video). If we've reach this max go ahead and create the buffers. - var pendingTracksCount = Object.keys(pendingTracks).length; - if (pendingTracksCount && (!bufferCodecEventsExpected || pendingTracksCount === 2 || 'audiovideo' in pendingTracks)) { - // ok, let's create them now ! - this.createSourceBuffers(pendingTracks); - this.pendingTracks = {}; - // append any pending segments now ! - var buffers = this.getSourceBufferTypes(); - if (buffers.length) { - this.hls.trigger(Events.BUFFER_CREATED, { - tracks: this.tracks - }); - buffers.forEach(function (type) { - operationQueue.executeNext(type); - }); - } else { - var error = new Error('could not create source buffer for media codec(s)'); - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR, - fatal: true, - error: error, - reason: error.message - }); - } - } - }; - _proto.createSourceBuffers = function createSourceBuffers(tracks) { - var _this11 = this; - var sourceBuffer = this.sourceBuffer, - mediaSource = this.mediaSource; - if (!mediaSource) { - throw Error('createSourceBuffers called when mediaSource was null'); - } - var _loop = function _loop(trackName) { - if (!sourceBuffer[trackName]) { - var _track$levelCodec; - var track = tracks[trackName]; - if (!track) { - throw Error("source buffer exists for track " + trackName + ", however track does not"); - } - // use levelCodec as first priority unless it contains multiple comma-separated codec values - var codec = ((_track$levelCodec = track.levelCodec) == null ? void 0 : _track$levelCodec.indexOf(',')) === -1 ? track.levelCodec : track.codec; - if (codec) { - if (trackName.slice(0, 5) === 'audio') { - codec = getCodecCompatibleName(codec, _this11.appendSource); - } - } - var mimeType = track.container + ";codecs=" + codec; - _this11.log("creating sourceBuffer(" + mimeType + ")"); - try { - var sb = sourceBuffer[trackName] = mediaSource.addSourceBuffer(mimeType); - var sbName = trackName; - _this11.addBufferListener(sbName, 'updatestart', _this11._onSBUpdateStart); - _this11.addBufferListener(sbName, 'updateend', _this11._onSBUpdateEnd); - _this11.addBufferListener(sbName, 'error', _this11._onSBUpdateError); - // ManagedSourceBuffer bufferedchange event - if (_this11.appendSource) { - _this11.addBufferListener(sbName, 'bufferedchange', function (type, event) { - // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event. - var removedRanges = event.removedRanges; - if (removedRanges != null && removedRanges.length) { - _this11.hls.trigger(Events.BUFFER_FLUSHED, { - type: trackName - }); - } - }); - } - _this11.tracks[trackName] = { - buffer: sb, - codec: codec, - container: track.container, - levelCodec: track.levelCodec, - metadata: track.metadata, - id: track.id - }; - } catch (err) { - _this11.error("error while trying to add sourceBuffer: " + err.message); - _this11.hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.BUFFER_ADD_CODEC_ERROR, - fatal: false, - error: err, - sourceBufferName: trackName, - mimeType: mimeType - }); - } - } - }; - for (var trackName in tracks) { - _loop(trackName); - } - }; - _proto._onSBUpdateStart = function _onSBUpdateStart(type) { - var operationQueue = this.operationQueue; - var operation = operationQueue.current(type); - operation.onStart(); - }; - _proto._onSBUpdateEnd = function _onSBUpdateEnd(type) { - var _this$mediaSource2; - if (((_this$mediaSource2 = this.mediaSource) == null ? void 0 : _this$mediaSource2.readyState) === 'closed') { - this.resetBuffer(type); - return; - } - var operationQueue = this.operationQueue; - var operation = operationQueue.current(type); - operation.onComplete(); - operationQueue.shiftAndExecuteNext(type); - }; - _proto._onSBUpdateError = function _onSBUpdateError(type, event) { - var _this$mediaSource3; - var error = new Error(type + " SourceBuffer error. MediaSource readyState: " + ((_this$mediaSource3 = this.mediaSource) == null ? void 0 : _this$mediaSource3.readyState)); - this.error("" + error, event); - // according to http://www.w3.org/TR/media-source/#sourcebuffer-append-error - // SourceBuffer errors are not necessarily fatal; if so, the HTMLMediaElement will fire an error event - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.BUFFER_APPENDING_ERROR, - sourceBufferName: type, - error: error, - fatal: false - }); - // updateend is always fired after error, so we'll allow that to shift the current operation off of the queue - var operation = this.operationQueue.current(type); - if (operation) { - operation.onError(error); - } - } - - // This method must result in an updateend event; if remove is not called, _onSBUpdateEnd must be called manually - ; - _proto.removeExecutor = function removeExecutor(type, startOffset, endOffset) { - var media = this.media, - mediaSource = this.mediaSource, - operationQueue = this.operationQueue, - sourceBuffer = this.sourceBuffer; - var sb = sourceBuffer[type]; - if (!media || !mediaSource || !sb) { - this.warn("Attempting to remove from the " + type + " SourceBuffer, but it does not exist"); - operationQueue.shiftAndExecuteNext(type); - return; - } - var mediaDuration = isFiniteNumber(media.duration) ? media.duration : Infinity; - var msDuration = isFiniteNumber(mediaSource.duration) ? mediaSource.duration : Infinity; - var removeStart = Math.max(0, startOffset); - var removeEnd = Math.min(endOffset, mediaDuration, msDuration); - if (removeEnd > removeStart && (!sb.ending || sb.ended)) { - sb.ended = false; - this.log("Removing [" + removeStart + "," + removeEnd + "] from the " + type + " SourceBuffer"); - sb.remove(removeStart, removeEnd); - } else { - // Cycle the queue - operationQueue.shiftAndExecuteNext(type); - } - } - - // This method must result in an updateend event; if append is not called, _onSBUpdateEnd must be called manually - ; - _proto.appendExecutor = function appendExecutor(data, type) { - var sb = this.sourceBuffer[type]; - if (!sb) { - if (!this.pendingTracks[type]) { - throw new Error("Attempting to append to the " + type + " SourceBuffer, but it does not exist"); - } - return; - } - sb.ended = false; - sb.appendBuffer(data); - } - - // Enqueues an operation to each SourceBuffer queue which, upon execution, resolves a promise. When all promises - // resolve, the onUnblocked function is executed. Functions calling this method do not need to unblock the queue - // upon completion, since we already do it here - ; - _proto.blockBuffers = function blockBuffers(onUnblocked, buffers) { - var _this12 = this; - if (buffers === void 0) { - buffers = this.getSourceBufferTypes(); - } - if (!buffers.length) { - this.log('Blocking operation requested, but no SourceBuffers exist'); - Promise.resolve().then(onUnblocked); - return; - } - var operationQueue = this.operationQueue; - - // logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`); - var blockingOperations = buffers.map(function (type) { - return operationQueue.appendBlocker(type); - }); - Promise.all(blockingOperations).then(function () { - // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`); - onUnblocked(); - buffers.forEach(function (type) { - var sb = _this12.sourceBuffer[type]; - // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to - // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration) - // While this is a workaround, it's probably useful to have around - if (!(sb != null && sb.updating)) { - operationQueue.shiftAndExecuteNext(type); - } - }); - }); - }; - _proto.getSourceBufferTypes = function getSourceBufferTypes() { - return Object.keys(this.sourceBuffer); - }; - _proto.addBufferListener = function addBufferListener(type, event, fn) { - var buffer = this.sourceBuffer[type]; - if (!buffer) { - return; - } - var listener = fn.bind(this, type); - this.listeners[type].push({ - event: event, - listener: listener - }); - buffer.addEventListener(event, listener); - }; - _proto.removeBufferListeners = function removeBufferListeners(type) { - var buffer = this.sourceBuffer[type]; - if (!buffer) { - return; - } - this.listeners[type].forEach(function (l) { - buffer.removeEventListener(l.event, l.listener); - }); - }; - _createClass(BufferController, [{ - key: "mediaSrc", - get: function get() { - var _this$media, _this$media$querySele; - var media = ((_this$media = this.media) == null ? void 0 : (_this$media$querySele = _this$media.querySelector) == null ? void 0 : _this$media$querySele.call(_this$media, 'source')) || this.media; - return media == null ? void 0 : media.src; - } - }]); - return BufferController; - }(); - function removeSourceChildren(node) { - var sourceChildren = node.querySelectorAll('source'); - [].slice.call(sourceChildren).forEach(function (source) { - node.removeChild(source); - }); - } - function addSource(media, url) { - var source = self.document.createElement('source'); - source.type = 'video/mp4'; - source.src = url; - media.appendChild(source); - } - - /** - * - * This code was ported from the dash.js project at: - * https://github.com/Dash-Industry-Forum/dash.js/blob/development/externals/cea608-parser.js - * https://github.com/Dash-Industry-Forum/dash.js/commit/8269b26a761e0853bb21d78780ed945144ecdd4d#diff-71bc295a2d6b6b7093a1d3290d53a4b2 - * - * The original copyright appears below: - * - * The copyright in this software is being made available under the BSD License, - * included below. This software may be subject to other third party and contributor - * rights, including patent rights, and no such rights are granted under this license. - * - * Copyright (c) 2015-2016, DASH Industry Forum. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * 2. Neither the name of Dash Industry Forum nor the names of its - * contributors may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - /** - * Exceptions from regular ASCII. CodePoints are mapped to UTF-16 codes - */ - - var specialCea608CharsCodes = { - 0x2a: 0xe1, - // lowercase a, acute accent - 0x5c: 0xe9, - // lowercase e, acute accent - 0x5e: 0xed, - // lowercase i, acute accent - 0x5f: 0xf3, - // lowercase o, acute accent - 0x60: 0xfa, - // lowercase u, acute accent - 0x7b: 0xe7, - // lowercase c with cedilla - 0x7c: 0xf7, - // division symbol - 0x7d: 0xd1, - // uppercase N tilde - 0x7e: 0xf1, - // lowercase n tilde - 0x7f: 0x2588, - // Full block - // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F - // THIS MEANS THAT \x50 MUST BE ADDED TO THE VALUES - 0x80: 0xae, - // Registered symbol (R) - 0x81: 0xb0, - // degree sign - 0x82: 0xbd, - // 1/2 symbol - 0x83: 0xbf, - // Inverted (open) question mark - 0x84: 0x2122, - // Trademark symbol (TM) - 0x85: 0xa2, - // Cents symbol - 0x86: 0xa3, - // Pounds sterling - 0x87: 0x266a, - // Music 8'th note - 0x88: 0xe0, - // lowercase a, grave accent - 0x89: 0x20, - // transparent space (regular) - 0x8a: 0xe8, - // lowercase e, grave accent - 0x8b: 0xe2, - // lowercase a, circumflex accent - 0x8c: 0xea, - // lowercase e, circumflex accent - 0x8d: 0xee, - // lowercase i, circumflex accent - 0x8e: 0xf4, - // lowercase o, circumflex accent - 0x8f: 0xfb, - // lowercase u, circumflex accent - // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F - 0x90: 0xc1, - // capital letter A with acute - 0x91: 0xc9, - // capital letter E with acute - 0x92: 0xd3, - // capital letter O with acute - 0x93: 0xda, - // capital letter U with acute - 0x94: 0xdc, - // capital letter U with diaresis - 0x95: 0xfc, - // lowercase letter U with diaeresis - 0x96: 0x2018, - // opening single quote - 0x97: 0xa1, - // inverted exclamation mark - 0x98: 0x2a, - // asterisk - 0x99: 0x2019, - // closing single quote - 0x9a: 0x2501, - // box drawings heavy horizontal - 0x9b: 0xa9, - // copyright sign - 0x9c: 0x2120, - // Service mark - 0x9d: 0x2022, - // (round) bullet - 0x9e: 0x201c, - // Left double quotation mark - 0x9f: 0x201d, - // Right double quotation mark - 0xa0: 0xc0, - // uppercase A, grave accent - 0xa1: 0xc2, - // uppercase A, circumflex - 0xa2: 0xc7, - // uppercase C with cedilla - 0xa3: 0xc8, - // uppercase E, grave accent - 0xa4: 0xca, - // uppercase E, circumflex - 0xa5: 0xcb, - // capital letter E with diaresis - 0xa6: 0xeb, - // lowercase letter e with diaresis - 0xa7: 0xce, - // uppercase I, circumflex - 0xa8: 0xcf, - // uppercase I, with diaresis - 0xa9: 0xef, - // lowercase i, with diaresis - 0xaa: 0xd4, - // uppercase O, circumflex - 0xab: 0xd9, - // uppercase U, grave accent - 0xac: 0xf9, - // lowercase u, grave accent - 0xad: 0xdb, - // uppercase U, circumflex - 0xae: 0xab, - // left-pointing double angle quotation mark - 0xaf: 0xbb, - // right-pointing double angle quotation mark - // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS - // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F - 0xb0: 0xc3, - // Uppercase A, tilde - 0xb1: 0xe3, - // Lowercase a, tilde - 0xb2: 0xcd, - // Uppercase I, acute accent - 0xb3: 0xcc, - // Uppercase I, grave accent - 0xb4: 0xec, - // Lowercase i, grave accent - 0xb5: 0xd2, - // Uppercase O, grave accent - 0xb6: 0xf2, - // Lowercase o, grave accent - 0xb7: 0xd5, - // Uppercase O, tilde - 0xb8: 0xf5, - // Lowercase o, tilde - 0xb9: 0x7b, - // Open curly brace - 0xba: 0x7d, - // Closing curly brace - 0xbb: 0x5c, - // Backslash - 0xbc: 0x5e, - // Caret - 0xbd: 0x5f, - // Underscore - 0xbe: 0x7c, - // Pipe (vertical line) - 0xbf: 0x223c, - // Tilde operator - 0xc0: 0xc4, - // Uppercase A, umlaut - 0xc1: 0xe4, - // Lowercase A, umlaut - 0xc2: 0xd6, - // Uppercase O, umlaut - 0xc3: 0xf6, - // Lowercase o, umlaut - 0xc4: 0xdf, - // Esszett (sharp S) - 0xc5: 0xa5, - // Yen symbol - 0xc6: 0xa4, - // Generic currency sign - 0xc7: 0x2503, - // Box drawings heavy vertical - 0xc8: 0xc5, - // Uppercase A, ring - 0xc9: 0xe5, - // Lowercase A, ring - 0xca: 0xd8, - // Uppercase O, stroke - 0xcb: 0xf8, - // Lowercase o, strok - 0xcc: 0x250f, - // Box drawings heavy down and right - 0xcd: 0x2513, - // Box drawings heavy down and left - 0xce: 0x2517, - // Box drawings heavy up and right - 0xcf: 0x251b // Box drawings heavy up and left - }; - - /** - * Utils - */ - var getCharForByte = function getCharForByte(_byte) { - return String.fromCharCode(specialCea608CharsCodes[_byte] || _byte); - }; - var NR_ROWS = 15; - var NR_COLS = 100; - // Tables to look up row from PAC data - var rowsLowCh1 = { - 0x11: 1, - 0x12: 3, - 0x15: 5, - 0x16: 7, - 0x17: 9, - 0x10: 11, - 0x13: 12, - 0x14: 14 - }; - var rowsHighCh1 = { - 0x11: 2, - 0x12: 4, - 0x15: 6, - 0x16: 8, - 0x17: 10, - 0x13: 13, - 0x14: 15 - }; - var rowsLowCh2 = { - 0x19: 1, - 0x1a: 3, - 0x1d: 5, - 0x1e: 7, - 0x1f: 9, - 0x18: 11, - 0x1b: 12, - 0x1c: 14 - }; - var rowsHighCh2 = { - 0x19: 2, - 0x1a: 4, - 0x1d: 6, - 0x1e: 8, - 0x1f: 10, - 0x1b: 13, - 0x1c: 15 - }; - var backgroundColors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'black', 'transparent']; - var CaptionsLogger = /*#__PURE__*/function () { - function CaptionsLogger() { - this.time = null; - this.verboseLevel = 0; - } - var _proto = CaptionsLogger.prototype; - _proto.log = function log(severity, msg) { - if (this.verboseLevel >= severity) { - var m = typeof msg === 'function' ? msg() : msg; - logger.log(this.time + " [" + severity + "] " + m); - } - }; - return CaptionsLogger; - }(); - var numArrayToHexArray = function numArrayToHexArray(numArray) { - var hexArray = []; - for (var j = 0; j < numArray.length; j++) { - hexArray.push(numArray[j].toString(16)); - } - return hexArray; - }; - var PenState = /*#__PURE__*/function () { - function PenState() { - this.foreground = 'white'; - this.underline = false; - this.italics = false; - this.background = 'black'; - this.flash = false; - } - var _proto2 = PenState.prototype; - _proto2.reset = function reset() { - this.foreground = 'white'; - this.underline = false; - this.italics = false; - this.background = 'black'; - this.flash = false; - }; - _proto2.setStyles = function setStyles(styles) { - var attribs = ['foreground', 'underline', 'italics', 'background', 'flash']; - for (var i = 0; i < attribs.length; i++) { - var style = attribs[i]; - if (styles.hasOwnProperty(style)) { - this[style] = styles[style]; - } - } - }; - _proto2.isDefault = function isDefault() { - return this.foreground === 'white' && !this.underline && !this.italics && this.background === 'black' && !this.flash; - }; - _proto2.equals = function equals(other) { - return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash; - }; - _proto2.copy = function copy(newPenState) { - this.foreground = newPenState.foreground; - this.underline = newPenState.underline; - this.italics = newPenState.italics; - this.background = newPenState.background; - this.flash = newPenState.flash; - }; - _proto2.toString = function toString() { - return 'color=' + this.foreground + ', underline=' + this.underline + ', italics=' + this.italics + ', background=' + this.background + ', flash=' + this.flash; - }; - return PenState; - }(); - /** - * Unicode character with styling and background. - * @constructor - */ - var StyledUnicodeChar = /*#__PURE__*/function () { - function StyledUnicodeChar() { - this.uchar = ' '; - this.penState = new PenState(); - } - var _proto3 = StyledUnicodeChar.prototype; - _proto3.reset = function reset() { - this.uchar = ' '; - this.penState.reset(); - }; - _proto3.setChar = function setChar(uchar, newPenState) { - this.uchar = uchar; - this.penState.copy(newPenState); - }; - _proto3.setPenState = function setPenState(newPenState) { - this.penState.copy(newPenState); - }; - _proto3.equals = function equals(other) { - return this.uchar === other.uchar && this.penState.equals(other.penState); - }; - _proto3.copy = function copy(newChar) { - this.uchar = newChar.uchar; - this.penState.copy(newChar.penState); - }; - _proto3.isEmpty = function isEmpty() { - return this.uchar === ' ' && this.penState.isDefault(); - }; - return StyledUnicodeChar; - }(); - /** - * CEA-608 row consisting of NR_COLS instances of StyledUnicodeChar. - * @constructor - */ - var Row = /*#__PURE__*/function () { - function Row(logger) { - this.chars = []; - this.pos = 0; - this.currPenState = new PenState(); - this.cueStartTime = null; - this.logger = void 0; - for (var i = 0; i < NR_COLS; i++) { - this.chars.push(new StyledUnicodeChar()); - } - this.logger = logger; - } - var _proto4 = Row.prototype; - _proto4.equals = function equals(other) { - for (var i = 0; i < NR_COLS; i++) { - if (!this.chars[i].equals(other.chars[i])) { - return false; - } - } - return true; - }; - _proto4.copy = function copy(other) { - for (var i = 0; i < NR_COLS; i++) { - this.chars[i].copy(other.chars[i]); - } - }; - _proto4.isEmpty = function isEmpty() { - var empty = true; - for (var i = 0; i < NR_COLS; i++) { - if (!this.chars[i].isEmpty()) { - empty = false; - break; - } - } - return empty; - } - - /** - * Set the cursor to a valid column. - */; - _proto4.setCursor = function setCursor(absPos) { - if (this.pos !== absPos) { - this.pos = absPos; - } - if (this.pos < 0) { - this.logger.log(3, 'Negative cursor position ' + this.pos); - this.pos = 0; - } else if (this.pos > NR_COLS) { - this.logger.log(3, 'Too large cursor position ' + this.pos); - this.pos = NR_COLS; - } - } - - /** - * Move the cursor relative to current position. - */; - _proto4.moveCursor = function moveCursor(relPos) { - var newPos = this.pos + relPos; - if (relPos > 1) { - for (var i = this.pos + 1; i < newPos + 1; i++) { - this.chars[i].setPenState(this.currPenState); - } - } - this.setCursor(newPos); - } - - /** - * Backspace, move one step back and clear character. - */; - _proto4.backSpace = function backSpace() { - this.moveCursor(-1); - this.chars[this.pos].setChar(' ', this.currPenState); - }; - _proto4.insertChar = function insertChar(_byte2) { - var _this = this; - if (_byte2 >= 0x90) { - // Extended char - this.backSpace(); - } - var _char = getCharForByte(_byte2); - if (this.pos >= NR_COLS) { - this.logger.log(0, function () { - return 'Cannot insert ' + _byte2.toString(16) + ' (' + _char + ') at position ' + _this.pos + '. Skipping it!'; - }); - return; - } - this.chars[this.pos].setChar(_char, this.currPenState); - this.moveCursor(1); - }; - _proto4.clearFromPos = function clearFromPos(startPos) { - var i; - for (i = startPos; i < NR_COLS; i++) { - this.chars[i].reset(); - } - }; - _proto4.clear = function clear() { - this.clearFromPos(0); - this.pos = 0; - this.currPenState.reset(); - }; - _proto4.clearToEndOfRow = function clearToEndOfRow() { - this.clearFromPos(this.pos); - }; - _proto4.getTextString = function getTextString() { - var chars = []; - var empty = true; - for (var i = 0; i < NR_COLS; i++) { - var _char2 = this.chars[i].uchar; - if (_char2 !== ' ') { - empty = false; - } - chars.push(_char2); - } - if (empty) { - return ''; - } else { - return chars.join(''); - } - }; - _proto4.setPenStyles = function setPenStyles(styles) { - this.currPenState.setStyles(styles); - var currChar = this.chars[this.pos]; - currChar.setPenState(this.currPenState); - }; - return Row; - }(); - - /** - * Keep a CEA-608 screen of 32x15 styled characters - * @constructor - */ - var CaptionScreen = /*#__PURE__*/function () { - function CaptionScreen(logger) { - this.rows = []; - this.currRow = NR_ROWS - 1; - this.nrRollUpRows = null; - this.lastOutputScreen = null; - this.logger = void 0; - for (var i = 0; i < NR_ROWS; i++) { - this.rows.push(new Row(logger)); - } - this.logger = logger; - } - var _proto5 = CaptionScreen.prototype; - _proto5.reset = function reset() { - for (var i = 0; i < NR_ROWS; i++) { - this.rows[i].clear(); - } - this.currRow = NR_ROWS - 1; - }; - _proto5.equals = function equals(other) { - var equal = true; - for (var i = 0; i < NR_ROWS; i++) { - if (!this.rows[i].equals(other.rows[i])) { - equal = false; - break; - } - } - return equal; - }; - _proto5.copy = function copy(other) { - for (var i = 0; i < NR_ROWS; i++) { - this.rows[i].copy(other.rows[i]); - } - }; - _proto5.isEmpty = function isEmpty() { - var empty = true; - for (var i = 0; i < NR_ROWS; i++) { - if (!this.rows[i].isEmpty()) { - empty = false; - break; - } - } - return empty; - }; - _proto5.backSpace = function backSpace() { - var row = this.rows[this.currRow]; - row.backSpace(); - }; - _proto5.clearToEndOfRow = function clearToEndOfRow() { - var row = this.rows[this.currRow]; - row.clearToEndOfRow(); - } - - /** - * Insert a character (without styling) in the current row. - */; - _proto5.insertChar = function insertChar(_char3) { - var row = this.rows[this.currRow]; - row.insertChar(_char3); - }; - _proto5.setPen = function setPen(styles) { - var row = this.rows[this.currRow]; - row.setPenStyles(styles); - }; - _proto5.moveCursor = function moveCursor(relPos) { - var row = this.rows[this.currRow]; - row.moveCursor(relPos); - }; - _proto5.setCursor = function setCursor(absPos) { - this.logger.log(2, 'setCursor: ' + absPos); - var row = this.rows[this.currRow]; - row.setCursor(absPos); - }; - _proto5.setPAC = function setPAC(pacData) { - this.logger.log(2, function () { - return 'pacData = ' + JSON.stringify(pacData); - }); - var newRow = pacData.row - 1; - if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) { - newRow = this.nrRollUpRows - 1; - } - - // Make sure this only affects Roll-up Captions by checking this.nrRollUpRows - if (this.nrRollUpRows && this.currRow !== newRow) { - // clear all rows first - for (var i = 0; i < NR_ROWS; i++) { - this.rows[i].clear(); - } - - // Copy this.nrRollUpRows rows from lastOutputScreen and place it in the newRow location - // topRowIndex - the start of rows to copy (inclusive index) - var topRowIndex = this.currRow + 1 - this.nrRollUpRows; - // We only copy if the last position was already shown. - // We use the cueStartTime value to check this. - var lastOutputScreen = this.lastOutputScreen; - if (lastOutputScreen) { - var prevLineTime = lastOutputScreen.rows[topRowIndex].cueStartTime; - var time = this.logger.time; - if (prevLineTime !== null && time !== null && prevLineTime < time) { - for (var _i = 0; _i < this.nrRollUpRows; _i++) { - this.rows[newRow - this.nrRollUpRows + _i + 1].copy(lastOutputScreen.rows[topRowIndex + _i]); - } - } - } - } - this.currRow = newRow; - var row = this.rows[this.currRow]; - if (pacData.indent !== null) { - var indent = pacData.indent; - var prevPos = Math.max(indent - 1, 0); - row.setCursor(pacData.indent); - pacData.color = row.chars[prevPos].penState.foreground; - } - var styles = { - foreground: pacData.color, - underline: pacData.underline, - italics: pacData.italics, - background: 'black', - flash: false - }; - this.setPen(styles); - } - - /** - * Set background/extra foreground, but first do back_space, and then insert space (backwards compatibility). - */; - _proto5.setBkgData = function setBkgData(bkgData) { - this.logger.log(2, function () { - return 'bkgData = ' + JSON.stringify(bkgData); - }); - this.backSpace(); - this.setPen(bkgData); - this.insertChar(0x20); // Space - }; - _proto5.setRollUpRows = function setRollUpRows(nrRows) { - this.nrRollUpRows = nrRows; - }; - _proto5.rollUp = function rollUp() { - var _this2 = this; - if (this.nrRollUpRows === null) { - this.logger.log(3, 'roll_up but nrRollUpRows not set yet'); - return; // Not properly setup - } - this.logger.log(1, function () { - return _this2.getDisplayText(); - }); - var topRowIndex = this.currRow + 1 - this.nrRollUpRows; - var topRow = this.rows.splice(topRowIndex, 1)[0]; - topRow.clear(); - this.rows.splice(this.currRow, 0, topRow); - this.logger.log(2, 'Rolling up'); - // this.logger.log(VerboseLevel.TEXT, this.get_display_text()) - } - - /** - * Get all non-empty rows with as unicode text. - */; - _proto5.getDisplayText = function getDisplayText(asOneRow) { - asOneRow = asOneRow || false; - var displayText = []; - var text = ''; - var rowNr = -1; - for (var i = 0; i < NR_ROWS; i++) { - var rowText = this.rows[i].getTextString(); - if (rowText) { - rowNr = i + 1; - if (asOneRow) { - displayText.push('Row ' + rowNr + ": '" + rowText + "'"); - } else { - displayText.push(rowText.trim()); - } - } - } - if (displayText.length > 0) { - if (asOneRow) { - text = '[' + displayText.join(' | ') + ']'; - } else { - text = displayText.join('\n'); - } - } - return text; - }; - _proto5.getTextAndFormat = function getTextAndFormat() { - return this.rows; - }; - return CaptionScreen; - }(); - - // var modes = ['MODE_ROLL-UP', 'MODE_POP-ON', 'MODE_PAINT-ON', 'MODE_TEXT']; - var Cea608Channel = /*#__PURE__*/function () { - function Cea608Channel(channelNumber, outputFilter, logger) { - this.chNr = void 0; - this.outputFilter = void 0; - this.mode = void 0; - this.verbose = void 0; - this.displayedMemory = void 0; - this.nonDisplayedMemory = void 0; - this.lastOutputScreen = void 0; - this.currRollUpRow = void 0; - this.writeScreen = void 0; - this.cueStartTime = void 0; - this.logger = void 0; - this.chNr = channelNumber; - this.outputFilter = outputFilter; - this.mode = null; - this.verbose = 0; - this.displayedMemory = new CaptionScreen(logger); - this.nonDisplayedMemory = new CaptionScreen(logger); - this.lastOutputScreen = new CaptionScreen(logger); - this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1]; - this.writeScreen = this.displayedMemory; - this.mode = null; - this.cueStartTime = null; // Keeps track of where a cue started. - this.logger = logger; - } - var _proto6 = Cea608Channel.prototype; - _proto6.reset = function reset() { - this.mode = null; - this.displayedMemory.reset(); - this.nonDisplayedMemory.reset(); - this.lastOutputScreen.reset(); - this.outputFilter.reset(); - this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1]; - this.writeScreen = this.displayedMemory; - this.mode = null; - this.cueStartTime = null; - }; - _proto6.getHandler = function getHandler() { - return this.outputFilter; - }; - _proto6.setHandler = function setHandler(newHandler) { - this.outputFilter = newHandler; - }; - _proto6.setPAC = function setPAC(pacData) { - this.writeScreen.setPAC(pacData); - }; - _proto6.setBkgData = function setBkgData(bkgData) { - this.writeScreen.setBkgData(bkgData); - }; - _proto6.setMode = function setMode(newMode) { - if (newMode === this.mode) { - return; - } - this.mode = newMode; - this.logger.log(2, function () { - return 'MODE=' + newMode; - }); - if (this.mode === 'MODE_POP-ON') { - this.writeScreen = this.nonDisplayedMemory; - } else { - this.writeScreen = this.displayedMemory; - this.writeScreen.reset(); - } - if (this.mode !== 'MODE_ROLL-UP') { - this.displayedMemory.nrRollUpRows = null; - this.nonDisplayedMemory.nrRollUpRows = null; - } - this.mode = newMode; - }; - _proto6.insertChars = function insertChars(chars) { - var _this3 = this; - for (var i = 0; i < chars.length; i++) { - this.writeScreen.insertChar(chars[i]); - } - var screen = this.writeScreen === this.displayedMemory ? 'DISP' : 'NON_DISP'; - this.logger.log(2, function () { - return screen + ': ' + _this3.writeScreen.getDisplayText(true); - }); - if (this.mode === 'MODE_PAINT-ON' || this.mode === 'MODE_ROLL-UP') { - this.logger.log(1, function () { - return 'DISPLAYED: ' + _this3.displayedMemory.getDisplayText(true); - }); - this.outputDataUpdate(); - } - }; - _proto6.ccRCL = function ccRCL() { - // Resume Caption Loading (switch mode to Pop On) - this.logger.log(2, 'RCL - Resume Caption Loading'); - this.setMode('MODE_POP-ON'); - }; - _proto6.ccBS = function ccBS() { - // BackSpace - this.logger.log(2, 'BS - BackSpace'); - if (this.mode === 'MODE_TEXT') { - return; - } - this.writeScreen.backSpace(); - if (this.writeScreen === this.displayedMemory) { - this.outputDataUpdate(); - } - }; - _proto6.ccAOF = function ccAOF() { - // Reserved (formerly Alarm Off) - }; - _proto6.ccAON = function ccAON() { - // Reserved (formerly Alarm On) - }; - _proto6.ccDER = function ccDER() { - // Delete to End of Row - this.logger.log(2, 'DER- Delete to End of Row'); - this.writeScreen.clearToEndOfRow(); - this.outputDataUpdate(); - }; - _proto6.ccRU = function ccRU(nrRows) { - // Roll-Up Captions-2,3,or 4 Rows - this.logger.log(2, 'RU(' + nrRows + ') - Roll Up'); - this.writeScreen = this.displayedMemory; - this.setMode('MODE_ROLL-UP'); - this.writeScreen.setRollUpRows(nrRows); - }; - _proto6.ccFON = function ccFON() { - // Flash On - this.logger.log(2, 'FON - Flash On'); - this.writeScreen.setPen({ - flash: true - }); - }; - _proto6.ccRDC = function ccRDC() { - // Resume Direct Captioning (switch mode to PaintOn) - this.logger.log(2, 'RDC - Resume Direct Captioning'); - this.setMode('MODE_PAINT-ON'); - }; - _proto6.ccTR = function ccTR() { - // Text Restart in text mode (not supported, however) - this.logger.log(2, 'TR'); - this.setMode('MODE_TEXT'); - }; - _proto6.ccRTD = function ccRTD() { - // Resume Text Display in Text mode (not supported, however) - this.logger.log(2, 'RTD'); - this.setMode('MODE_TEXT'); - }; - _proto6.ccEDM = function ccEDM() { - // Erase Displayed Memory - this.logger.log(2, 'EDM - Erase Displayed Memory'); - this.displayedMemory.reset(); - this.outputDataUpdate(true); - }; - _proto6.ccCR = function ccCR() { - // Carriage Return - this.logger.log(2, 'CR - Carriage Return'); - this.writeScreen.rollUp(); - this.outputDataUpdate(true); - }; - _proto6.ccENM = function ccENM() { - // Erase Non-Displayed Memory - this.logger.log(2, 'ENM - Erase Non-displayed Memory'); - this.nonDisplayedMemory.reset(); - }; - _proto6.ccEOC = function ccEOC() { - var _this4 = this; - // End of Caption (Flip Memories) - this.logger.log(2, 'EOC - End Of Caption'); - if (this.mode === 'MODE_POP-ON') { - var tmp = this.displayedMemory; - this.displayedMemory = this.nonDisplayedMemory; - this.nonDisplayedMemory = tmp; - this.writeScreen = this.nonDisplayedMemory; - this.logger.log(1, function () { - return 'DISP: ' + _this4.displayedMemory.getDisplayText(); - }); - } - this.outputDataUpdate(true); - }; - _proto6.ccTO = function ccTO(nrCols) { - // Tab Offset 1,2, or 3 columns - this.logger.log(2, 'TO(' + nrCols + ') - Tab Offset'); - this.writeScreen.moveCursor(nrCols); - }; - _proto6.ccMIDROW = function ccMIDROW(secondByte) { - // Parse MIDROW command - var styles = { - flash: false - }; - styles.underline = secondByte % 2 === 1; - styles.italics = secondByte >= 0x2e; - if (!styles.italics) { - var colorIndex = Math.floor(secondByte / 2) - 0x10; - var colors = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta']; - styles.foreground = colors[colorIndex]; - } else { - styles.foreground = 'white'; - } - this.logger.log(2, 'MIDROW: ' + JSON.stringify(styles)); - this.writeScreen.setPen(styles); - }; - _proto6.outputDataUpdate = function outputDataUpdate(dispatch) { - if (dispatch === void 0) { - dispatch = false; - } - var time = this.logger.time; - if (time === null) { - return; - } - if (this.outputFilter) { - if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) { - // Start of a new cue - this.cueStartTime = time; - } else { - if (!this.displayedMemory.equals(this.lastOutputScreen)) { - this.outputFilter.newCue(this.cueStartTime, time, this.lastOutputScreen); - if (dispatch && this.outputFilter.dispatchCue) { - this.outputFilter.dispatchCue(); - } - this.cueStartTime = this.displayedMemory.isEmpty() ? null : time; - } - } - this.lastOutputScreen.copy(this.displayedMemory); - } - }; - _proto6.cueSplitAtTime = function cueSplitAtTime(t) { - if (this.outputFilter) { - if (!this.displayedMemory.isEmpty()) { - if (this.outputFilter.newCue) { - this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory); - } - this.cueStartTime = t; - } - } - }; - return Cea608Channel; - }(); // Will be 1 or 2 when parsing captions - var Cea608Parser = /*#__PURE__*/function () { - function Cea608Parser(field, out1, out2) { - this.channels = void 0; - this.currentChannel = 0; - this.cmdHistory = createCmdHistory(); - this.logger = void 0; - var logger = this.logger = new CaptionsLogger(); - this.channels = [null, new Cea608Channel(field, out1, logger), new Cea608Channel(field + 1, out2, logger)]; - } - var _proto7 = Cea608Parser.prototype; - _proto7.getHandler = function getHandler(channel) { - return this.channels[channel].getHandler(); - }; - _proto7.setHandler = function setHandler(channel, newHandler) { - this.channels[channel].setHandler(newHandler); - } - - /** - * Add data for time t in forms of list of bytes (unsigned ints). The bytes are treated as pairs. - */; - _proto7.addData = function addData(time, byteList) { - var _this5 = this; - this.logger.time = time; - var _loop = function _loop(i) { - var a = byteList[i] & 0x7f; - var b = byteList[i + 1] & 0x7f; - var cmdFound = false; - var charsFound = null; - if (a === 0 && b === 0) { - return 0; // continue - } else { - _this5.logger.log(3, function () { - return '[' + numArrayToHexArray([byteList[i], byteList[i + 1]]) + '] -> (' + numArrayToHexArray([a, b]) + ')'; - }); - } - var cmdHistory = _this5.cmdHistory; - var isControlCode = a >= 0x10 && a <= 0x1f; - if (isControlCode) { - // Skip redundant control codes - if (hasCmdRepeated(a, b, cmdHistory)) { - setLastCmd(null, null, cmdHistory); - _this5.logger.log(3, function () { - return 'Repeated command (' + numArrayToHexArray([a, b]) + ') is dropped'; - }); - return 0; // continue - } - setLastCmd(a, b, _this5.cmdHistory); - cmdFound = _this5.parseCmd(a, b); - if (!cmdFound) { - cmdFound = _this5.parseMidrow(a, b); - } - if (!cmdFound) { - cmdFound = _this5.parsePAC(a, b); - } - if (!cmdFound) { - cmdFound = _this5.parseBackgroundAttributes(a, b); - } - } else { - setLastCmd(null, null, cmdHistory); - } - if (!cmdFound) { - charsFound = _this5.parseChars(a, b); - if (charsFound) { - var currChNr = _this5.currentChannel; - if (currChNr && currChNr > 0) { - var channel = _this5.channels[currChNr]; - channel.insertChars(charsFound); - } else { - _this5.logger.log(2, 'No channel found yet. TEXT-MODE?'); - } - } - } - if (!cmdFound && !charsFound) { - _this5.logger.log(2, function () { - return "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + ' orig: ' + numArrayToHexArray([byteList[i], byteList[i + 1]]); - }); - } - }, - _ret; - for (var i = 0; i < byteList.length; i += 2) { - _ret = _loop(i); - if (_ret === 0) continue; - } - } - - /** - * Parse Command. - * @returns True if a command was found - */; - _proto7.parseCmd = function parseCmd(a, b) { - var cond1 = (a === 0x14 || a === 0x1c || a === 0x15 || a === 0x1d) && b >= 0x20 && b <= 0x2f; - var cond2 = (a === 0x17 || a === 0x1f) && b >= 0x21 && b <= 0x23; - if (!(cond1 || cond2)) { - return false; - } - var chNr = a === 0x14 || a === 0x15 || a === 0x17 ? 1 : 2; - var channel = this.channels[chNr]; - if (a === 0x14 || a === 0x15 || a === 0x1c || a === 0x1d) { - if (b === 0x20) { - channel.ccRCL(); - } else if (b === 0x21) { - channel.ccBS(); - } else if (b === 0x22) { - channel.ccAOF(); - } else if (b === 0x23) { - channel.ccAON(); - } else if (b === 0x24) { - channel.ccDER(); - } else if (b === 0x25) { - channel.ccRU(2); - } else if (b === 0x26) { - channel.ccRU(3); - } else if (b === 0x27) { - channel.ccRU(4); - } else if (b === 0x28) { - channel.ccFON(); - } else if (b === 0x29) { - channel.ccRDC(); - } else if (b === 0x2a) { - channel.ccTR(); - } else if (b === 0x2b) { - channel.ccRTD(); - } else if (b === 0x2c) { - channel.ccEDM(); - } else if (b === 0x2d) { - channel.ccCR(); - } else if (b === 0x2e) { - channel.ccENM(); - } else if (b === 0x2f) { - channel.ccEOC(); - } - } else { - // a == 0x17 || a == 0x1F - channel.ccTO(b - 0x20); - } - this.currentChannel = chNr; - return true; - } - - /** - * Parse midrow styling command - */; - _proto7.parseMidrow = function parseMidrow(a, b) { - var chNr = 0; - if ((a === 0x11 || a === 0x19) && b >= 0x20 && b <= 0x2f) { - if (a === 0x11) { - chNr = 1; - } else { - chNr = 2; - } - if (chNr !== this.currentChannel) { - this.logger.log(0, 'Mismatch channel in midrow parsing'); - return false; - } - var channel = this.channels[chNr]; - if (!channel) { - return false; - } - channel.ccMIDROW(b); - this.logger.log(3, function () { - return 'MIDROW (' + numArrayToHexArray([a, b]) + ')'; - }); - return true; - } - return false; - } - - /** - * Parse Preable Access Codes (Table 53). - * @returns {Boolean} Tells if PAC found - */; - _proto7.parsePAC = function parsePAC(a, b) { - var row; - var case1 = (a >= 0x11 && a <= 0x17 || a >= 0x19 && a <= 0x1f) && b >= 0x40 && b <= 0x7f; - var case2 = (a === 0x10 || a === 0x18) && b >= 0x40 && b <= 0x5f; - if (!(case1 || case2)) { - return false; - } - var chNr = a <= 0x17 ? 1 : 2; - if (b >= 0x40 && b <= 0x5f) { - row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a]; - } else { - // 0x60 <= b <= 0x7F - row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a]; - } - var channel = this.channels[chNr]; - if (!channel) { - return false; - } - channel.setPAC(this.interpretPAC(row, b)); - this.currentChannel = chNr; - return true; - } - - /** - * Interpret the second byte of the pac, and return the information. - * @returns pacData with style parameters - */; - _proto7.interpretPAC = function interpretPAC(row, _byte3) { - var pacIndex; - var pacData = { - color: null, - italics: false, - indent: null, - underline: false, - row: row - }; - if (_byte3 > 0x5f) { - pacIndex = _byte3 - 0x60; - } else { - pacIndex = _byte3 - 0x40; - } - pacData.underline = (pacIndex & 1) === 1; - if (pacIndex <= 0xd) { - pacData.color = ['white', 'green', 'blue', 'cyan', 'red', 'yellow', 'magenta', 'white'][Math.floor(pacIndex / 2)]; - } else if (pacIndex <= 0xf) { - pacData.italics = true; - pacData.color = 'white'; - } else { - pacData.indent = Math.floor((pacIndex - 0x10) / 2) * 4; - } - return pacData; // Note that row has zero offset. The spec uses 1. - } - - /** - * Parse characters. - * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise. - */; - _proto7.parseChars = function parseChars(a, b) { - var channelNr; - var charCodes = null; - var charCode1 = null; - if (a >= 0x19) { - channelNr = 2; - charCode1 = a - 8; - } else { - channelNr = 1; - charCode1 = a; - } - if (charCode1 >= 0x11 && charCode1 <= 0x13) { - // Special character - var oneCode; - if (charCode1 === 0x11) { - oneCode = b + 0x50; - } else if (charCode1 === 0x12) { - oneCode = b + 0x70; - } else { - oneCode = b + 0x90; - } - this.logger.log(2, function () { - return "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr; - }); - charCodes = [oneCode]; - } else if (a >= 0x20 && a <= 0x7f) { - charCodes = b === 0 ? [a] : [a, b]; - } - if (charCodes) { - this.logger.log(3, function () { - return 'Char codes = ' + numArrayToHexArray(charCodes).join(','); - }); - } - return charCodes; - } - - /** - * Parse extended background attributes as well as new foreground color black. - * @returns True if background attributes are found - */; - _proto7.parseBackgroundAttributes = function parseBackgroundAttributes(a, b) { - var case1 = (a === 0x10 || a === 0x18) && b >= 0x20 && b <= 0x2f; - var case2 = (a === 0x17 || a === 0x1f) && b >= 0x2d && b <= 0x2f; - if (!(case1 || case2)) { - return false; - } - var index; - var bkgData = {}; - if (a === 0x10 || a === 0x18) { - index = Math.floor((b - 0x20) / 2); - bkgData.background = backgroundColors[index]; - if (b % 2 === 1) { - bkgData.background = bkgData.background + '_semi'; - } - } else if (b === 0x2d) { - bkgData.background = 'transparent'; - } else { - bkgData.foreground = 'black'; - if (b === 0x2f) { - bkgData.underline = true; - } - } - var chNr = a <= 0x17 ? 1 : 2; - var channel = this.channels[chNr]; - channel.setBkgData(bkgData); - return true; - } - - /** - * Reset state of parser and its channels. - */; - _proto7.reset = function reset() { - for (var i = 0; i < Object.keys(this.channels).length; i++) { - var channel = this.channels[i]; - if (channel) { - channel.reset(); - } - } - setLastCmd(null, null, this.cmdHistory); - } - - /** - * Trigger the generation of a cue, and the start of a new one if displayScreens are not empty. - */; - _proto7.cueSplitAtTime = function cueSplitAtTime(t) { - for (var i = 0; i < this.channels.length; i++) { - var channel = this.channels[i]; - if (channel) { - channel.cueSplitAtTime(t); - } - } - }; - return Cea608Parser; - }(); - function setLastCmd(a, b, cmdHistory) { - cmdHistory.a = a; - cmdHistory.b = b; - } - function hasCmdRepeated(a, b, cmdHistory) { - return cmdHistory.a === a && cmdHistory.b === b; - } - function createCmdHistory() { - return { - a: null, - b: null - }; - } - - var OutputFilter = /*#__PURE__*/function () { - function OutputFilter(timelineController, trackName) { - this.timelineController = void 0; - this.cueRanges = []; - this.trackName = void 0; - this.startTime = null; - this.endTime = null; - this.screen = null; - this.timelineController = timelineController; - this.trackName = trackName; - } - var _proto = OutputFilter.prototype; - _proto.dispatchCue = function dispatchCue() { - if (this.startTime === null) { - return; - } - this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges); - this.startTime = null; - }; - _proto.newCue = function newCue(startTime, endTime, screen) { - if (this.startTime === null || this.startTime > startTime) { - this.startTime = startTime; - } - this.endTime = endTime; - this.screen = screen; - this.timelineController.createCaptionsTrack(this.trackName); - }; - _proto.reset = function reset() { - this.cueRanges = []; - this.startTime = null; - }; - return OutputFilter; - }(); - - /** - * Copyright 2013 vtt.js Contributors - * - * Licensed under the Apache License, Version 2.0 (the 'License'); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an 'AS IS' BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - var VTTCue = (function () { - if (optionalSelf != null && optionalSelf.VTTCue) { - return self.VTTCue; - } - var AllowedDirections = ['', 'lr', 'rl']; - var AllowedAlignments = ['start', 'middle', 'end', 'left', 'right']; - function isAllowedValue(allowed, value) { - if (typeof value !== 'string') { - return false; - } - // necessary for assuring the generic conforms to the Array interface - if (!Array.isArray(allowed)) { - return false; - } - // reset the type so that the next narrowing works well - var lcValue = value.toLowerCase(); - // use the allow list to narrow the type to a specific subset of strings - if (~allowed.indexOf(lcValue)) { - return lcValue; - } - return false; - } - function findDirectionSetting(value) { - return isAllowedValue(AllowedDirections, value); - } - function findAlignSetting(value) { - return isAllowedValue(AllowedAlignments, value); - } - function extend(obj) { - for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - rest[_key - 1] = arguments[_key]; - } - var i = 1; - for (; i < arguments.length; i++) { - var cobj = arguments[i]; - for (var p in cobj) { - obj[p] = cobj[p]; - } - } - return obj; - } - function VTTCue(startTime, endTime, text) { - var cue = this; - var baseObj = { - enumerable: true - }; - /** - * Shim implementation specific properties. These properties are not in - * the spec. - */ - - // Lets us know when the VTTCue's data has changed in such a way that we need - // to recompute its display state. This lets us compute its display state - // lazily. - cue.hasBeenReset = false; - - /** - * VTTCue and TextTrackCue properties - * http://dev.w3.org/html5/webvtt/#vttcue-interface - */ - - var _id = ''; - var _pauseOnExit = false; - var _startTime = startTime; - var _endTime = endTime; - var _text = text; - var _region = null; - var _vertical = ''; - var _snapToLines = true; - var _line = 'auto'; - var _lineAlign = 'start'; - var _position = 50; - var _positionAlign = 'middle'; - var _size = 50; - var _align = 'middle'; - Object.defineProperty(cue, 'id', extend({}, baseObj, { - get: function get() { - return _id; - }, - set: function set(value) { - _id = '' + value; - } - })); - Object.defineProperty(cue, 'pauseOnExit', extend({}, baseObj, { - get: function get() { - return _pauseOnExit; - }, - set: function set(value) { - _pauseOnExit = !!value; - } - })); - Object.defineProperty(cue, 'startTime', extend({}, baseObj, { - get: function get() { - return _startTime; - }, - set: function set(value) { - if (typeof value !== 'number') { - throw new TypeError('Start time must be set to a number.'); - } - _startTime = value; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'endTime', extend({}, baseObj, { - get: function get() { - return _endTime; - }, - set: function set(value) { - if (typeof value !== 'number') { - throw new TypeError('End time must be set to a number.'); - } - _endTime = value; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'text', extend({}, baseObj, { - get: function get() { - return _text; - }, - set: function set(value) { - _text = '' + value; - this.hasBeenReset = true; - } - })); - - // todo: implement VTTRegion polyfill? - Object.defineProperty(cue, 'region', extend({}, baseObj, { - get: function get() { - return _region; - }, - set: function set(value) { - _region = value; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'vertical', extend({}, baseObj, { - get: function get() { - return _vertical; - }, - set: function set(value) { - var setting = findDirectionSetting(value); - // Have to check for false because the setting an be an empty string. - if (setting === false) { - throw new SyntaxError('An invalid or illegal string was specified.'); - } - _vertical = setting; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'snapToLines', extend({}, baseObj, { - get: function get() { - return _snapToLines; - }, - set: function set(value) { - _snapToLines = !!value; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'line', extend({}, baseObj, { - get: function get() { - return _line; - }, - set: function set(value) { - if (typeof value !== 'number' && value !== 'auto') { - throw new SyntaxError('An invalid number or illegal string was specified.'); - } - _line = value; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'lineAlign', extend({}, baseObj, { - get: function get() { - return _lineAlign; - }, - set: function set(value) { - var setting = findAlignSetting(value); - if (!setting) { - throw new SyntaxError('An invalid or illegal string was specified.'); - } - _lineAlign = setting; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'position', extend({}, baseObj, { - get: function get() { - return _position; - }, - set: function set(value) { - if (value < 0 || value > 100) { - throw new Error('Position must be between 0 and 100.'); - } - _position = value; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'positionAlign', extend({}, baseObj, { - get: function get() { - return _positionAlign; - }, - set: function set(value) { - var setting = findAlignSetting(value); - if (!setting) { - throw new SyntaxError('An invalid or illegal string was specified.'); - } - _positionAlign = setting; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'size', extend({}, baseObj, { - get: function get() { - return _size; - }, - set: function set(value) { - if (value < 0 || value > 100) { - throw new Error('Size must be between 0 and 100.'); - } - _size = value; - this.hasBeenReset = true; - } - })); - Object.defineProperty(cue, 'align', extend({}, baseObj, { - get: function get() { - return _align; - }, - set: function set(value) { - var setting = findAlignSetting(value); - if (!setting) { - throw new SyntaxError('An invalid or illegal string was specified.'); - } - _align = setting; - this.hasBeenReset = true; - } - })); - - /** - * Other <track> spec defined properties - */ - - // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state - cue.displayState = undefined; - } - - /** - * VTTCue methods - */ - - VTTCue.prototype.getCueAsHTML = function () { - // Assume WebVTT.convertCueToDOMTree is on the global. - var WebVTT = self.WebVTT; - return WebVTT.convertCueToDOMTree(self, this.text); - }; - // this is a polyfill hack - return VTTCue; - })(); - - /* - * Source: https://github.com/mozilla/vtt.js/blob/master/dist/vtt.js - */ - - var StringDecoder = /*#__PURE__*/function () { - function StringDecoder() {} - var _proto = StringDecoder.prototype; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _proto.decode = function decode(data, options) { - if (!data) { - return ''; - } - if (typeof data !== 'string') { - throw new Error('Error - expected string data.'); - } - return decodeURIComponent(encodeURIComponent(data)); - }; - return StringDecoder; - }(); // Try to parse input as a time stamp. - function parseTimeStamp(input) { - function computeSeconds(h, m, s, f) { - return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + parseFloat(f || 0); - } - var m = input.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/); - if (!m) { - return null; - } - if (parseFloat(m[2]) > 59) { - // Timestamp takes the form of [hours]:[minutes].[milliseconds] - // First position is hours as it's over 59. - return computeSeconds(m[2], m[3], 0, m[4]); - } - // Timestamp takes the form of [hours (optional)]:[minutes]:[seconds].[milliseconds] - return computeSeconds(m[1], m[2], m[3], m[4]); - } - - // A settings object holds key/value pairs and will ignore anything but the first - // assignment to a specific key. - var Settings = /*#__PURE__*/function () { - function Settings() { - this.values = Object.create(null); - } - var _proto2 = Settings.prototype; - // Only accept the first assignment to any key. - _proto2.set = function set(k, v) { - if (!this.get(k) && v !== '') { - this.values[k] = v; - } - } - // Return the value for a key, or a default value. - // If 'defaultKey' is passed then 'dflt' is assumed to be an object with - // a number of possible default values as properties where 'defaultKey' is - // the key of the property that will be chosen; otherwise it's assumed to be - // a single value. - ; - _proto2.get = function get(k, dflt, defaultKey) { - if (defaultKey) { - return this.has(k) ? this.values[k] : dflt[defaultKey]; - } - return this.has(k) ? this.values[k] : dflt; - } - // Check whether we have a value for a key. - ; - _proto2.has = function has(k) { - return k in this.values; - } - // Accept a setting if its one of the given alternatives. - ; - _proto2.alt = function alt(k, v, a) { - for (var n = 0; n < a.length; ++n) { - if (v === a[n]) { - this.set(k, v); - break; - } - } - } - // Accept a setting if its a valid (signed) integer. - ; - _proto2.integer = function integer(k, v) { - if (/^-?\d+$/.test(v)) { - // integer - this.set(k, parseInt(v, 10)); - } - } - // Accept a setting if its a valid percentage. - ; - _proto2.percent = function percent(k, v) { - if (/^([\d]{1,3})(\.[\d]*)?%$/.test(v)) { - var percent = parseFloat(v); - if (percent >= 0 && percent <= 100) { - this.set(k, percent); - return true; - } - } - return false; - }; - return Settings; - }(); // Helper function to parse input into groups separated by 'groupDelim', and - // interpret each group as a key/value pair separated by 'keyValueDelim'. - function parseOptions(input, callback, keyValueDelim, groupDelim) { - var groups = groupDelim ? input.split(groupDelim) : [input]; - for (var i in groups) { - if (typeof groups[i] !== 'string') { - continue; - } - var kv = groups[i].split(keyValueDelim); - if (kv.length !== 2) { - continue; - } - var _k = kv[0]; - var _v = kv[1]; - callback(_k, _v); - } - } - var defaults = new VTTCue(0, 0, ''); - // 'middle' was changed to 'center' in the spec: https://github.com/w3c/webvtt/pull/244 - // Safari doesn't yet support this change, but FF and Chrome do. - var center = defaults.align === 'middle' ? 'middle' : 'center'; - function parseCue(input, cue, regionList) { - // Remember the original input if we need to throw an error. - var oInput = input; - // 4.1 WebVTT timestamp - function consumeTimeStamp() { - var ts = parseTimeStamp(input); - if (ts === null) { - throw new Error('Malformed timestamp: ' + oInput); - } - - // Remove time stamp from input. - input = input.replace(/^[^\sa-zA-Z-]+/, ''); - return ts; - } - - // 4.4.2 WebVTT cue settings - function consumeCueSettings(input, cue) { - var settings = new Settings(); - parseOptions(input, function (k, v) { - var vals; - switch (k) { - case 'region': - // Find the last region we parsed with the same region id. - for (var i = regionList.length - 1; i >= 0; i--) { - if (regionList[i].id === v) { - settings.set(k, regionList[i].region); - break; - } - } - break; - case 'vertical': - settings.alt(k, v, ['rl', 'lr']); - break; - case 'line': - vals = v.split(','); - settings.integer(k, vals[0]); - if (settings.percent(k, vals[0])) { - settings.set('snapToLines', false); - } - settings.alt(k, vals[0], ['auto']); - if (vals.length === 2) { - settings.alt('lineAlign', vals[1], ['start', center, 'end']); - } - break; - case 'position': - vals = v.split(','); - settings.percent(k, vals[0]); - if (vals.length === 2) { - settings.alt('positionAlign', vals[1], ['start', center, 'end', 'line-left', 'line-right', 'auto']); - } - break; - case 'size': - settings.percent(k, v); - break; - case 'align': - settings.alt(k, v, ['start', center, 'end', 'left', 'right']); - break; - } - }, /:/, /\s/); - - // Apply default values for any missing fields. - cue.region = settings.get('region', null); - cue.vertical = settings.get('vertical', ''); - var line = settings.get('line', 'auto'); - if (line === 'auto' && defaults.line === -1) { - // set numeric line number for Safari - line = -1; - } - cue.line = line; - cue.lineAlign = settings.get('lineAlign', 'start'); - cue.snapToLines = settings.get('snapToLines', true); - cue.size = settings.get('size', 100); - cue.align = settings.get('align', center); - var position = settings.get('position', 'auto'); - if (position === 'auto' && defaults.position === 50) { - // set numeric position for Safari - position = cue.align === 'start' || cue.align === 'left' ? 0 : cue.align === 'end' || cue.align === 'right' ? 100 : 50; - } - cue.position = position; - } - function skipWhitespace() { - input = input.replace(/^\s+/, ''); - } - - // 4.1 WebVTT cue timings. - skipWhitespace(); - cue.startTime = consumeTimeStamp(); // (1) collect cue start time - skipWhitespace(); - if (input.slice(0, 3) !== '-->') { - // (3) next characters must match '-->' - throw new Error("Malformed time stamp (time stamps must be separated by '-->'): " + oInput); - } - input = input.slice(3); - skipWhitespace(); - cue.endTime = consumeTimeStamp(); // (5) collect cue end time - - // 4.1 WebVTT cue settings list. - skipWhitespace(); - consumeCueSettings(input, cue); - } - function fixLineBreaks(input) { - return input.replace(/<br(?: \/)?>/gi, '\n'); - } - var VTTParser = /*#__PURE__*/function () { - function VTTParser() { - this.state = 'INITIAL'; - this.buffer = ''; - this.decoder = new StringDecoder(); - this.regionList = []; - this.cue = null; - this.oncue = void 0; - this.onparsingerror = void 0; - this.onflush = void 0; - } - var _proto3 = VTTParser.prototype; - _proto3.parse = function parse(data) { - var _this = this; - - // If there is no data then we won't decode it, but will just try to parse - // whatever is in buffer already. This may occur in circumstances, for - // example when flush() is called. - if (data) { - // Try to decode the data that we received. - _this.buffer += _this.decoder.decode(data, { - stream: true - }); - } - function collectNextLine() { - var buffer = _this.buffer; - var pos = 0; - buffer = fixLineBreaks(buffer); - while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') { - ++pos; - } - var line = buffer.slice(0, pos); - // Advance the buffer early in case we fail below. - if (buffer[pos] === '\r') { - ++pos; - } - if (buffer[pos] === '\n') { - ++pos; - } - _this.buffer = buffer.slice(pos); - return line; - } - - // 3.2 WebVTT metadata header syntax - function parseHeader(input) { - parseOptions(input, function (k, v) { - // switch (k) { - // case 'region': - // 3.3 WebVTT region metadata header syntax - // console.log('parse region', v); - // parseRegion(v); - // break; - // } - }, /:/); - } - - // 5.1 WebVTT file parsing. - try { - var line = ''; - if (_this.state === 'INITIAL') { - // We can't start parsing until we have the first line. - if (!/\r\n|\n/.test(_this.buffer)) { - return this; - } - line = collectNextLine(); - // strip of UTF-8 BOM if any - // https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 - var m = line.match(/^()?WEBVTT([ \t].*)?$/); - if (!(m != null && m[0])) { - throw new Error('Malformed WebVTT signature.'); - } - _this.state = 'HEADER'; - } - var alreadyCollectedLine = false; - while (_this.buffer) { - // We can't parse a line until we have the full line. - if (!/\r\n|\n/.test(_this.buffer)) { - return this; - } - if (!alreadyCollectedLine) { - line = collectNextLine(); - } else { - alreadyCollectedLine = false; - } - switch (_this.state) { - case 'HEADER': - // 13-18 - Allow a header (metadata) under the WEBVTT line. - if (/:/.test(line)) { - parseHeader(line); - } else if (!line) { - // An empty line terminates the header and starts the body (cues). - _this.state = 'ID'; - } - continue; - case 'NOTE': - // Ignore NOTE blocks. - if (!line) { - _this.state = 'ID'; - } - continue; - case 'ID': - // Check for the start of NOTE blocks. - if (/^NOTE($|[ \t])/.test(line)) { - _this.state = 'NOTE'; - break; - } - // 19-29 - Allow any number of line terminators, then initialize new cue values. - if (!line) { - continue; - } - _this.cue = new VTTCue(0, 0, ''); - _this.state = 'CUE'; - // 30-39 - Check if self line contains an optional identifier or timing data. - if (line.indexOf('-->') === -1) { - _this.cue.id = line; - continue; - } - // Process line as start of a cue. - /* falls through */ - case 'CUE': - // 40 - Collect cue timings and settings. - if (!_this.cue) { - _this.state = 'BADCUE'; - continue; - } - try { - parseCue(line, _this.cue, _this.regionList); - } catch (e) { - // In case of an error ignore rest of the cue. - _this.cue = null; - _this.state = 'BADCUE'; - continue; - } - _this.state = 'CUETEXT'; - continue; - case 'CUETEXT': - { - var hasSubstring = line.indexOf('-->') !== -1; - // 34 - If we have an empty line then report the cue. - // 35 - If we have the special substring '-->' then report the cue, - // but do not collect the line as we need to process the current - // one as a new cue. - if (!line || hasSubstring && (alreadyCollectedLine = true)) { - // We are done parsing self cue. - if (_this.oncue && _this.cue) { - _this.oncue(_this.cue); - } - _this.cue = null; - _this.state = 'ID'; - continue; - } - if (_this.cue === null) { - continue; - } - if (_this.cue.text) { - _this.cue.text += '\n'; - } - _this.cue.text += line; - } - continue; - case 'BADCUE': - // 54-62 - Collect and discard the remaining cue. - if (!line) { - _this.state = 'ID'; - } - } - } - } catch (e) { - // If we are currently parsing a cue, report what we have. - if (_this.state === 'CUETEXT' && _this.cue && _this.oncue) { - _this.oncue(_this.cue); - } - _this.cue = null; - // Enter BADWEBVTT state if header was not parsed correctly otherwise - // another exception occurred so enter BADCUE state. - _this.state = _this.state === 'INITIAL' ? 'BADWEBVTT' : 'BADCUE'; - } - return this; - }; - _proto3.flush = function flush() { - var _this = this; - try { - // Finish decoding the stream. - // _this.buffer += _this.decoder.decode(); - // Synthesize the end of the current cue or region. - if (_this.cue || _this.state === 'HEADER') { - _this.buffer += '\n\n'; - _this.parse(); - } - // If we've flushed, parsed, and we're still on the INITIAL state then - // that means we don't have enough of the stream to parse the first - // line. - if (_this.state === 'INITIAL' || _this.state === 'BADWEBVTT') { - throw new Error('Malformed WebVTT signature.'); - } - } catch (e) { - if (_this.onparsingerror) { - _this.onparsingerror(e); - } - } - if (_this.onflush) { - _this.onflush(); - } - return this; - }; - return VTTParser; - }(); - - var LINEBREAKS = /\r\n|\n\r|\n|\r/g; - - // String.prototype.startsWith is not supported in IE11 - var startsWith = function startsWith(inputString, searchString, position) { - if (position === void 0) { - position = 0; - } - return inputString.slice(position, position + searchString.length) === searchString; - }; - var cueString2millis = function cueString2millis(timeString) { - var ts = parseInt(timeString.slice(-3)); - var secs = parseInt(timeString.slice(-6, -4)); - var mins = parseInt(timeString.slice(-9, -7)); - var hours = timeString.length > 9 ? parseInt(timeString.substring(0, timeString.indexOf(':'))) : 0; - if (!isFiniteNumber(ts) || !isFiniteNumber(secs) || !isFiniteNumber(mins) || !isFiniteNumber(hours)) { - throw Error("Malformed X-TIMESTAMP-MAP: Local:" + timeString); - } - ts += 1000 * secs; - ts += 60 * 1000 * mins; - ts += 60 * 60 * 1000 * hours; - return ts; - }; - - // From https://github.com/darkskyapp/string-hash - var hash = function hash(text) { - var hash = 5381; - var i = text.length; - while (i) { - hash = hash * 33 ^ text.charCodeAt(--i); - } - return (hash >>> 0).toString(); - }; - - // Create a unique hash id for a cue based on start/end times and text. - // This helps timeline-controller to avoid showing repeated captions. - function generateCueId(startTime, endTime, text) { - return hash(startTime.toString()) + hash(endTime.toString()) + hash(text); - } - var calculateOffset = function calculateOffset(vttCCs, cc, presentationTime) { - var currCC = vttCCs[cc]; - var prevCC = vttCCs[currCC.prevCC]; - - // This is the first discontinuity or cues have been processed since the last discontinuity - // Offset = current discontinuity time - if (!prevCC || !prevCC.new && currCC.new) { - vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start; - currCC.new = false; - return; - } - - // There have been discontinuities since cues were last parsed. - // Offset = time elapsed - while ((_prevCC = prevCC) != null && _prevCC.new) { - var _prevCC; - vttCCs.ccOffset += currCC.start - prevCC.start; - currCC.new = false; - currCC = prevCC; - prevCC = vttCCs[currCC.prevCC]; - } - vttCCs.presentationOffset = presentationTime; - }; - function parseWebVTT(vttByteArray, initPTS, vttCCs, cc, timeOffset, callBack, errorCallBack) { - var parser = new VTTParser(); - // Convert byteArray into string, replacing any somewhat exotic linefeeds with "\n", then split on that character. - // Uint8Array.prototype.reduce is not implemented in IE11 - var vttLines = utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, '\n').split('\n'); - var cues = []; - var init90kHz = initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale) : 0; - var cueTime = '00:00.000'; - var timestampMapMPEGTS = 0; - var timestampMapLOCAL = 0; - var parsingError; - var inHeader = true; - parser.oncue = function (cue) { - // Adjust cue timing; clamp cues to start no earlier than - and drop cues that don't end after - 0 on timeline. - var currCC = vttCCs[cc]; - var cueOffset = vttCCs.ccOffset; - - // Calculate subtitle PTS offset - var webVttMpegTsMapOffset = (timestampMapMPEGTS - init90kHz) / 90000; - - // Update offsets for new discontinuities - if (currCC != null && currCC.new) { - if (timestampMapLOCAL !== undefined) { - // When local time is provided, offset = discontinuity start time - local time - cueOffset = vttCCs.ccOffset = currCC.start; - } else { - calculateOffset(vttCCs, cc, webVttMpegTsMapOffset); - } - } - if (webVttMpegTsMapOffset) { - if (!initPTS) { - parsingError = new Error('Missing initPTS for VTT MPEGTS'); - return; - } - // If we have MPEGTS, offset = presentation time + discontinuity offset - cueOffset = webVttMpegTsMapOffset - vttCCs.presentationOffset; - } - var duration = cue.endTime - cue.startTime; - var startTime = normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 90000, timeOffset * 90000) / 90000; - cue.startTime = Math.max(startTime, 0); - cue.endTime = Math.max(startTime + duration, 0); - - //trim trailing webvtt block whitespaces - var text = cue.text.trim(); - - // Fix encoding of special characters - cue.text = decodeURIComponent(encodeURIComponent(text)); - - // If the cue was not assigned an id from the VTT file (line above the content), create one. - if (!cue.id) { - cue.id = generateCueId(cue.startTime, cue.endTime, text); - } - if (cue.endTime > 0) { - cues.push(cue); - } - }; - parser.onparsingerror = function (error) { - parsingError = error; - }; - parser.onflush = function () { - if (parsingError) { - errorCallBack(parsingError); - return; - } - callBack(cues); - }; - - // Go through contents line by line. - vttLines.forEach(function (line) { - if (inHeader) { - // Look for X-TIMESTAMP-MAP in header. - if (startsWith(line, 'X-TIMESTAMP-MAP=')) { - // Once found, no more are allowed anyway, so stop searching. - inHeader = false; - // Extract LOCAL and MPEGTS. - line.slice(16).split(',').forEach(function (timestamp) { - if (startsWith(timestamp, 'LOCAL:')) { - cueTime = timestamp.slice(6); - } else if (startsWith(timestamp, 'MPEGTS:')) { - timestampMapMPEGTS = parseInt(timestamp.slice(7)); - } - }); - try { - // Convert cue time to seconds - timestampMapLOCAL = cueString2millis(cueTime) / 1000; - } catch (error) { - parsingError = error; - } - // Return without parsing X-TIMESTAMP-MAP line. - return; - } else if (line === '') { - inHeader = false; - } - } - // Parse line by default. - parser.parse(line + '\n'); - }); - parser.flush(); - } - - var IMSC1_CODEC = 'stpp.ttml.im1t'; - - // Time format: h:m:s:frames(.subframes) - var HMSF_REGEX = /^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/; - - // Time format: hours, minutes, seconds, milliseconds, frames, ticks - var TIME_UNIT_REGEX = /^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/; - var textAlignToLineAlign = { - left: 'start', - center: 'center', - right: 'end', - start: 'start', - end: 'end' - }; - function parseIMSC1(payload, initPTS, callBack, errorCallBack) { - var results = findBox(new Uint8Array(payload), ['mdat']); - if (results.length === 0) { - errorCallBack(new Error('Could not parse IMSC1 mdat')); - return; - } - var ttmlList = results.map(function (mdat) { - return utf8ArrayToStr(mdat); - }); - var syncTime = toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale); - try { - ttmlList.forEach(function (ttml) { - return callBack(parseTTML(ttml, syncTime)); - }); - } catch (error) { - errorCallBack(error); - } - } - function parseTTML(ttml, syncTime) { - var parser = new DOMParser(); - var xmlDoc = parser.parseFromString(ttml, 'text/xml'); - var tt = xmlDoc.getElementsByTagName('tt')[0]; - if (!tt) { - throw new Error('Invalid ttml'); - } - var defaultRateInfo = { - frameRate: 30, - subFrameRate: 1, - frameRateMultiplier: 0, - tickRate: 0 - }; - var rateInfo = Object.keys(defaultRateInfo).reduce(function (result, key) { - result[key] = tt.getAttribute("ttp:" + key) || defaultRateInfo[key]; - return result; - }, {}); - var trim = tt.getAttribute('xml:space') !== 'preserve'; - var styleElements = collectionToDictionary(getElementCollection(tt, 'styling', 'style')); - var regionElements = collectionToDictionary(getElementCollection(tt, 'layout', 'region')); - var cueElements = getElementCollection(tt, 'body', '[begin]'); - return [].map.call(cueElements, function (cueElement) { - var cueText = getTextContent(cueElement, trim); - if (!cueText || !cueElement.hasAttribute('begin')) { - return null; - } - var startTime = parseTtmlTime(cueElement.getAttribute('begin'), rateInfo); - var duration = parseTtmlTime(cueElement.getAttribute('dur'), rateInfo); - var endTime = parseTtmlTime(cueElement.getAttribute('end'), rateInfo); - if (startTime === null) { - throw timestampParsingError(cueElement); - } - if (endTime === null) { - if (duration === null) { - throw timestampParsingError(cueElement); - } - endTime = startTime + duration; - } - var cue = new VTTCue(startTime - syncTime, endTime - syncTime, cueText); - cue.id = generateCueId(cue.startTime, cue.endTime, cue.text); - var region = regionElements[cueElement.getAttribute('region')]; - var style = styleElements[cueElement.getAttribute('style')]; - - // Apply styles to cue - var styles = getTtmlStyles(region, style, styleElements); - var textAlign = styles.textAlign; - if (textAlign) { - // cue.positionAlign not settable in FF~2016 - var lineAlign = textAlignToLineAlign[textAlign]; - if (lineAlign) { - cue.lineAlign = lineAlign; - } - cue.align = textAlign; - } - _extends(cue, styles); - return cue; - }).filter(function (cue) { - return cue !== null; - }); - } - function getElementCollection(fromElement, parentName, childName) { - var parent = fromElement.getElementsByTagName(parentName)[0]; - if (parent) { - return [].slice.call(parent.querySelectorAll(childName)); - } - return []; - } - function collectionToDictionary(elementsWithId) { - return elementsWithId.reduce(function (dict, element) { - var id = element.getAttribute('xml:id'); - if (id) { - dict[id] = element; - } - return dict; - }, {}); - } - function getTextContent(element, trim) { - return [].slice.call(element.childNodes).reduce(function (str, node, i) { - var _node$childNodes; - if (node.nodeName === 'br' && i) { - return str + '\n'; - } - if ((_node$childNodes = node.childNodes) != null && _node$childNodes.length) { - return getTextContent(node, trim); - } else if (trim) { - return str + node.textContent.trim().replace(/\s+/g, ' '); - } - return str + node.textContent; - }, ''); - } - function getTtmlStyles(region, style, styleElements) { - var ttsNs = 'http://www.w3.org/ns/ttml#styling'; - var regionStyle = null; - var styleAttributes = ['displayAlign', 'textAlign', 'color', 'backgroundColor', 'fontSize', 'fontFamily' - // 'fontWeight', - // 'lineHeight', - // 'wrapOption', - // 'fontStyle', - // 'direction', - // 'writingMode' - ]; - var regionStyleName = region != null && region.hasAttribute('style') ? region.getAttribute('style') : null; - if (regionStyleName && styleElements.hasOwnProperty(regionStyleName)) { - regionStyle = styleElements[regionStyleName]; - } - return styleAttributes.reduce(function (styles, name) { - var value = getAttributeNS(style, ttsNs, name) || getAttributeNS(region, ttsNs, name) || getAttributeNS(regionStyle, ttsNs, name); - if (value) { - styles[name] = value; - } - return styles; - }, {}); - } - function getAttributeNS(element, ns, name) { - if (!element) { - return null; - } - return element.hasAttributeNS(ns, name) ? element.getAttributeNS(ns, name) : null; - } - function timestampParsingError(node) { - return new Error("Could not parse ttml timestamp " + node); - } - function parseTtmlTime(timeAttributeValue, rateInfo) { - if (!timeAttributeValue) { - return null; - } - var seconds = parseTimeStamp(timeAttributeValue); - if (seconds === null) { - if (HMSF_REGEX.test(timeAttributeValue)) { - seconds = parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo); - } else if (TIME_UNIT_REGEX.test(timeAttributeValue)) { - seconds = parseTimeUnits(timeAttributeValue, rateInfo); - } - } - return seconds; - } - function parseHoursMinutesSecondsFrames(timeAttributeValue, rateInfo) { - var m = HMSF_REGEX.exec(timeAttributeValue); - var frames = (m[4] | 0) + (m[5] | 0) / rateInfo.subFrameRate; - return (m[1] | 0) * 3600 + (m[2] | 0) * 60 + (m[3] | 0) + frames / rateInfo.frameRate; - } - function parseTimeUnits(timeAttributeValue, rateInfo) { - var m = TIME_UNIT_REGEX.exec(timeAttributeValue); - var value = Number(m[1]); - var unit = m[2]; - switch (unit) { - case 'h': - return value * 3600; - case 'm': - return value * 60; - case 'ms': - return value * 1000; - case 'f': - return value / rateInfo.frameRate; - case 't': - return value / rateInfo.tickRate; - } - return value; - } - - var TimelineController = /*#__PURE__*/function () { - function TimelineController(hls) { - this.hls = void 0; - this.media = null; - this.config = void 0; - this.enabled = true; - this.Cues = void 0; - this.textTracks = []; - this.tracks = []; - this.initPTS = []; - this.unparsedVttFrags = []; - this.captionsTracks = {}; - this.nonNativeCaptionsTracks = {}; - this.cea608Parser1 = void 0; - this.cea608Parser2 = void 0; - this.lastCc = -1; - // Last video (CEA-608) fragment CC - this.lastSn = -1; - // Last video (CEA-608) fragment MSN - this.lastPartIndex = -1; - // Last video (CEA-608) fragment Part Index - this.prevCC = -1; - // Last subtitle fragment CC - this.vttCCs = newVTTCCs(); - this.captionsProperties = void 0; - this.hls = hls; - this.config = hls.config; - this.Cues = hls.config.cueHandler; - this.captionsProperties = { - textTrack1: { - label: this.config.captionsTextTrack1Label, - languageCode: this.config.captionsTextTrack1LanguageCode - }, - textTrack2: { - label: this.config.captionsTextTrack2Label, - languageCode: this.config.captionsTextTrack2LanguageCode - }, - textTrack3: { - label: this.config.captionsTextTrack3Label, - languageCode: this.config.captionsTextTrack3LanguageCode - }, - textTrack4: { - label: this.config.captionsTextTrack4Label, - languageCode: this.config.captionsTextTrack4LanguageCode - } - }; - hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); - hls.on(Events.FRAG_LOADING, this.onFragLoading, this); - hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); - hls.on(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this); - hls.on(Events.FRAG_DECRYPTED, this.onFragDecrypted, this); - hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); - hls.on(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this); - hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - } - var _proto = TimelineController.prototype; - _proto.destroy = function destroy() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this); - hls.off(Events.FRAG_LOADING, this.onFragLoading, this); - hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); - hls.off(Events.FRAG_PARSING_USERDATA, this.onFragParsingUserdata, this); - hls.off(Events.FRAG_DECRYPTED, this.onFragDecrypted, this); - hls.off(Events.INIT_PTS_FOUND, this.onInitPtsFound, this); - hls.off(Events.SUBTITLE_TRACKS_CLEARED, this.onSubtitleTracksCleared, this); - hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - // @ts-ignore - this.hls = this.config = null; - this.cea608Parser1 = this.cea608Parser2 = undefined; - }; - _proto.initCea608Parsers = function initCea608Parsers() { - if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) { - var channel1 = new OutputFilter(this, 'textTrack1'); - var channel2 = new OutputFilter(this, 'textTrack2'); - var channel3 = new OutputFilter(this, 'textTrack3'); - var channel4 = new OutputFilter(this, 'textTrack4'); - this.cea608Parser1 = new Cea608Parser(1, channel1, channel2); - this.cea608Parser2 = new Cea608Parser(3, channel3, channel4); - } - }; - _proto.addCues = function addCues(trackName, startTime, endTime, screen, cueRanges) { - // skip cues which overlap more than 50% with previously parsed time ranges - var merged = false; - for (var i = cueRanges.length; i--;) { - var cueRange = cueRanges[i]; - var overlap = intersection(cueRange[0], cueRange[1], startTime, endTime); - if (overlap >= 0) { - cueRange[0] = Math.min(cueRange[0], startTime); - cueRange[1] = Math.max(cueRange[1], endTime); - merged = true; - if (overlap / (endTime - startTime) > 0.5) { - return; - } - } - } - if (!merged) { - cueRanges.push([startTime, endTime]); - } - if (this.config.renderTextTracksNatively) { - var track = this.captionsTracks[trackName]; - this.Cues.newCue(track, startTime, endTime, screen); - } else { - var cues = this.Cues.newCue(null, startTime, endTime, screen); - this.hls.trigger(Events.CUES_PARSED, { - type: 'captions', - cues: cues, - track: trackName - }); - } - } - - // Triggered when an initial PTS is found; used for synchronisation of WebVTT. - ; - _proto.onInitPtsFound = function onInitPtsFound(event, _ref) { - var _this = this; - var frag = _ref.frag, - id = _ref.id, - initPTS = _ref.initPTS, - timescale = _ref.timescale; - var unparsedVttFrags = this.unparsedVttFrags; - if (id === 'main') { - this.initPTS[frag.cc] = { - baseTime: initPTS, - timescale: timescale - }; - } - - // Due to asynchronous processing, initial PTS may arrive later than the first VTT fragments are loaded. - // Parse any unparsed fragments upon receiving the initial PTS. - if (unparsedVttFrags.length) { - this.unparsedVttFrags = []; - unparsedVttFrags.forEach(function (frag) { - _this.onFragLoaded(Events.FRAG_LOADED, frag); - }); - } - }; - _proto.getExistingTrack = function getExistingTrack(label, language) { - var media = this.media; - if (media) { - for (var i = 0; i < media.textTracks.length; i++) { - var textTrack = media.textTracks[i]; - if (canReuseVttTextTrack(textTrack, { - name: label, - lang: language, - attrs: {} - })) { - return textTrack; - } - } - } - return null; - }; - _proto.createCaptionsTrack = function createCaptionsTrack(trackName) { - if (this.config.renderTextTracksNatively) { - this.createNativeTrack(trackName); - } else { - this.createNonNativeTrack(trackName); - } - }; - _proto.createNativeTrack = function createNativeTrack(trackName) { - if (this.captionsTracks[trackName]) { - return; - } - var captionsProperties = this.captionsProperties, - captionsTracks = this.captionsTracks, - media = this.media; - var _captionsProperties$t = captionsProperties[trackName], - label = _captionsProperties$t.label, - languageCode = _captionsProperties$t.languageCode; - // Enable reuse of existing text track. - var existingTrack = this.getExistingTrack(label, languageCode); - if (!existingTrack) { - var textTrack = this.createTextTrack('captions', label, languageCode); - if (textTrack) { - // Set a special property on the track so we know it's managed by Hls.js - textTrack[trackName] = true; - captionsTracks[trackName] = textTrack; - } - } else { - captionsTracks[trackName] = existingTrack; - clearCurrentCues(captionsTracks[trackName]); - sendAddTrackEvent(captionsTracks[trackName], media); - } - }; - _proto.createNonNativeTrack = function createNonNativeTrack(trackName) { - if (this.nonNativeCaptionsTracks[trackName]) { - return; - } - // Create a list of a single track for the provider to consume - var trackProperties = this.captionsProperties[trackName]; - if (!trackProperties) { - return; - } - var label = trackProperties.label; - var track = { - _id: trackName, - label: label, - kind: 'captions', - default: trackProperties.media ? !!trackProperties.media.default : false, - closedCaptions: trackProperties.media - }; - this.nonNativeCaptionsTracks[trackName] = track; - this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { - tracks: [track] - }); - }; - _proto.createTextTrack = function createTextTrack(kind, label, lang) { - var media = this.media; - if (!media) { - return; - } - return media.addTextTrack(kind, label, lang); - }; - _proto.onMediaAttaching = function onMediaAttaching(event, data) { - this.media = data.media; - this._cleanTracks(); - }; - _proto.onMediaDetaching = function onMediaDetaching() { - var captionsTracks = this.captionsTracks; - Object.keys(captionsTracks).forEach(function (trackName) { - clearCurrentCues(captionsTracks[trackName]); - delete captionsTracks[trackName]; - }); - this.nonNativeCaptionsTracks = {}; - }; - _proto.onManifestLoading = function onManifestLoading() { - // Detect discontinuity in video fragment (CEA-608) parsing - this.lastCc = -1; - this.lastSn = -1; - this.lastPartIndex = -1; - // Detect discontinuity in subtitle manifests - this.prevCC = -1; - this.vttCCs = newVTTCCs(); - // Reset tracks - this._cleanTracks(); - this.tracks = []; - this.captionsTracks = {}; - this.nonNativeCaptionsTracks = {}; - this.textTracks = []; - this.unparsedVttFrags = []; - this.initPTS = []; - if (this.cea608Parser1 && this.cea608Parser2) { - this.cea608Parser1.reset(); - this.cea608Parser2.reset(); - } - }; - _proto._cleanTracks = function _cleanTracks() { - // clear outdated subtitles - var media = this.media; - if (!media) { - return; - } - var textTracks = media.textTracks; - if (textTracks) { - for (var i = 0; i < textTracks.length; i++) { - clearCurrentCues(textTracks[i]); - } - } - }; - _proto.onSubtitleTracksUpdated = function onSubtitleTracksUpdated(event, data) { - var _this2 = this; - var tracks = data.subtitleTracks || []; - var hasIMSC1 = tracks.some(function (track) { - return track.textCodec === IMSC1_CODEC; - }); - if (this.config.enableWebVTT || hasIMSC1 && this.config.enableIMSC1) { - var listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks); - if (listIsIdentical) { - this.tracks = tracks; - return; - } - this.textTracks = []; - this.tracks = tracks; - if (this.config.renderTextTracksNatively) { - var media = this.media; - var inUseTracks = media ? filterSubtitleTracks(media.textTracks) : null; - this.tracks.forEach(function (track, index) { - // Reuse tracks with the same label and lang, but do not reuse 608/708 tracks - var textTrack; - if (inUseTracks) { - var inUseTrack = null; - for (var i = 0; i < inUseTracks.length; i++) { - if (inUseTracks[i] && canReuseVttTextTrack(inUseTracks[i], track)) { - inUseTrack = inUseTracks[i]; - inUseTracks[i] = null; - break; - } - } - if (inUseTrack) { - textTrack = inUseTrack; - } - } - if (textTrack) { - clearCurrentCues(textTrack); - } else { - var textTrackKind = captionsOrSubtitlesFromCharacteristics(track); - textTrack = _this2.createTextTrack(textTrackKind, track.name, track.lang); - if (textTrack) { - textTrack.mode = 'disabled'; - } - } - if (textTrack) { - _this2.textTracks.push(textTrack); - } - }); - // Warn when video element has captions or subtitle TextTracks carried over from another source - if (inUseTracks != null && inUseTracks.length) { - var unusedTextTracks = inUseTracks.filter(function (t) { - return t !== null; - }).map(function (t) { - return t.label; - }); - if (unusedTextTracks.length) { - logger.warn("Media element contains unused subtitle tracks: " + unusedTextTracks.join(', ') + ". Replace media element for each source to clear TextTracks and captions menu."); - } - } - } else if (this.tracks.length) { - // Create a list of tracks for the provider to consume - var tracksList = this.tracks.map(function (track) { - return { - label: track.name, - kind: track.type.toLowerCase(), - default: track.default, - subtitleTrack: track - }; - }); - this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { - tracks: tracksList - }); - } - } - }; - _proto.onManifestLoaded = function onManifestLoaded(event, data) { - var _this3 = this; - if (this.config.enableCEA708Captions && data.captions) { - data.captions.forEach(function (captionsTrack) { - var instreamIdMatch = /(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId); - if (!instreamIdMatch) { - return; - } - var trackName = "textTrack" + instreamIdMatch[1]; - var trackProperties = _this3.captionsProperties[trackName]; - if (!trackProperties) { - return; - } - trackProperties.label = captionsTrack.name; - if (captionsTrack.lang) { - // optional attribute - trackProperties.languageCode = captionsTrack.lang; - } - trackProperties.media = captionsTrack; - }); - } - }; - _proto.closedCaptionsForLevel = function closedCaptionsForLevel(frag) { - var level = this.hls.levels[frag.level]; - return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS']; - }; - _proto.onFragLoading = function onFragLoading(event, data) { - // if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack - if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) { - var _data$part$index, _data$part; - var cea608Parser1 = this.cea608Parser1, - cea608Parser2 = this.cea608Parser2, - lastSn = this.lastSn; - var _data$frag = data.frag, - cc = _data$frag.cc, - sn = _data$frag.sn; - var partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1; - if (cea608Parser1 && cea608Parser2) { - if (sn !== lastSn + 1 || sn === lastSn && partIndex !== this.lastPartIndex + 1 || cc !== this.lastCc) { - cea608Parser1.reset(); - cea608Parser2.reset(); - } - } - this.lastCc = cc; - this.lastSn = sn; - this.lastPartIndex = partIndex; - } - }; - _proto.onFragLoaded = function onFragLoaded(event, data) { - var frag = data.frag, - payload = data.payload; - if (frag.type === PlaylistLevelType.SUBTITLE) { - // If fragment is subtitle type, parse as WebVTT. - if (payload.byteLength) { - var decryptData = frag.decryptdata; - // fragment after decryption has a stats object - var decrypted = ('stats' in data); - // If the subtitles are not encrypted, parse VTTs now. Otherwise, we need to wait. - if (decryptData == null || !decryptData.encrypted || decrypted) { - var trackPlaylistMedia = this.tracks[frag.level]; - var vttCCs = this.vttCCs; - if (!vttCCs[frag.cc]) { - vttCCs[frag.cc] = { - start: frag.start, - prevCC: this.prevCC, - new: true - }; - this.prevCC = frag.cc; - } - if (trackPlaylistMedia && trackPlaylistMedia.textCodec === IMSC1_CODEC) { - this._parseIMSC1(frag, payload); - } else { - this._parseVTTs(data); - } - } - } else { - // In case there is no payload, finish unsuccessfully. - this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { - success: false, - frag: frag, - error: new Error('Empty subtitle payload') - }); - } - } - }; - _proto._parseIMSC1 = function _parseIMSC1(frag, payload) { - var _this4 = this; - var hls = this.hls; - parseIMSC1(payload, this.initPTS[frag.cc], function (cues) { - _this4._appendCues(cues, frag.level); - hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { - success: true, - frag: frag - }); - }, function (error) { - logger.log("Failed to parse IMSC1: " + error); - hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { - success: false, - frag: frag, - error: error - }); - }); - }; - _proto._parseVTTs = function _parseVTTs(data) { - var _frag$initSegment, - _this5 = this; - var frag = data.frag, - payload = data.payload; - // We need an initial synchronisation PTS. Store fragments as long as none has arrived - var initPTS = this.initPTS, - unparsedVttFrags = this.unparsedVttFrags; - var maxAvCC = initPTS.length - 1; - if (!initPTS[frag.cc] && maxAvCC === -1) { - unparsedVttFrags.push(data); - return; - } - var hls = this.hls; - // Parse the WebVTT file contents. - var payloadWebVTT = (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)) : payload; - parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, function (cues) { - _this5._appendCues(cues, frag.level); - hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { - success: true, - frag: frag - }); - }, function (error) { - var missingInitPTS = error.message === 'Missing initPTS for VTT MPEGTS'; - if (missingInitPTS) { - unparsedVttFrags.push(data); - } else { - _this5._fallbackToIMSC1(frag, payload); - } - // Something went wrong while parsing. Trigger event with success false. - logger.log("Failed to parse VTT cue: " + error); - if (missingInitPTS && maxAvCC > frag.cc) { - return; - } - hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { - success: false, - frag: frag, - error: error - }); - }); - }; - _proto._fallbackToIMSC1 = function _fallbackToIMSC1(frag, payload) { - var _this6 = this; - // If textCodec is unknown, try parsing as IMSC1. Set textCodec based on the result - var trackPlaylistMedia = this.tracks[frag.level]; - if (!trackPlaylistMedia.textCodec) { - parseIMSC1(payload, this.initPTS[frag.cc], function () { - trackPlaylistMedia.textCodec = IMSC1_CODEC; - _this6._parseIMSC1(frag, payload); - }, function () { - trackPlaylistMedia.textCodec = 'wvtt'; - }); - } - }; - _proto._appendCues = function _appendCues(cues, fragLevel) { - var hls = this.hls; - if (this.config.renderTextTracksNatively) { - var textTrack = this.textTracks[fragLevel]; - // WebVTTParser.parse is an async method and if the currently selected text track mode is set to "disabled" - // before parsing is done then don't try to access currentTrack.cues.getCueById as cues will be null - // and trying to access getCueById method of cues will throw an exception - // Because we check if the mode is disabled, we can force check `cues` below. They can't be null. - if (!textTrack || textTrack.mode === 'disabled') { - return; - } - cues.forEach(function (cue) { - return addCueToTrack(textTrack, cue); - }); - } else { - var currentTrack = this.tracks[fragLevel]; - if (!currentTrack) { - return; - } - var track = currentTrack.default ? 'default' : 'subtitles' + fragLevel; - hls.trigger(Events.CUES_PARSED, { - type: 'subtitles', - cues: cues, - track: track - }); - } - }; - _proto.onFragDecrypted = function onFragDecrypted(event, data) { - var frag = data.frag; - if (frag.type === PlaylistLevelType.SUBTITLE) { - this.onFragLoaded(Events.FRAG_LOADED, data); - } - }; - _proto.onSubtitleTracksCleared = function onSubtitleTracksCleared() { - this.tracks = []; - this.captionsTracks = {}; - }; - _proto.onFragParsingUserdata = function onFragParsingUserdata(event, data) { - this.initCea608Parsers(); - var cea608Parser1 = this.cea608Parser1, - cea608Parser2 = this.cea608Parser2; - if (!this.enabled || !cea608Parser1 || !cea608Parser2) { - return; - } - var frag = data.frag, - samples = data.samples; - if (frag.type === PlaylistLevelType.MAIN && this.closedCaptionsForLevel(frag) === 'NONE') { - return; - } - // If the event contains captions (found in the bytes property), push all bytes into the parser immediately - // It will create the proper timestamps based on the PTS value - for (var i = 0; i < samples.length; i++) { - var ccBytes = samples[i].bytes; - if (ccBytes) { - var ccdatas = this.extractCea608Data(ccBytes); - cea608Parser1.addData(samples[i].pts, ccdatas[0]); - cea608Parser2.addData(samples[i].pts, ccdatas[1]); - } - } - }; - _proto.onBufferFlushing = function onBufferFlushing(event, _ref2) { - var startOffset = _ref2.startOffset, - endOffset = _ref2.endOffset, - endOffsetSubtitles = _ref2.endOffsetSubtitles, - type = _ref2.type; - var media = this.media; - if (!media || media.currentTime < endOffset) { - return; - } - // Clear 608 caption cues from the captions TextTracks when the video back buffer is flushed - // Forward cues are never removed because we can loose streamed 608 content from recent fragments - if (!type || type === 'video') { - var captionsTracks = this.captionsTracks; - Object.keys(captionsTracks).forEach(function (trackName) { - return removeCuesInRange(captionsTracks[trackName], startOffset, endOffset); - }); - } - if (this.config.renderTextTracksNatively) { - // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed - if (startOffset === 0 && endOffsetSubtitles !== undefined) { - var textTracks = this.textTracks; - Object.keys(textTracks).forEach(function (trackName) { - return removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles); - }); - } - } - }; - _proto.extractCea608Data = function extractCea608Data(byteArray) { - var actualCCBytes = [[], []]; - var count = byteArray[0] & 0x1f; - var position = 2; - for (var j = 0; j < count; j++) { - var tmpByte = byteArray[position++]; - var ccbyte1 = 0x7f & byteArray[position++]; - var ccbyte2 = 0x7f & byteArray[position++]; - if (ccbyte1 === 0 && ccbyte2 === 0) { - continue; - } - var ccValid = (0x04 & tmpByte) !== 0; // Support all four channels - if (ccValid) { - var ccType = 0x03 & tmpByte; - if (0x00 /* CEA608 field1*/ === ccType || 0x01 /* CEA608 field2*/ === ccType) { - // Exclude CEA708 CC data. - actualCCBytes[ccType].push(ccbyte1); - actualCCBytes[ccType].push(ccbyte2); - } - } - } - return actualCCBytes; - }; - return TimelineController; - }(); - function captionsOrSubtitlesFromCharacteristics(track) { - if (track.characteristics) { - if (/transcribes-spoken-dialog/gi.test(track.characteristics) && /describes-music-and-sound/gi.test(track.characteristics)) { - return 'captions'; - } - } - return 'subtitles'; - } - function canReuseVttTextTrack(inUseTrack, manifestTrack) { - return !!inUseTrack && inUseTrack.kind === captionsOrSubtitlesFromCharacteristics(manifestTrack) && subtitleTrackMatchesTextTrack(manifestTrack, inUseTrack); - } - function intersection(x1, x2, y1, y2) { - return Math.min(x2, y2) - Math.max(x1, y1); - } - function newVTTCCs() { - return { - ccOffset: 0, - presentationOffset: 0, - 0: { - start: 0, - prevCC: -1, - new: true - } - }; - } - - var CapLevelController = /*#__PURE__*/function () { - function CapLevelController(hls) { - this.hls = void 0; - this.autoLevelCapping = void 0; - this.firstLevel = void 0; - this.media = void 0; - this.restrictedLevels = void 0; - this.timer = void 0; - this.clientRect = void 0; - this.streamController = void 0; - this.hls = hls; - this.autoLevelCapping = Number.POSITIVE_INFINITY; - this.firstLevel = -1; - this.media = null; - this.restrictedLevels = []; - this.timer = undefined; - this.clientRect = null; - this.registerListeners(); - } - var _proto = CapLevelController.prototype; - _proto.setStreamController = function setStreamController(streamController) { - this.streamController = streamController; - }; - _proto.destroy = function destroy() { - if (this.hls) { - this.unregisterListener(); - } - if (this.timer) { - this.stopCapping(); - } - this.media = null; - this.clientRect = null; - // @ts-ignore - this.hls = this.streamController = null; - }; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); - hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - }; - _proto.unregisterListener = function unregisterListener() { - var hls = this.hls; - hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); - hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - }; - _proto.onFpsDropLevelCapping = function onFpsDropLevelCapping(event, data) { - // Don't add a restricted level more than once - var level = this.hls.levels[data.droppedLevel]; - if (this.isLevelAllowed(level)) { - this.restrictedLevels.push({ - bitrate: level.bitrate, - height: level.height, - width: level.width - }); - } - }; - _proto.onMediaAttaching = function onMediaAttaching(event, data) { - this.media = data.media instanceof HTMLVideoElement ? data.media : null; - this.clientRect = null; - if (this.timer && this.hls.levels.length) { - this.detectPlayerSize(); - } - }; - _proto.onManifestParsed = function onManifestParsed(event, data) { - var hls = this.hls; - this.restrictedLevels = []; - this.firstLevel = data.firstLevel; - if (hls.config.capLevelToPlayerSize && data.video) { - // Start capping immediately if the manifest has signaled video codecs - this.startCapping(); - } - }; - _proto.onLevelsUpdated = function onLevelsUpdated(event, data) { - if (this.timer && isFiniteNumber(this.autoLevelCapping)) { - this.detectPlayerSize(); - } - } - - // Only activate capping when playing a video stream; otherwise, multi-bitrate audio-only streams will be restricted - // to the first level - ; - _proto.onBufferCodecs = function onBufferCodecs(event, data) { - var hls = this.hls; - if (hls.config.capLevelToPlayerSize && data.video) { - // If the manifest did not signal a video codec capping has been deferred until we're certain video is present - this.startCapping(); - } - }; - _proto.onMediaDetaching = function onMediaDetaching() { - this.stopCapping(); - }; - _proto.detectPlayerSize = function detectPlayerSize() { - if (this.media) { - if (this.mediaHeight <= 0 || this.mediaWidth <= 0) { - this.clientRect = null; - return; - } - var levels = this.hls.levels; - if (levels.length) { - var hls = this.hls; - var maxLevel = this.getMaxLevel(levels.length - 1); - if (maxLevel !== this.autoLevelCapping) { - logger.log("Setting autoLevelCapping to " + maxLevel + ": " + levels[maxLevel].height + "p@" + levels[maxLevel].bitrate + " for media " + this.mediaWidth + "x" + this.mediaHeight); - } - hls.autoLevelCapping = maxLevel; - if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) { - // if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch - // usually happen when the user go to the fullscreen mode. - this.streamController.nextLevelSwitch(); - } - this.autoLevelCapping = hls.autoLevelCapping; - } - } - } - - /* - * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled) - */; - _proto.getMaxLevel = function getMaxLevel(capLevelIndex) { - var _this = this; - var levels = this.hls.levels; - if (!levels.length) { - return -1; - } - var validLevels = levels.filter(function (level, index) { - return _this.isLevelAllowed(level) && index <= capLevelIndex; - }); - this.clientRect = null; - return CapLevelController.getMaxLevelByMediaSize(validLevels, this.mediaWidth, this.mediaHeight); - }; - _proto.startCapping = function startCapping() { - if (this.timer) { - // Don't reset capping if started twice; this can happen if the manifest signals a video codec - return; - } - this.autoLevelCapping = Number.POSITIVE_INFINITY; - self.clearInterval(this.timer); - this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000); - this.detectPlayerSize(); - }; - _proto.stopCapping = function stopCapping() { - this.restrictedLevels = []; - this.firstLevel = -1; - this.autoLevelCapping = Number.POSITIVE_INFINITY; - if (this.timer) { - self.clearInterval(this.timer); - this.timer = undefined; - } - }; - _proto.getDimensions = function getDimensions() { - if (this.clientRect) { - return this.clientRect; - } - var media = this.media; - var boundsRect = { - width: 0, - height: 0 - }; - if (media) { - var clientRect = media.getBoundingClientRect(); - boundsRect.width = clientRect.width; - boundsRect.height = clientRect.height; - if (!boundsRect.width && !boundsRect.height) { - // When the media element has no width or height (equivalent to not being in the DOM), - // then use its width and height attributes (media.width, media.height) - boundsRect.width = clientRect.right - clientRect.left || media.width || 0; - boundsRect.height = clientRect.bottom - clientRect.top || media.height || 0; - } - } - this.clientRect = boundsRect; - return boundsRect; - }; - _proto.isLevelAllowed = function isLevelAllowed(level) { - var restrictedLevels = this.restrictedLevels; - return !restrictedLevels.some(function (restrictedLevel) { - return level.bitrate === restrictedLevel.bitrate && level.width === restrictedLevel.width && level.height === restrictedLevel.height; - }); - }; - CapLevelController.getMaxLevelByMediaSize = function getMaxLevelByMediaSize(levels, width, height) { - if (!(levels != null && levels.length)) { - return -1; - } - - // Levels can have the same dimensions but differing bandwidths - since levels are ordered, we can look to the next - // to determine whether we've chosen the greatest bandwidth for the media's dimensions - var atGreatestBandwidth = function atGreatestBandwidth(curLevel, nextLevel) { - if (!nextLevel) { - return true; - } - return curLevel.width !== nextLevel.width || curLevel.height !== nextLevel.height; - }; - - // If we run through the loop without breaking, the media's dimensions are greater than every level, so default to - // the max level - var maxLevelIndex = levels.length - 1; - // Prevent changes in aspect-ratio from causing capping to toggle back and forth - var squareSize = Math.max(width, height); - for (var i = 0; i < levels.length; i += 1) { - var level = levels[i]; - if ((level.width >= squareSize || level.height >= squareSize) && atGreatestBandwidth(level, levels[i + 1])) { - maxLevelIndex = i; - break; - } - } - return maxLevelIndex; - }; - _createClass(CapLevelController, [{ - key: "mediaWidth", - get: function get() { - return this.getDimensions().width * this.contentScaleFactor; - } - }, { - key: "mediaHeight", - get: function get() { - return this.getDimensions().height * this.contentScaleFactor; - } - }, { - key: "contentScaleFactor", - get: function get() { - var pixelRatio = 1; - if (!this.hls.config.ignoreDevicePixelRatio) { - try { - pixelRatio = self.devicePixelRatio; - } catch (e) { - /* no-op */ - } - } - return pixelRatio; - } - }]); - return CapLevelController; - }(); - - var FPSController = /*#__PURE__*/function () { - function FPSController(hls) { - this.hls = void 0; - this.isVideoPlaybackQualityAvailable = false; - this.timer = void 0; - this.media = null; - this.lastTime = void 0; - this.lastDroppedFrames = 0; - this.lastDecodedFrames = 0; - // stream controller must be provided as a dependency! - this.streamController = void 0; - this.hls = hls; - this.registerListeners(); - } - var _proto = FPSController.prototype; - _proto.setStreamController = function setStreamController(streamController) { - this.streamController = streamController; - }; - _proto.registerListeners = function registerListeners() { - this.hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - this.hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - }; - _proto.destroy = function destroy() { - if (this.timer) { - clearInterval(this.timer); - } - this.unregisterListeners(); - this.isVideoPlaybackQualityAvailable = false; - this.media = null; - }; - _proto.onMediaAttaching = function onMediaAttaching(event, data) { - var config = this.hls.config; - if (config.capLevelOnFPSDrop) { - var media = data.media instanceof self.HTMLVideoElement ? data.media : null; - this.media = media; - if (media && typeof media.getVideoPlaybackQuality === 'function') { - this.isVideoPlaybackQualityAvailable = true; - } - self.clearInterval(this.timer); - this.timer = self.setInterval(this.checkFPSInterval.bind(this), config.fpsDroppedMonitoringPeriod); - } - }; - _proto.checkFPS = function checkFPS(video, decodedFrames, droppedFrames) { - var currentTime = performance.now(); - if (decodedFrames) { - if (this.lastTime) { - var currentPeriod = currentTime - this.lastTime; - var currentDropped = droppedFrames - this.lastDroppedFrames; - var currentDecoded = decodedFrames - this.lastDecodedFrames; - var droppedFPS = 1000 * currentDropped / currentPeriod; - var hls = this.hls; - hls.trigger(Events.FPS_DROP, { - currentDropped: currentDropped, - currentDecoded: currentDecoded, - totalDroppedFrames: droppedFrames - }); - if (droppedFPS > 0) { - // logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod)); - if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) { - var currentLevel = hls.currentLevel; - logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel); - if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) { - currentLevel = currentLevel - 1; - hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, { - level: currentLevel, - droppedLevel: hls.currentLevel - }); - hls.autoLevelCapping = currentLevel; - this.streamController.nextLevelSwitch(); - } - } - } - } - this.lastTime = currentTime; - this.lastDroppedFrames = droppedFrames; - this.lastDecodedFrames = decodedFrames; - } - }; - _proto.checkFPSInterval = function checkFPSInterval() { - var video = this.media; - if (video) { - if (this.isVideoPlaybackQualityAvailable) { - var videoPlaybackQuality = video.getVideoPlaybackQuality(); - this.checkFPS(video, videoPlaybackQuality.totalVideoFrames, videoPlaybackQuality.droppedVideoFrames); - } else { - // HTMLVideoElement doesn't include the webkit types - this.checkFPS(video, video.webkitDecodedFrameCount, video.webkitDroppedFrameCount); - } - } - }; - return FPSController; - }(); - - var LOGGER_PREFIX = '[eme]'; - /** - * Controller to deal with encrypted media extensions (EME) - * @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API - * - * @class - * @constructor - */ - var EMEController = /*#__PURE__*/function () { - function EMEController(hls) { - this.hls = void 0; - this.config = void 0; - this.media = null; - this.keyFormatPromise = null; - this.keySystemAccessPromises = {}; - this._requestLicenseFailureCount = 0; - this.mediaKeySessions = []; - this.keyIdToKeySessionPromise = {}; - this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : []; - this.onMediaEncrypted = this._onMediaEncrypted.bind(this); - this.onWaitingForKey = this._onWaitingForKey.bind(this); - this.debug = logger.debug.bind(logger, LOGGER_PREFIX); - this.log = logger.log.bind(logger, LOGGER_PREFIX); - this.warn = logger.warn.bind(logger, LOGGER_PREFIX); - this.error = logger.error.bind(logger, LOGGER_PREFIX); - this.hls = hls; - this.config = hls.config; - this.registerListeners(); - } - var _proto = EMEController.prototype; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.onMediaDetached(); - // Remove any references that could be held in config options or callbacks - var config = this.config; - config.requestMediaKeySystemAccessFunc = null; - config.licenseXhrSetup = config.licenseResponseCallback = undefined; - config.drmSystems = config.drmSystemOptions = {}; - // @ts-ignore - this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null; - // @ts-ignore - this.config = null; - }; - _proto.registerListeners = function registerListeners() { - this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this); - this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - this.hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this); - this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - }; - _proto.getLicenseServerUrl = function getLicenseServerUrl(keySystem) { - var _this$config = this.config, - drmSystems = _this$config.drmSystems, - widevineLicenseUrl = _this$config.widevineLicenseUrl; - var keySystemConfiguration = drmSystems[keySystem]; - if (keySystemConfiguration) { - return keySystemConfiguration.licenseUrl; - } - - // For backward compatibility - if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) { - return widevineLicenseUrl; - } - throw new Error("no license server URL configured for key-system \"" + keySystem + "\""); - }; - _proto.getServerCertificateUrl = function getServerCertificateUrl(keySystem) { - var drmSystems = this.config.drmSystems; - var keySystemConfiguration = drmSystems[keySystem]; - if (keySystemConfiguration) { - return keySystemConfiguration.serverCertificateUrl; - } else { - this.log("No Server Certificate in config.drmSystems[\"" + keySystem + "\"]"); - } - }; - _proto.attemptKeySystemAccess = function attemptKeySystemAccess(keySystemsToAttempt) { - var _this = this; - var levels = this.hls.levels; - var uniqueCodec = function uniqueCodec(value, i, a) { - return !!value && a.indexOf(value) === i; - }; - var audioCodecs = levels.map(function (level) { - return level.audioCodec; - }).filter(uniqueCodec); - var videoCodecs = levels.map(function (level) { - return level.videoCodec; - }).filter(uniqueCodec); - if (audioCodecs.length + videoCodecs.length === 0) { - videoCodecs.push('avc1.42e01e'); - } - return new Promise(function (resolve, reject) { - var attempt = function attempt(keySystems) { - var keySystem = keySystems.shift(); - _this.getMediaKeysPromise(keySystem, audioCodecs, videoCodecs).then(function (mediaKeys) { - return resolve({ - keySystem: keySystem, - mediaKeys: mediaKeys - }); - }).catch(function (error) { - if (keySystems.length) { - attempt(keySystems); - } else if (error instanceof EMEKeyError) { - reject(error); - } else { - reject(new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_NO_ACCESS, - error: error, - fatal: true - }, error.message)); - } - }); - }; - attempt(keySystemsToAttempt); - }); - }; - _proto.requestMediaKeySystemAccess = function requestMediaKeySystemAccess$1(keySystem, supportedConfigurations) { - var requestMediaKeySystemAccessFunc = this.config.requestMediaKeySystemAccessFunc; - if (!(typeof requestMediaKeySystemAccessFunc === 'function')) { - var errMessage = "Configured requestMediaKeySystemAccess is not a function " + requestMediaKeySystemAccessFunc; - if (requestMediaKeySystemAccess === null && self.location.protocol === 'http:') { - errMessage = "navigator.requestMediaKeySystemAccess is not available over insecure protocol " + location.protocol; - } - return Promise.reject(new Error(errMessage)); - } - return requestMediaKeySystemAccessFunc(keySystem, supportedConfigurations); - }; - _proto.getMediaKeysPromise = function getMediaKeysPromise(keySystem, audioCodecs, videoCodecs) { - var _this2 = this; - // This can throw, but is caught in event handler callpath - var mediaKeySystemConfigs = getSupportedMediaKeySystemConfigurations(keySystem, audioCodecs, videoCodecs, this.config.drmSystemOptions); - var keySystemAccessPromises = this.keySystemAccessPromises[keySystem]; - var keySystemAccess = keySystemAccessPromises == null ? void 0 : keySystemAccessPromises.keySystemAccess; - if (!keySystemAccess) { - this.log("Requesting encrypted media \"" + keySystem + "\" key-system access with config: " + JSON.stringify(mediaKeySystemConfigs)); - keySystemAccess = this.requestMediaKeySystemAccess(keySystem, mediaKeySystemConfigs); - var _keySystemAccessPromises = this.keySystemAccessPromises[keySystem] = { - keySystemAccess: keySystemAccess - }; - keySystemAccess.catch(function (error) { - _this2.log("Failed to obtain access to key-system \"" + keySystem + "\": " + error); - }); - return keySystemAccess.then(function (mediaKeySystemAccess) { - _this2.log("Access for key-system \"" + mediaKeySystemAccess.keySystem + "\" obtained"); - var certificateRequest = _this2.fetchServerCertificate(keySystem); - _this2.log("Create media-keys for \"" + keySystem + "\""); - _keySystemAccessPromises.mediaKeys = mediaKeySystemAccess.createMediaKeys().then(function (mediaKeys) { - _this2.log("Media-keys created for \"" + keySystem + "\""); - return certificateRequest.then(function (certificate) { - if (certificate) { - return _this2.setMediaKeysServerCertificate(mediaKeys, keySystem, certificate); - } - return mediaKeys; - }); - }); - _keySystemAccessPromises.mediaKeys.catch(function (error) { - _this2.error("Failed to create media-keys for \"" + keySystem + "\"}: " + error); - }); - return _keySystemAccessPromises.mediaKeys; - }); - } - return keySystemAccess.then(function () { - return keySystemAccessPromises.mediaKeys; - }); - }; - _proto.createMediaKeySessionContext = function createMediaKeySessionContext(_ref) { - var decryptdata = _ref.decryptdata, - keySystem = _ref.keySystem, - mediaKeys = _ref.mediaKeys; - this.log("Creating key-system session \"" + keySystem + "\" keyId: " + Hex.hexDump(decryptdata.keyId || [])); - var mediaKeysSession = mediaKeys.createSession(); - var mediaKeySessionContext = { - decryptdata: decryptdata, - keySystem: keySystem, - mediaKeys: mediaKeys, - mediaKeysSession: mediaKeysSession, - keyStatus: 'status-pending' - }; - this.mediaKeySessions.push(mediaKeySessionContext); - return mediaKeySessionContext; - }; - _proto.renewKeySession = function renewKeySession(mediaKeySessionContext) { - var decryptdata = mediaKeySessionContext.decryptdata; - if (decryptdata.pssh) { - var keySessionContext = this.createMediaKeySessionContext(mediaKeySessionContext); - var _keyId = this.getKeyIdString(decryptdata); - var scheme = 'cenc'; - this.keyIdToKeySessionPromise[_keyId] = this.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'expired'); - } else { - this.warn("Could not renew expired session. Missing pssh initData."); - } - this.removeSession(mediaKeySessionContext); - }; - _proto.getKeyIdString = function getKeyIdString(decryptdata) { - if (!decryptdata) { - throw new Error('Could not read keyId of undefined decryptdata'); - } - if (decryptdata.keyId === null) { - throw new Error('keyId is null'); - } - return Hex.hexDump(decryptdata.keyId); - }; - _proto.updateKeySession = function updateKeySession(mediaKeySessionContext, data) { - var _mediaKeySessionConte; - var keySession = mediaKeySessionContext.mediaKeysSession; - this.log("Updating key-session \"" + keySession.sessionId + "\" for keyID " + Hex.hexDump(((_mediaKeySessionConte = mediaKeySessionContext.decryptdata) == null ? void 0 : _mediaKeySessionConte.keyId) || []) + "\n } (data length: " + (data ? data.byteLength : data) + ")"); - return keySession.update(data); - }; - _proto.selectKeySystemFormat = function selectKeySystemFormat(frag) { - var keyFormats = Object.keys(frag.levelkeys || {}); - if (!this.keyFormatPromise) { - this.log("Selecting key-system from fragment (sn: " + frag.sn + " " + frag.type + ": " + frag.level + ") key formats " + keyFormats.join(', ')); - this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); - } - return this.keyFormatPromise; - }; - _proto.getKeyFormatPromise = function getKeyFormatPromise(keyFormats) { - var _this3 = this; - return new Promise(function (resolve, reject) { - var keySystemsInConfig = getKeySystemsForConfig(_this3.config); - var keySystemsToAttempt = keyFormats.map(keySystemFormatToKeySystemDomain).filter(function (value) { - return !!value && keySystemsInConfig.indexOf(value) !== -1; - }); - return _this3.getKeySystemSelectionPromise(keySystemsToAttempt).then(function (_ref2) { - var keySystem = _ref2.keySystem; - var keySystemFormat = keySystemDomainToKeySystemFormat(keySystem); - if (keySystemFormat) { - resolve(keySystemFormat); - } else { - reject(new Error("Unable to find format for key-system \"" + keySystem + "\"")); - } - }).catch(reject); - }); - }; - _proto.loadKey = function loadKey(data) { - var _this4 = this; - var decryptdata = data.keyInfo.decryptdata; - var keyId = this.getKeyIdString(decryptdata); - var keyDetails = "(keyId: " + keyId + " format: \"" + decryptdata.keyFormat + "\" method: " + decryptdata.method + " uri: " + decryptdata.uri + ")"; - this.log("Starting session for key " + keyDetails); - var keySessionContextPromise = this.keyIdToKeySessionPromise[keyId]; - if (!keySessionContextPromise) { - keySessionContextPromise = this.keyIdToKeySessionPromise[keyId] = this.getKeySystemForKeyPromise(decryptdata).then(function (_ref3) { - var keySystem = _ref3.keySystem, - mediaKeys = _ref3.mediaKeys; - _this4.throwIfDestroyed(); - _this4.log("Handle encrypted media sn: " + data.frag.sn + " " + data.frag.type + ": " + data.frag.level + " using key " + keyDetails); - return _this4.attemptSetMediaKeys(keySystem, mediaKeys).then(function () { - _this4.throwIfDestroyed(); - var keySessionContext = _this4.createMediaKeySessionContext({ - keySystem: keySystem, - mediaKeys: mediaKeys, - decryptdata: decryptdata - }); - var scheme = 'cenc'; - return _this4.generateRequestWithPreferredKeySession(keySessionContext, scheme, decryptdata.pssh, 'playlist-key'); - }); - }); - keySessionContextPromise.catch(function (error) { - return _this4.handleError(error); - }); - } - return keySessionContextPromise; - }; - _proto.throwIfDestroyed = function throwIfDestroyed(message) { - if (!this.hls) { - throw new Error('invalid state'); - } - }; - _proto.handleError = function handleError(error) { - if (!this.hls) { - return; - } - this.error(error.message); - if (error instanceof EMEKeyError) { - this.hls.trigger(Events.ERROR, error.data); - } else { - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_NO_KEYS, - error: error, - fatal: true - }); - } - }; - _proto.getKeySystemForKeyPromise = function getKeySystemForKeyPromise(decryptdata) { - var keyId = this.getKeyIdString(decryptdata); - var mediaKeySessionContext = this.keyIdToKeySessionPromise[keyId]; - if (!mediaKeySessionContext) { - var keySystem = keySystemFormatToKeySystemDomain(decryptdata.keyFormat); - var keySystemsToAttempt = keySystem ? [keySystem] : getKeySystemsForConfig(this.config); - return this.attemptKeySystemAccess(keySystemsToAttempt); - } - return mediaKeySessionContext; - }; - _proto.getKeySystemSelectionPromise = function getKeySystemSelectionPromise(keySystemsToAttempt) { - if (!keySystemsToAttempt.length) { - keySystemsToAttempt = getKeySystemsForConfig(this.config); - } - if (keySystemsToAttempt.length === 0) { - throw new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_NO_CONFIGURED_LICENSE, - fatal: true - }, "Missing key-system license configuration options " + JSON.stringify({ - drmSystems: this.config.drmSystems - })); - } - return this.attemptKeySystemAccess(keySystemsToAttempt); - }; - _proto._onMediaEncrypted = function _onMediaEncrypted(event) { - var _this5 = this; - var initDataType = event.initDataType, - initData = event.initData; - this.debug("\"" + event.type + "\" event: init data type: \"" + initDataType + "\""); - - // Ignore event when initData is null - if (initData === null) { - return; - } - var keyId; - var keySystemDomain; - if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) { - // Match sinf keyId to playlist skd://keyId= - var json = bin2str(new Uint8Array(initData)); - try { - var sinf = base64Decode(JSON.parse(json).sinf); - var tenc = parseSinf(new Uint8Array(sinf)); - if (!tenc) { - return; - } - keyId = tenc.subarray(8, 24); - keySystemDomain = KeySystems.FAIRPLAY; - } catch (error) { - this.warn('Failed to parse sinf "encrypted" event message initData'); - return; - } - } else { - // Support clear-lead key-session creation (otherwise depend on playlist keys) - var psshInfo = parsePssh(initData); - if (psshInfo === null) { - return; - } - if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) { - keyId = psshInfo.data.subarray(8, 24); - } - keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId); - } - if (!keySystemDomain || !keyId) { - return; - } - var keyIdHex = Hex.hexDump(keyId); - var keyIdToKeySessionPromise = this.keyIdToKeySessionPromise, - mediaKeySessions = this.mediaKeySessions; - var keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex]; - var _loop = function _loop() { - // Match playlist key - var keyContext = mediaKeySessions[i]; - var decryptdata = keyContext.decryptdata; - if (decryptdata.pssh || !decryptdata.keyId) { - return 0; // continue - } - var oldKeyIdHex = Hex.hexDump(decryptdata.keyId); - if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) { - keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex]; - delete keyIdToKeySessionPromise[oldKeyIdHex]; - decryptdata.pssh = new Uint8Array(initData); - decryptdata.keyId = keyId; - keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(function () { - return _this5.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match'); - }); - return 1; // break - } - }, - _ret; - for (var i = 0; i < mediaKeySessions.length; i++) { - _ret = _loop(); - if (_ret === 0) continue; - if (_ret === 1) break; - } - if (!keySessionContextPromise) { - // Clear-lead key (not encountered in playlist) - keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(function (_ref4) { - var _keySystemToKeySystem; - var keySystem = _ref4.keySystem, - mediaKeys = _ref4.mediaKeys; - _this5.throwIfDestroyed(); - var decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : ''); - decryptdata.pssh = new Uint8Array(initData); - decryptdata.keyId = keyId; - return _this5.attemptSetMediaKeys(keySystem, mediaKeys).then(function () { - _this5.throwIfDestroyed(); - var keySessionContext = _this5.createMediaKeySessionContext({ - decryptdata: decryptdata, - keySystem: keySystem, - mediaKeys: mediaKeys - }); - return _this5.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match'); - }); - }); - } - keySessionContextPromise.catch(function (error) { - return _this5.handleError(error); - }); - }; - _proto._onWaitingForKey = function _onWaitingForKey(event) { - this.log("\"" + event.type + "\" event"); - }; - _proto.attemptSetMediaKeys = function attemptSetMediaKeys(keySystem, mediaKeys) { - var _this6 = this; - var queue = this.setMediaKeysQueue.slice(); - this.log("Setting media-keys for \"" + keySystem + "\""); - // Only one setMediaKeys() can run at one time, and multiple setMediaKeys() operations - // can be queued for execution for multiple key sessions. - var setMediaKeysPromise = Promise.all(queue).then(function () { - if (!_this6.media) { - throw new Error('Attempted to set mediaKeys without media element attached'); - } - return _this6.media.setMediaKeys(mediaKeys); - }); - this.setMediaKeysQueue.push(setMediaKeysPromise); - return setMediaKeysPromise.then(function () { - _this6.log("Media-keys set for \"" + keySystem + "\""); - queue.push(setMediaKeysPromise); - _this6.setMediaKeysQueue = _this6.setMediaKeysQueue.filter(function (p) { - return queue.indexOf(p) === -1; - }); - }); - }; - _proto.generateRequestWithPreferredKeySession = function generateRequestWithPreferredKeySession(context, initDataType, initData, reason) { - var _this$config$drmSyste, - _this$config$drmSyste2, - _this7 = this; - var generateRequestFilter = (_this$config$drmSyste = this.config.drmSystems) == null ? void 0 : (_this$config$drmSyste2 = _this$config$drmSyste[context.keySystem]) == null ? void 0 : _this$config$drmSyste2.generateRequest; - if (generateRequestFilter) { - try { - var mappedInitData = generateRequestFilter.call(this.hls, initDataType, initData, context); - if (!mappedInitData) { - throw new Error('Invalid response from configured generateRequest filter'); - } - initDataType = mappedInitData.initDataType; - initData = context.decryptdata.pssh = mappedInitData.initData ? new Uint8Array(mappedInitData.initData) : null; - } catch (error) { - var _this$hls; - this.warn(error.message); - if ((_this$hls = this.hls) != null && _this$hls.config.debug) { - throw error; - } - } - } - if (initData === null) { - this.log("Skipping key-session request for \"" + reason + "\" (no initData)"); - return Promise.resolve(context); - } - var keyId = this.getKeyIdString(context.decryptdata); - this.log("Generating key-session request for \"" + reason + "\": " + keyId + " (init data type: " + initDataType + " length: " + (initData ? initData.byteLength : null) + ")"); - var licenseStatus = new EventEmitter(); - var onmessage = context._onmessage = function (event) { - var keySession = context.mediaKeysSession; - if (!keySession) { - licenseStatus.emit('error', new Error('invalid state')); - return; - } - var messageType = event.messageType, - message = event.message; - _this7.log("\"" + messageType + "\" message event for session \"" + keySession.sessionId + "\" message size: " + message.byteLength); - if (messageType === 'license-request' || messageType === 'license-renewal') { - _this7.renewLicense(context, message).catch(function (error) { - _this7.handleError(error); - licenseStatus.emit('error', error); - }); - } else if (messageType === 'license-release') { - if (context.keySystem === KeySystems.FAIRPLAY) { - _this7.updateKeySession(context, strToUtf8array('acknowledged')); - _this7.removeSession(context); - } - } else { - _this7.warn("unhandled media key message type \"" + messageType + "\""); - } - }; - var onkeystatuseschange = context._onkeystatuseschange = function (event) { - var keySession = context.mediaKeysSession; - if (!keySession) { - licenseStatus.emit('error', new Error('invalid state')); - return; - } - _this7.onKeyStatusChange(context); - var keyStatus = context.keyStatus; - licenseStatus.emit('keyStatus', keyStatus); - if (keyStatus === 'expired') { - _this7.warn(context.keySystem + " expired for key " + keyId); - _this7.renewKeySession(context); - } - }; - context.mediaKeysSession.addEventListener('message', onmessage); - context.mediaKeysSession.addEventListener('keystatuseschange', onkeystatuseschange); - var keyUsablePromise = new Promise(function (resolve, reject) { - licenseStatus.on('error', reject); - licenseStatus.on('keyStatus', function (keyStatus) { - if (keyStatus.startsWith('usable')) { - resolve(); - } else if (keyStatus === 'output-restricted') { - reject(new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED, - fatal: false - }, 'HDCP level output restricted')); - } else if (keyStatus === 'internal-error') { - reject(new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR, - fatal: true - }, "key status changed to \"" + keyStatus + "\"")); - } else if (keyStatus === 'expired') { - reject(new Error('key expired while generating request')); - } else { - _this7.warn("unhandled key status change \"" + keyStatus + "\""); - } - }); - }); - return context.mediaKeysSession.generateRequest(initDataType, initData).then(function () { - var _context$mediaKeysSes; - _this7.log("Request generated for key-session \"" + ((_context$mediaKeysSes = context.mediaKeysSession) == null ? void 0 : _context$mediaKeysSes.sessionId) + "\" keyId: " + keyId); - }).catch(function (error) { - throw new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_NO_SESSION, - error: error, - fatal: false - }, "Error generating key-session request: " + error); - }).then(function () { - return keyUsablePromise; - }).catch(function (error) { - licenseStatus.removeAllListeners(); - _this7.removeSession(context); - throw error; - }).then(function () { - licenseStatus.removeAllListeners(); - return context; - }); - }; - _proto.onKeyStatusChange = function onKeyStatusChange(mediaKeySessionContext) { - var _this8 = this; - mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach(function (status, keyId) { - _this8.log("key status change \"" + status + "\" for keyStatuses keyId: " + Hex.hexDump('buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) : new Uint8Array(keyId)) + " session keyId: " + Hex.hexDump(new Uint8Array(mediaKeySessionContext.decryptdata.keyId || [])) + " uri: " + mediaKeySessionContext.decryptdata.uri); - mediaKeySessionContext.keyStatus = status; - }); - }; - _proto.fetchServerCertificate = function fetchServerCertificate(keySystem) { - var config = this.config; - var Loader = config.loader; - var certLoader = new Loader(config); - var url = this.getServerCertificateUrl(keySystem); - if (!url) { - return Promise.resolve(); - } - this.log("Fetching server certificate for \"" + keySystem + "\""); - return new Promise(function (resolve, reject) { - var loaderContext = { - responseType: 'arraybuffer', - url: url - }; - var loadPolicy = config.certLoadPolicy.default; - var loaderConfig = { - loadPolicy: loadPolicy, - timeout: loadPolicy.maxLoadTimeMs, - maxRetry: 0, - retryDelay: 0, - maxRetryDelay: 0 - }; - var loaderCallbacks = { - onSuccess: function onSuccess(response, stats, context, networkDetails) { - resolve(response.data); - }, - onError: function onError(response, contex, networkDetails, stats) { - reject(new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, - fatal: true, - networkDetails: networkDetails, - response: _objectSpread2({ - url: loaderContext.url, - data: undefined - }, response) - }, "\"" + keySystem + "\" certificate request failed (" + url + "). Status: " + response.code + " (" + response.text + ")")); - }, - onTimeout: function onTimeout(stats, context, networkDetails) { - reject(new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, - fatal: true, - networkDetails: networkDetails, - response: { - url: loaderContext.url, - data: undefined - } - }, "\"" + keySystem + "\" certificate request timed out (" + url + ")")); - }, - onAbort: function onAbort(stats, context, networkDetails) { - reject(new Error('aborted')); - } - }; - certLoader.load(loaderContext, loaderConfig, loaderCallbacks); - }); - }; - _proto.setMediaKeysServerCertificate = function setMediaKeysServerCertificate(mediaKeys, keySystem, cert) { - var _this9 = this; - return new Promise(function (resolve, reject) { - mediaKeys.setServerCertificate(cert).then(function (success) { - _this9.log("setServerCertificate " + (success ? 'success' : 'not supported by CDM') + " (" + (cert == null ? void 0 : cert.byteLength) + ") on \"" + keySystem + "\""); - resolve(mediaKeys); - }).catch(function (error) { - reject(new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED, - error: error, - fatal: true - }, error.message)); - }); - }); - }; - _proto.renewLicense = function renewLicense(context, keyMessage) { - var _this10 = this; - return this.requestLicense(context, new Uint8Array(keyMessage)).then(function (data) { - return _this10.updateKeySession(context, new Uint8Array(data)).catch(function (error) { - throw new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED, - error: error, - fatal: true - }, error.message); - }); - }); - }; - _proto.unpackPlayReadyKeyMessage = function unpackPlayReadyKeyMessage(xhr, licenseChallenge) { - // On Edge, the raw license message is UTF-16-encoded XML. We need - // to unpack the Challenge element (base64-encoded string containing the - // actual license request) and any HttpHeader elements (sent as request - // headers). - // For PlayReady CDMs, we need to dig the Challenge out of the XML. - var xmlString = String.fromCharCode.apply(null, new Uint16Array(licenseChallenge.buffer)); - if (!xmlString.includes('PlayReadyKeyMessage')) { - // This does not appear to be a wrapped message as on Edge. Some - // clients do not need this unwrapping, so we will assume this is one of - // them. Note that "xml" at this point probably looks like random - // garbage, since we interpreted UTF-8 as UTF-16. - xhr.setRequestHeader('Content-Type', 'text/xml; charset=utf-8'); - return licenseChallenge; - } - var keyMessageXml = new DOMParser().parseFromString(xmlString, 'application/xml'); - // Set request headers. - var headers = keyMessageXml.querySelectorAll('HttpHeader'); - if (headers.length > 0) { - var header; - for (var i = 0, len = headers.length; i < len; i++) { - var _header$querySelector, _header$querySelector2; - header = headers[i]; - var name = (_header$querySelector = header.querySelector('name')) == null ? void 0 : _header$querySelector.textContent; - var _value = (_header$querySelector2 = header.querySelector('value')) == null ? void 0 : _header$querySelector2.textContent; - if (name && _value) { - xhr.setRequestHeader(name, _value); - } - } - } - var challengeElement = keyMessageXml.querySelector('Challenge'); - var challengeText = challengeElement == null ? void 0 : challengeElement.textContent; - if (!challengeText) { - throw new Error("Cannot find <Challenge> in key message"); - } - return strToUtf8array(atob(challengeText)); - }; - _proto.setupLicenseXHR = function setupLicenseXHR(xhr, url, keysListItem, licenseChallenge) { - var _this11 = this; - var licenseXhrSetup = this.config.licenseXhrSetup; - if (!licenseXhrSetup) { - xhr.open('POST', url, true); - return Promise.resolve({ - xhr: xhr, - licenseChallenge: licenseChallenge - }); - } - return Promise.resolve().then(function () { - if (!keysListItem.decryptdata) { - throw new Error('Key removed'); - } - return licenseXhrSetup.call(_this11.hls, xhr, url, keysListItem, licenseChallenge); - }).catch(function (error) { - if (!keysListItem.decryptdata) { - // Key session removed. Cancel license request. - throw error; - } - // let's try to open before running setup - xhr.open('POST', url, true); - return licenseXhrSetup.call(_this11.hls, xhr, url, keysListItem, licenseChallenge); - }).then(function (licenseXhrSetupResult) { - // if licenseXhrSetup did not yet call open, let's do it now - if (!xhr.readyState) { - xhr.open('POST', url, true); - } - var finalLicenseChallenge = licenseXhrSetupResult ? licenseXhrSetupResult : licenseChallenge; - return { - xhr: xhr, - licenseChallenge: finalLicenseChallenge - }; - }); - }; - _proto.requestLicense = function requestLicense(keySessionContext, licenseChallenge) { - var _this12 = this; - var keyLoadPolicy = this.config.keyLoadPolicy.default; - return new Promise(function (resolve, reject) { - var url = _this12.getLicenseServerUrl(keySessionContext.keySystem); - _this12.log("Sending license request to URL: " + url); - var xhr = new XMLHttpRequest(); - xhr.responseType = 'arraybuffer'; - xhr.onreadystatechange = function () { - if (!_this12.hls || !keySessionContext.mediaKeysSession) { - return reject(new Error('invalid state')); - } - if (xhr.readyState === 4) { - if (xhr.status === 200) { - _this12._requestLicenseFailureCount = 0; - var data = xhr.response; - _this12.log("License received " + (data instanceof ArrayBuffer ? data.byteLength : data)); - var licenseResponseCallback = _this12.config.licenseResponseCallback; - if (licenseResponseCallback) { - try { - data = licenseResponseCallback.call(_this12.hls, xhr, url, keySessionContext); - } catch (error) { - _this12.error(error); - } - } - resolve(data); - } else { - var retryConfig = keyLoadPolicy.errorRetry; - var maxNumRetry = retryConfig ? retryConfig.maxNumRetry : 0; - _this12._requestLicenseFailureCount++; - if (_this12._requestLicenseFailureCount > maxNumRetry || xhr.status >= 400 && xhr.status < 500) { - reject(new EMEKeyError({ - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED, - fatal: true, - networkDetails: xhr, - response: { - url: url, - data: undefined, - code: xhr.status, - text: xhr.statusText - } - }, "License Request XHR failed (" + url + "). Status: " + xhr.status + " (" + xhr.statusText + ")")); - } else { - var attemptsLeft = maxNumRetry - _this12._requestLicenseFailureCount + 1; - _this12.warn("Retrying license request, " + attemptsLeft + " attempts left"); - _this12.requestLicense(keySessionContext, licenseChallenge).then(resolve, reject); - } - } - } - }; - if (keySessionContext.licenseXhr && keySessionContext.licenseXhr.readyState !== XMLHttpRequest.DONE) { - keySessionContext.licenseXhr.abort(); - } - keySessionContext.licenseXhr = xhr; - _this12.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then(function (_ref5) { - var xhr = _ref5.xhr, - licenseChallenge = _ref5.licenseChallenge; - if (keySessionContext.keySystem == KeySystems.PLAYREADY) { - licenseChallenge = _this12.unpackPlayReadyKeyMessage(xhr, licenseChallenge); - } - xhr.send(licenseChallenge); - }); - }); - }; - _proto.onMediaAttached = function onMediaAttached(event, data) { - if (!this.config.emeEnabled) { - return; - } - var media = data.media; - - // keep reference of media - this.media = media; - media.addEventListener('encrypted', this.onMediaEncrypted); - media.addEventListener('waitingforkey', this.onWaitingForKey); - }; - _proto.onMediaDetached = function onMediaDetached() { - var _this13 = this; - var media = this.media; - var mediaKeysList = this.mediaKeySessions; - if (media) { - media.removeEventListener('encrypted', this.onMediaEncrypted); - media.removeEventListener('waitingforkey', this.onWaitingForKey); - this.media = null; - } - this._requestLicenseFailureCount = 0; - this.setMediaKeysQueue = []; - this.mediaKeySessions = []; - this.keyIdToKeySessionPromise = {}; - LevelKey.clearKeyUriToKeyIdMap(); - - // Close all sessions and remove media keys from the video element. - var keySessionCount = mediaKeysList.length; - EMEController.CDMCleanupPromise = Promise.all(mediaKeysList.map(function (mediaKeySessionContext) { - return _this13.removeSession(mediaKeySessionContext); - }).concat(media == null ? void 0 : media.setMediaKeys(null).catch(function (error) { - _this13.log("Could not clear media keys: " + error); - }))).then(function () { - if (keySessionCount) { - _this13.log('finished closing key sessions and clearing media keys'); - mediaKeysList.length = 0; - } - }).catch(function (error) { - _this13.log("Could not close sessions and clear media keys: " + error); - }); - }; - _proto.onManifestLoading = function onManifestLoading() { - this.keyFormatPromise = null; - }; - _proto.onManifestLoaded = function onManifestLoaded(event, _ref6) { - var sessionKeys = _ref6.sessionKeys; - if (!sessionKeys || !this.config.emeEnabled) { - return; - } - if (!this.keyFormatPromise) { - var keyFormats = sessionKeys.reduce(function (formats, sessionKey) { - if (formats.indexOf(sessionKey.keyFormat) === -1) { - formats.push(sessionKey.keyFormat); - } - return formats; - }, []); - this.log("Selecting key-system from session-keys " + keyFormats.join(', ')); - this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); - } - }; - _proto.removeSession = function removeSession(mediaKeySessionContext) { - var _this14 = this; - var mediaKeysSession = mediaKeySessionContext.mediaKeysSession, - licenseXhr = mediaKeySessionContext.licenseXhr; - if (mediaKeysSession) { - this.log("Remove licenses and keys and close session " + mediaKeysSession.sessionId); - if (mediaKeySessionContext._onmessage) { - mediaKeysSession.removeEventListener('message', mediaKeySessionContext._onmessage); - mediaKeySessionContext._onmessage = undefined; - } - if (mediaKeySessionContext._onkeystatuseschange) { - mediaKeysSession.removeEventListener('keystatuseschange', mediaKeySessionContext._onkeystatuseschange); - mediaKeySessionContext._onkeystatuseschange = undefined; - } - if (licenseXhr && licenseXhr.readyState !== XMLHttpRequest.DONE) { - licenseXhr.abort(); - } - mediaKeySessionContext.mediaKeysSession = mediaKeySessionContext.decryptdata = mediaKeySessionContext.licenseXhr = undefined; - var index = this.mediaKeySessions.indexOf(mediaKeySessionContext); - if (index > -1) { - this.mediaKeySessions.splice(index, 1); - } - return mediaKeysSession.remove().catch(function (error) { - _this14.log("Could not remove session: " + error); - }).then(function () { - return mediaKeysSession.close(); - }).catch(function (error) { - _this14.log("Could not close session: " + error); - }); - } - }; - return EMEController; - }(); - EMEController.CDMCleanupPromise = void 0; - var EMEKeyError = /*#__PURE__*/function (_Error) { - _inheritsLoose(EMEKeyError, _Error); - function EMEKeyError(data, message) { - var _this15; - _this15 = _Error.call(this, message) || this; - _this15.data = void 0; - data.error || (data.error = new Error(message)); - _this15.data = data; - data.err = data.error; - return _this15; - } - return EMEKeyError; - }( /*#__PURE__*/_wrapNativeSuper(Error)); - - /** - * Common Media Object Type - * - * @group CMCD - * @group CMSD - * - * @beta - */ - var CmObjectType; - (function (CmObjectType) { - /** - * text file, such as a manifest or playlist - */ - CmObjectType["MANIFEST"] = "m"; - /** - * audio only - */ - CmObjectType["AUDIO"] = "a"; - /** - * video only - */ - CmObjectType["VIDEO"] = "v"; - /** - * muxed audio and video - */ - CmObjectType["MUXED"] = "av"; - /** - * init segment - */ - CmObjectType["INIT"] = "i"; - /** - * caption or subtitle - */ - CmObjectType["CAPTION"] = "c"; - /** - * ISOBMFF timed text track - */ - CmObjectType["TIMED_TEXT"] = "tt"; - /** - * cryptographic key, license or certificate. - */ - CmObjectType["KEY"] = "k"; - /** - * other - */ - CmObjectType["OTHER"] = "o"; - })(CmObjectType || (CmObjectType = {})); - - /** - * Common Media Streaming Format - * - * @group CMCD - * @group CMSD - * - * @beta - */ - var CmStreamingFormat; - (function (CmStreamingFormat) { - /** - * MPEG DASH - */ - CmStreamingFormat["DASH"] = "d"; - /** - * HTTP Live Streaming (HLS) - */ - CmStreamingFormat["HLS"] = "h"; - /** - * Smooth Streaming - */ - CmStreamingFormat["SMOOTH"] = "s"; - /** - * Other - */ - CmStreamingFormat["OTHER"] = "o"; - })(CmStreamingFormat || (CmStreamingFormat = {})); - - /** - * CMCD header fields. - * - * @group CMCD - * - * @beta - */ - var CmcdHeaderField; - (function (CmcdHeaderField) { - /** - * keys whose values vary with the object being requested. - */ - CmcdHeaderField["OBJECT"] = "CMCD-Object"; - /** - * keys whose values vary with each request. - */ - CmcdHeaderField["REQUEST"] = "CMCD-Request"; - /** - * keys whose values are expected to be invariant over the life of the session. - */ - CmcdHeaderField["SESSION"] = "CMCD-Session"; - /** - * keys whose values do not vary with every request or object. - */ - CmcdHeaderField["STATUS"] = "CMCD-Status"; - })(CmcdHeaderField || (CmcdHeaderField = {})); - - var _CmcdHeaderMap; - /** - * The map of CMCD header fields to official CMCD keys. - * - * @internal - * - * @group CMCD - */ - var CmcdHeaderMap = (_CmcdHeaderMap = {}, _CmcdHeaderMap[CmcdHeaderField.OBJECT] = ['br', 'd', 'ot', 'tb'], _CmcdHeaderMap[CmcdHeaderField.REQUEST] = ['bl', 'dl', 'mtp', 'nor', 'nrr', 'su'], _CmcdHeaderMap[CmcdHeaderField.SESSION] = ['cid', 'pr', 'sf', 'sid', 'st', 'v'], _CmcdHeaderMap[CmcdHeaderField.STATUS] = ['bs', 'rtp'], _CmcdHeaderMap); - - /** - * Structured Field Item - * - * @group Structured Field - * - * @beta - */ - var SfItem = function SfItem(value, params) { - this.value = void 0; - this.params = void 0; - if (Array.isArray(value)) { - value = value.map(function (v) { - return v instanceof SfItem ? v : new SfItem(v); - }); - } - this.value = value; - this.params = params; - }; - - /** - * A class to represent structured field tokens when `Symbol` is not available. - * - * @group Structured Field - * - * @beta - */ - var SfToken = function SfToken(description) { - this.description = void 0; - this.description = description; - }; - - var DICT = 'Dict'; - - function format(value) { - if (Array.isArray(value)) { - return JSON.stringify(value); - } - if (value instanceof Map) { - return 'Map{}'; - } - if (value instanceof Set) { - return 'Set{}'; - } - if (typeof value === 'object') { - return JSON.stringify(value); - } - return String(value); - } - function throwError(action, src, type, cause) { - return new Error("failed to " + action + " \"" + format(src) + "\" as " + type, { - cause: cause - }); - } - - var BARE_ITEM = 'Bare Item'; - - var BOOLEAN = 'Boolean'; - - var BYTES = 'Byte Sequence'; - - var DECIMAL = 'Decimal'; - - var INTEGER = 'Integer'; - - function isInvalidInt(value) { - return value < -999999999999999 || 999999999999999 < value; - } - - var STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex - - var TOKEN = 'Token'; - - var KEY = 'Key'; - - function serializeError(src, type, cause) { - return throwError('serialize', src, type, cause); - } - - // 4.1.9. Serializing a Boolean - // - // Given a Boolean as input_boolean, return an ASCII string suitable for - // use in a HTTP field value. - // - // 1. If input_boolean is not a boolean, fail serialization. - // - // 2. Let output be an empty string. - // - // 3. Append "?" to output. - // - // 4. If input_boolean is true, append "1" to output. - // - // 5. If input_boolean is false, append "0" to output. - // - // 6. Return output. - function serializeBoolean(value) { - if (typeof value !== 'boolean') { - throw serializeError(value, BOOLEAN); - } - return value ? '?1' : '?0'; - } - - /** - * Encodes binary data to base64 - * - * @param binary - The binary data to encode - * @returns The base64 encoded string - * - * @group Utils - * - * @beta - */ - function base64encode(binary) { - return btoa(String.fromCharCode.apply(String, binary)); - } - - // 4.1.8. Serializing a Byte Sequence - // - // Given a Byte Sequence as input_bytes, return an ASCII string suitable - // for use in a HTTP field value. - // - // 1. If input_bytes is not a sequence of bytes, fail serialization. - // - // 2. Let output be an empty string. - // - // 3. Append ":" to output. - // - // 4. Append the result of base64-encoding input_bytes as per - // [RFC4648], Section 4, taking account of the requirements below. - // - // 5. Append ":" to output. - // - // 6. Return output. - // - // The encoded data is required to be padded with "=", as per [RFC4648], - // Section 3.2. - // - // Likewise, encoded data SHOULD have pad bits set to zero, as per - // [RFC4648], Section 3.5, unless it is not possible to do so due to - // implementation constraints. - function serializeByteSequence(value) { - if (ArrayBuffer.isView(value) === false) { - throw serializeError(value, BYTES); - } - return ":" + base64encode(value) + ":"; - } - - // 4.1.4. Serializing an Integer - // - // Given an Integer as input_integer, return an ASCII string suitable - // for use in a HTTP field value. - // - // 1. If input_integer is not an integer in the range of - // -999,999,999,999,999 to 999,999,999,999,999 inclusive, fail - // serialization. - // - // 2. Let output be an empty string. - // - // 3. If input_integer is less than (but not equal to) 0, append "-" to - // output. - // - // 4. Append input_integer's numeric value represented in base 10 using - // only decimal digits to output. - // - // 5. Return output. - function serializeInteger(value) { - if (isInvalidInt(value)) { - throw serializeError(value, INTEGER); - } - return value.toString(); - } - - // 4.1.10. Serializing a Date - // - // Given a Date as input_integer, return an ASCII string suitable for - // use in an HTTP field value. - // 1. Let output be "@". - // 2. Append to output the result of running Serializing an Integer - // with input_date (Section 4.1.4). - // 3. Return output. - function serializeDate(value) { - return "@" + serializeInteger(value.getTime() / 1000); - } - - /** - * This implements the rounding procedure described in step 2 of the "Serializing a Decimal" specification. - * This rounding style is known as "even rounding", "banker's rounding", or "commercial rounding". - * - * @param value - The value to round - * @param precision - The number of decimal places to round to - * @returns The rounded value - * - * @group Utils - * - * @beta - */ - function roundToEven(value, precision) { - if (value < 0) { - return -roundToEven(-value, precision); - } - var decimalShift = Math.pow(10, precision); - var isEquidistant = Math.abs(value * decimalShift % 1 - 0.5) < Number.EPSILON; - if (isEquidistant) { - // If the tail of the decimal place is 'equidistant' we round to the nearest even value - var flooredValue = Math.floor(value * decimalShift); - return (flooredValue % 2 === 0 ? flooredValue : flooredValue + 1) / decimalShift; - } else { - // Otherwise, proceed as normal - return Math.round(value * decimalShift) / decimalShift; - } - } - - // 4.1.5. Serializing a Decimal - // - // Given a decimal number as input_decimal, return an ASCII string - // suitable for use in a HTTP field value. - // - // 1. If input_decimal is not a decimal number, fail serialization. - // - // 2. If input_decimal has more than three significant digits to the - // right of the decimal point, round it to three decimal places, - // rounding the final digit to the nearest value, or to the even - // value if it is equidistant. - // - // 3. If input_decimal has more than 12 significant digits to the left - // of the decimal point after rounding, fail serialization. - // - // 4. Let output be an empty string. - // - // 5. If input_decimal is less than (but not equal to) 0, append "-" - // to output. - // - // 6. Append input_decimal's integer component represented in base 10 - // (using only decimal digits) to output; if it is zero, append - // "0". - // - // 7. Append "." to output. - // - // 8. If input_decimal's fractional component is zero, append "0" to - // output. - // - // 9. Otherwise, append the significant digits of input_decimal's - // fractional component represented in base 10 (using only decimal - // digits) to output. - // - // 10. Return output. - function serializeDecimal(value) { - var roundedValue = roundToEven(value, 3); // round to 3 decimal places - if (Math.floor(Math.abs(roundedValue)).toString().length > 12) { - throw serializeError(value, DECIMAL); - } - var stringValue = roundedValue.toString(); - return stringValue.includes('.') ? stringValue : stringValue + ".0"; - } - - var STRING = 'String'; - - // 4.1.6. Serializing a String - // - // Given a String as input_string, return an ASCII string suitable for - // use in a HTTP field value. - // - // 1. Convert input_string into a sequence of ASCII characters; if - // conversion fails, fail serialization. - // - // 2. If input_string contains characters in the range %x00-1f or %x7f - // (i.e., not in VCHAR or SP), fail serialization. - // - // 3. Let output be the string DQUOTE. - // - // 4. For each character char in input_string: - // - // 1. If char is "\" or DQUOTE: - // - // 1. Append "\" to output. - // - // 2. Append char to output. - // - // 5. Append DQUOTE to output. - // - // 6. Return output. - function serializeString(value) { - if (STRING_REGEX.test(value)) { - throw serializeError(value, STRING); - } - return "\"" + value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\""; - } - - function symbolToStr(symbol) { - return symbol.description || symbol.toString().slice(7, -1); - } - - function serializeToken(token) { - var value = symbolToStr(token); - if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) { - throw serializeError(value, TOKEN); - } - return value; - } - - // 4.1.3.1. Serializing a Bare Item - // - // Given an Item as input_item, return an ASCII string suitable for use - // in a HTTP field value. - // - // 1. If input_item is an Integer, return the result of running - // Serializing an Integer (Section 4.1.4) with input_item. - // - // 2. If input_item is a Decimal, return the result of running - // Serializing a Decimal (Section 4.1.5) with input_item. - // - // 3. If input_item is a String, return the result of running - // Serializing a String (Section 4.1.6) with input_item. - // - // 4. If input_item is a Token, return the result of running - // Serializing a Token (Section 4.1.7) with input_item. - // - // 5. If input_item is a Boolean, return the result of running - // Serializing a Boolean (Section 4.1.9) with input_item. - // - // 6. If input_item is a Byte Sequence, return the result of running - // Serializing a Byte Sequence (Section 4.1.8) with input_item. - // - // 7. If input_item is a Date, return the result of running Serializing - // a Date (Section 4.1.10) with input_item. - // - // 8. Otherwise, fail serialization. - function serializeBareItem(value) { - switch (typeof value) { - case 'number': - if (!isFiniteNumber(value)) { - throw serializeError(value, BARE_ITEM); - } - if (Number.isInteger(value)) { - return serializeInteger(value); - } - return serializeDecimal(value); - case 'string': - return serializeString(value); - case 'symbol': - return serializeToken(value); - case 'boolean': - return serializeBoolean(value); - case 'object': - if (value instanceof Date) { - return serializeDate(value); - } - if (value instanceof Uint8Array) { - return serializeByteSequence(value); - } - if (value instanceof SfToken) { - return serializeToken(value); - } - default: - // fail - throw serializeError(value, BARE_ITEM); - } - } - - // 4.1.1.3. Serializing a Key - // - // Given a key as input_key, return an ASCII string suitable for use in - // a HTTP field value. - // - // 1. Convert input_key into a sequence of ASCII characters; if - // conversion fails, fail serialization. - // - // 2. If input_key contains characters not in lcalpha, DIGIT, "_", "-", - // ".", or "*" fail serialization. - // - // 3. If the first character of input_key is not lcalpha or "*", fail - // serialization. - // - // 4. Let output be an empty string. - // - // 5. Append input_key to output. - // - // 6. Return output. - function serializeKey(value) { - if (/^[a-z*][a-z0-9\-_.*]*$/.test(value) === false) { - throw serializeError(value, KEY); - } - return value; - } - - // 4.1.1.2. Serializing Parameters - // - // Given an ordered Dictionary as input_parameters (each member having a - // param_name and a param_value), return an ASCII string suitable for - // use in a HTTP field value. - // - // 1. Let output be an empty string. - // - // 2. For each param_name with a value of param_value in - // input_parameters: - // - // 1. Append ";" to output. - // - // 2. Append the result of running Serializing a Key - // (Section 4.1.1.3) with param_name to output. - // - // 3. If param_value is not Boolean true: - // - // 1. Append "=" to output. - // - // 2. Append the result of running Serializing a bare Item - // (Section 4.1.3.1) with param_value to output. - // - // 3. Return output. - function serializeParams(params) { - if (params == null) { - return ''; - } - return Object.entries(params).map(function (_ref) { - var key = _ref[0], - value = _ref[1]; - if (value === true) { - return ";" + serializeKey(key); // omit true - } - return ";" + serializeKey(key) + "=" + serializeBareItem(value); - }).join(''); - } - - // 4.1.3. Serializing an Item - // - // Given an Item as bare_item and Parameters as item_parameters, return - // an ASCII string suitable for use in a HTTP field value. - // - // 1. Let output be an empty string. - // - // 2. Append the result of running Serializing a Bare Item - // Section 4.1.3.1 with bare_item to output. - // - // 3. Append the result of running Serializing Parameters - // Section 4.1.1.2 with item_parameters to output. - // - // 4. Return output. - function serializeItem(value) { - if (value instanceof SfItem) { - return "" + serializeBareItem(value.value) + serializeParams(value.params); - } else { - return serializeBareItem(value); - } - } - - // 4.1.1.1. Serializing an Inner List - // - // Given an array of (member_value, parameters) tuples as inner_list, - // and parameters as list_parameters, return an ASCII string suitable - // for use in a HTTP field value. - // - // 1. Let output be the string "(". - // - // 2. For each (member_value, parameters) of inner_list: - // - // 1. Append the result of running Serializing an Item - // (Section 4.1.3) with (member_value, parameters) to output. - // - // 2. If more values remain in inner_list, append a single SP to - // output. - // - // 3. Append ")" to output. - // - // 4. Append the result of running Serializing Parameters - // (Section 4.1.1.2) with list_parameters to output. - // - // 5. Return output. - function serializeInnerList(value) { - return "(" + value.value.map(serializeItem).join(' ') + ")" + serializeParams(value.params); - } - - // 4.1.2. Serializing a Dictionary - // - // Given an ordered Dictionary as input_dictionary (each member having a - // member_name and a tuple value of (member_value, parameters)), return - // an ASCII string suitable for use in a HTTP field value. - // - // 1. Let output be an empty string. - // - // 2. For each member_name with a value of (member_value, parameters) - // in input_dictionary: - // - // 1. Append the result of running Serializing a Key - // (Section 4.1.1.3) with member's member_name to output. - // - // 2. If member_value is Boolean true: - // - // 1. Append the result of running Serializing Parameters - // (Section 4.1.1.2) with parameters to output. - // - // 3. Otherwise: - // - // 1. Append "=" to output. - // - // 2. If member_value is an array, append the result of running - // Serializing an Inner List (Section 4.1.1.1) with - // (member_value, parameters) to output. - // - // 3. Otherwise, append the result of running Serializing an - // Item (Section 4.1.3) with (member_value, parameters) to - // output. - // - // 4. If more members remain in input_dictionary: - // - // 1. Append "," to output. - // - // 2. Append a single SP to output. - // - // 3. Return output. - function serializeDict(dict, options) { - var _options; - if (options === void 0) { - options = { - whitespace: true - }; - } - if (typeof dict !== 'object') { - throw serializeError(dict, DICT); - } - var entries = dict instanceof Map ? dict.entries() : Object.entries(dict); - var optionalWhiteSpace = (_options = options) != null && _options.whitespace ? ' ' : ''; - return Array.from(entries).map(function (_ref) { - var key = _ref[0], - item = _ref[1]; - if (item instanceof SfItem === false) { - item = new SfItem(item); - } - var output = serializeKey(key); - if (item.value === true) { - output += serializeParams(item.params); - } else { - output += '='; - if (Array.isArray(item.value)) { - output += serializeInnerList(item); - } else { - output += serializeItem(item); - } - } - return output; - }).join("," + optionalWhiteSpace); - } - - /** - * Encode an object into a structured field dictionary - * - * @param input - The structured field dictionary to encode - * @returns The structured field string - * - * @group Structured Field - * - * @beta - */ - function encodeSfDict(value, options) { - return serializeDict(value, options); - } - - /** - * Checks if the given key is a token field. - * - * @param key - The key to check. - * - * @returns `true` if the key is a token field. - * - * @internal - * - * @group CMCD - */ - var isTokenField = function isTokenField(key) { - return key === 'ot' || key === 'sf' || key === 'st'; - }; - - var isValid = function isValid(value) { - if (typeof value === 'number') { - return isFiniteNumber(value); - } - return value != null && value !== '' && value !== false; - }; - - /** - * Constructs a relative path from a URL. - * - * @param url - The destination URL - * @param base - The base URL - * @returns The relative path - * - * @group Utils - * - * @beta - */ - function urlToRelativePath(url, base) { - var to = new URL(url); - var from = new URL(base); - if (to.origin !== from.origin) { - return url; - } - var toPath = to.pathname.split('/').slice(1); - var fromPath = from.pathname.split('/').slice(1, -1); - // remove common parents - while (toPath[0] === fromPath[0]) { - toPath.shift(); - fromPath.shift(); - } - // add back paths - while (fromPath.length) { - fromPath.shift(); - toPath.unshift('..'); - } - return toPath.join('/'); - } - - /** - * Generate a random v4 UUID - * - * @returns A random v4 UUID - * - * @group Utils - * - * @beta - */ - function uuid() { - try { - return crypto.randomUUID(); - } catch (error) { - try { - var url = URL.createObjectURL(new Blob()); - var _uuid = url.toString(); - URL.revokeObjectURL(url); - return _uuid.slice(_uuid.lastIndexOf('/') + 1); - } catch (error) { - var dt = new Date().getTime(); - var _uuid2 = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = (dt + Math.random() * 16) % 16 | 0; - dt = Math.floor(dt / 16); - return (c == 'x' ? r : r & 0x3 | 0x8).toString(16); - }); - return _uuid2; - } - } - } - - var toRounded = function toRounded(value) { - return Math.round(value); - }; - var toUrlSafe = function toUrlSafe(value, options) { - if (options != null && options.baseUrl) { - value = urlToRelativePath(value, options.baseUrl); - } - return encodeURIComponent(value); - }; - var toHundred = function toHundred(value) { - return toRounded(value / 100) * 100; - }; - /** - * The default formatters for CMCD values. - * - * @group CMCD - * - * @beta - */ - var CmcdFormatters = { - /** - * Bitrate (kbps) rounded integer - */ - br: toRounded, - /** - * Duration (milliseconds) rounded integer - */ - d: toRounded, - /** - * Buffer Length (milliseconds) rounded nearest 100ms - */ - bl: toHundred, - /** - * Deadline (milliseconds) rounded nearest 100ms - */ - dl: toHundred, - /** - * Measured Throughput (kbps) rounded nearest 100kbps - */ - mtp: toHundred, - /** - * Next Object Request URL encoded - */ - nor: toUrlSafe, - /** - * Requested maximum throughput (kbps) rounded nearest 100kbps - */ - rtp: toHundred, - /** - * Top Bitrate (kbps) rounded integer - */ - tb: toRounded - }; - - /** - * Internal CMCD processing function. - * - * @param obj - The CMCD object to process. - * @param map - The mapping function to use. - * @param options - Options for encoding. - * - * @internal - * - * @group CMCD - */ - function processCmcd(obj, options) { - var results = {}; - if (obj == null || typeof obj !== 'object') { - return results; - } - var keys = Object.keys(obj).sort(); - var formatters = _extends({}, CmcdFormatters, options == null ? void 0 : options.formatters); - var filter = options == null ? void 0 : options.filter; - keys.forEach(function (key) { - if (filter != null && filter(key)) { - return; - } - var value = obj[key]; - var formatter = formatters[key]; - if (formatter) { - value = formatter(value, options); - } - // Version should only be reported if not equal to 1. - if (key === 'v' && value === 1) { - return; - } - // Playback rate should only be sent if not equal to 1. - if (key == 'pr' && value === 1) { - return; - } - // ignore invalid values - if (!isValid(value)) { - return; - } - if (isTokenField(key) && typeof value === 'string') { - value = new SfToken(value); - } - results[key] = value; - }); - return results; - } - - /** - * Encode a CMCD object to a string. - * - * @param cmcd - The CMCD object to encode. - * @param options - Options for encoding. - * - * @returns The encoded CMCD string. - * - * @group CMCD - * - * @beta - */ - function encodeCmcd(cmcd, options) { - if (options === void 0) { - options = {}; - } - if (!cmcd) { - return ''; - } - return encodeSfDict(processCmcd(cmcd, options), _extends({ - whitespace: false - }, options)); - } - - /** - * Convert a CMCD data object to request headers - * - * @param cmcd - The CMCD data object to convert. - * @param options - Options for encoding the CMCD object. - * - * @returns The CMCD header shards. - * - * @group CMCD - * - * @beta - */ - function toCmcdHeaders(cmcd, options) { - var _options; - if (options === void 0) { - options = {}; - } - if (!cmcd) { - return {}; - } - var entries = Object.entries(cmcd); - var headerMap = Object.entries(CmcdHeaderMap).concat(Object.entries(((_options = options) == null ? void 0 : _options.customHeaderMap) || {})); - var shards = entries.reduce(function (acc, entry) { - var _headerMap$find, _acc$field; - var key = entry[0], - value = entry[1]; - var field = ((_headerMap$find = headerMap.find(function (entry) { - return entry[1].includes(key); - })) == null ? void 0 : _headerMap$find[0]) || CmcdHeaderField.REQUEST; - (_acc$field = acc[field]) != null ? _acc$field : acc[field] = {}; - acc[field][key] = value; - return acc; - }, {}); - return Object.entries(shards).reduce(function (acc, _ref) { - var field = _ref[0], - value = _ref[1]; - acc[field] = encodeCmcd(value, options); - return acc; - }, {}); - } - - /** - * Append CMCD query args to a header object. - * - * @param headers - The headers to append to. - * @param cmcd - The CMCD object to append. - * @param customHeaderMap - A map of custom CMCD keys to header fields. - * - * @returns The headers with the CMCD header shards appended. - * - * @group CMCD - * - * @beta - */ - function appendCmcdHeaders(headers, cmcd, options) { - return _extends(headers, toCmcdHeaders(cmcd, options)); - } - - /** - * CMCD parameter name. - * - * @group CMCD - * - * @beta - */ - var CMCD_PARAM = 'CMCD'; - - /** - * Convert a CMCD data object to a query arg. - * - * @param cmcd - The CMCD object to convert. - * @param options - Options for encoding the CMCD object. - * - * @returns The CMCD query arg. - * - * @group CMCD - * - * @beta - */ - function toCmcdQuery(cmcd, options) { - if (options === void 0) { - options = {}; - } - if (!cmcd) { - return ''; - } - var params = encodeCmcd(cmcd, options); - return CMCD_PARAM + "=" + encodeURIComponent(params); - } - - var REGEX = /CMCD=[^&#]+/; - /** - * Append CMCD query args to a URL. - * - * @param url - The URL to append to. - * @param cmcd - The CMCD object to append. - * @param options - Options for encoding the CMCD object. - * - * @returns The URL with the CMCD query args appended. - * - * @group CMCD - * - * @beta - */ - function appendCmcdQuery(url, cmcd, options) { - // TODO: Replace with URLSearchParams once we drop Safari < 10.1 & Chrome < 49 support. - // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams - var query = toCmcdQuery(cmcd, options); - if (!query) { - return url; - } - if (REGEX.test(url)) { - return url.replace(REGEX, query); - } - var separator = url.includes('?') ? '&' : '?'; - return "" + url + separator + query; - } - - /** - * Controller to deal with Common Media Client Data (CMCD) - * @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf - */ - var CMCDController = /*#__PURE__*/function () { - // eslint-disable-line no-restricted-globals - - function CMCDController(hls) { - var _this = this; - this.hls = void 0; - this.config = void 0; - this.media = void 0; - this.sid = void 0; - this.cid = void 0; - this.useHeaders = false; - this.includeKeys = void 0; - this.initialized = false; - this.starved = false; - this.buffering = true; - this.audioBuffer = void 0; - // eslint-disable-line no-restricted-globals - this.videoBuffer = void 0; - this.onWaiting = function () { - if (_this.initialized) { - _this.starved = true; - } - _this.buffering = true; - }; - this.onPlaying = function () { - if (!_this.initialized) { - _this.initialized = true; - } - _this.buffering = false; - }; - /** - * Apply CMCD data to a manifest request. - */ - this.applyPlaylistData = function (context) { - try { - _this.apply(context, { - ot: CmObjectType.MANIFEST, - su: !_this.initialized - }); - } catch (error) { - logger.warn('Could not generate manifest CMCD data.', error); - } - }; - /** - * Apply CMCD data to a segment request - */ - this.applyFragmentData = function (context) { - try { - var fragment = context.frag; - var level = _this.hls.levels[fragment.level]; - var ot = _this.getObjectType(fragment); - var data = { - d: fragment.duration * 1000, - ot: ot - }; - if (ot === CmObjectType.VIDEO || ot === CmObjectType.AUDIO || ot == CmObjectType.MUXED) { - data.br = level.bitrate / 1000; - data.tb = _this.getTopBandwidth(ot) / 1000; - data.bl = _this.getBufferLength(ot); - } - _this.apply(context, data); - } catch (error) { - logger.warn('Could not generate segment CMCD data.', error); - } - }; - this.hls = hls; - var config = this.config = hls.config; - var cmcd = config.cmcd; - if (cmcd != null) { - config.pLoader = this.createPlaylistLoader(); - config.fLoader = this.createFragmentLoader(); - this.sid = cmcd.sessionId || uuid(); - this.cid = cmcd.contentId; - this.useHeaders = cmcd.useHeaders === true; - this.includeKeys = cmcd.includeKeys; - this.registerListeners(); - } - } - var _proto = CMCDController.prototype; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this); - hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this); - hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); - }; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.onMediaDetached(); - - // @ts-ignore - this.hls = this.config = this.audioBuffer = this.videoBuffer = null; - // @ts-ignore - this.onWaiting = this.onPlaying = null; - }; - _proto.onMediaAttached = function onMediaAttached(event, data) { - this.media = data.media; - this.media.addEventListener('waiting', this.onWaiting); - this.media.addEventListener('playing', this.onPlaying); - }; - _proto.onMediaDetached = function onMediaDetached() { - if (!this.media) { - return; - } - this.media.removeEventListener('waiting', this.onWaiting); - this.media.removeEventListener('playing', this.onPlaying); - - // @ts-ignore - this.media = null; - }; - _proto.onBufferCreated = function onBufferCreated(event, data) { - var _data$tracks$audio, _data$tracks$video; - this.audioBuffer = (_data$tracks$audio = data.tracks.audio) == null ? void 0 : _data$tracks$audio.buffer; - this.videoBuffer = (_data$tracks$video = data.tracks.video) == null ? void 0 : _data$tracks$video.buffer; - }; - /** - * Create baseline CMCD data - */ - _proto.createData = function createData() { - var _this$media; - return { - v: 1, - sf: CmStreamingFormat.HLS, - sid: this.sid, - cid: this.cid, - pr: (_this$media = this.media) == null ? void 0 : _this$media.playbackRate, - mtp: this.hls.bandwidthEstimate / 1000 - }; - } - - /** - * Apply CMCD data to a request. - */; - _proto.apply = function apply(context, data) { - if (data === void 0) { - data = {}; - } - // apply baseline data - _extends(data, this.createData()); - var isVideo = data.ot === CmObjectType.INIT || data.ot === CmObjectType.VIDEO || data.ot === CmObjectType.MUXED; - if (this.starved && isVideo) { - data.bs = true; - data.su = true; - this.starved = false; - } - if (data.su == null) { - data.su = this.buffering; - } - - // TODO: Implement rtp, nrr, nor, dl - - var includeKeys = this.includeKeys; - if (includeKeys) { - data = Object.keys(data).reduce(function (acc, key) { - includeKeys.includes(key) && (acc[key] = data[key]); - return acc; - }, {}); - } - if (this.useHeaders) { - if (!context.headers) { - context.headers = {}; - } - appendCmcdHeaders(context.headers, data); - } else { - context.url = appendCmcdQuery(context.url, data); - } - }; - /** - * The CMCD object type. - */ - _proto.getObjectType = function getObjectType(fragment) { - var type = fragment.type; - if (type === 'subtitle') { - return CmObjectType.TIMED_TEXT; - } - if (fragment.sn === 'initSegment') { - return CmObjectType.INIT; - } - if (type === 'audio') { - return CmObjectType.AUDIO; - } - if (type === 'main') { - if (!this.hls.audioTracks.length) { - return CmObjectType.MUXED; - } - return CmObjectType.VIDEO; - } - return undefined; - } - - /** - * Get the highest bitrate. - */; - _proto.getTopBandwidth = function getTopBandwidth(type) { - var bitrate = 0; - var levels; - var hls = this.hls; - if (type === CmObjectType.AUDIO) { - levels = hls.audioTracks; - } else { - var max = hls.maxAutoLevel; - var len = max > -1 ? max + 1 : hls.levels.length; - levels = hls.levels.slice(0, len); - } - for (var _iterator = _createForOfIteratorHelperLoose(levels), _step; !(_step = _iterator()).done;) { - var level = _step.value; - if (level.bitrate > bitrate) { - bitrate = level.bitrate; - } - } - return bitrate > 0 ? bitrate : NaN; - } - - /** - * Get the buffer length for a media type in milliseconds - */; - _proto.getBufferLength = function getBufferLength(type) { - var media = this.hls.media; - var buffer = type === CmObjectType.AUDIO ? this.audioBuffer : this.videoBuffer; - if (!buffer || !media) { - return NaN; - } - var info = BufferHelper.bufferInfo(buffer, media.currentTime, this.config.maxBufferHole); - return info.len * 1000; - } - - /** - * Create a playlist loader - */; - _proto.createPlaylistLoader = function createPlaylistLoader() { - var pLoader = this.config.pLoader; - var apply = this.applyPlaylistData; - var Ctor = pLoader || this.config.loader; - return /*#__PURE__*/function () { - function CmcdPlaylistLoader(config) { - this.loader = void 0; - this.loader = new Ctor(config); - } - var _proto2 = CmcdPlaylistLoader.prototype; - _proto2.destroy = function destroy() { - this.loader.destroy(); - }; - _proto2.abort = function abort() { - this.loader.abort(); - }; - _proto2.load = function load(context, config, callbacks) { - apply(context); - this.loader.load(context, config, callbacks); - }; - _createClass(CmcdPlaylistLoader, [{ - key: "stats", - get: function get() { - return this.loader.stats; - } - }, { - key: "context", - get: function get() { - return this.loader.context; - } - }]); - return CmcdPlaylistLoader; - }(); - } - - /** - * Create a playlist loader - */; - _proto.createFragmentLoader = function createFragmentLoader() { - var fLoader = this.config.fLoader; - var apply = this.applyFragmentData; - var Ctor = fLoader || this.config.loader; - return /*#__PURE__*/function () { - function CmcdFragmentLoader(config) { - this.loader = void 0; - this.loader = new Ctor(config); - } - var _proto3 = CmcdFragmentLoader.prototype; - _proto3.destroy = function destroy() { - this.loader.destroy(); - }; - _proto3.abort = function abort() { - this.loader.abort(); - }; - _proto3.load = function load(context, config, callbacks) { - apply(context); - this.loader.load(context, config, callbacks); - }; - _createClass(CmcdFragmentLoader, [{ - key: "stats", - get: function get() { - return this.loader.stats; - } - }, { - key: "context", - get: function get() { - return this.loader.context; - } - }]); - return CmcdFragmentLoader; - }(); - }; - return CMCDController; - }(); - - var PATHWAY_PENALTY_DURATION_MS = 300000; - var ContentSteeringController = /*#__PURE__*/function () { - function ContentSteeringController(hls) { - this.hls = void 0; - this.log = void 0; - this.loader = null; - this.uri = null; - this.pathwayId = '.'; - this.pathwayPriority = null; - this.timeToLoad = 300; - this.reloadTimer = -1; - this.updated = 0; - this.started = false; - this.enabled = true; - this.levels = null; - this.audioTracks = null; - this.subtitleTracks = null; - this.penalizedPathways = {}; - this.hls = hls; - this.log = logger.log.bind(logger, "[content-steering]:"); - this.registerListeners(); - } - var _proto = ContentSteeringController.prototype; - _proto.registerListeners = function registerListeners() { - var hls = this.hls; - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.ERROR, this.onError, this); - }; - _proto.unregisterListeners = function unregisterListeners() { - var hls = this.hls; - if (!hls) { - return; - } - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.ERROR, this.onError, this); - }; - _proto.startLoad = function startLoad() { - this.started = true; - this.clearTimeout(); - if (this.enabled && this.uri) { - if (this.updated) { - var ttl = this.timeToLoad * 1000 - (performance.now() - this.updated); - if (ttl > 0) { - this.scheduleRefresh(this.uri, ttl); - return; - } - } - this.loadSteeringManifest(this.uri); - } - }; - _proto.stopLoad = function stopLoad() { - this.started = false; - if (this.loader) { - this.loader.destroy(); - this.loader = null; - } - this.clearTimeout(); - }; - _proto.clearTimeout = function clearTimeout() { - if (this.reloadTimer !== -1) { - self.clearTimeout(this.reloadTimer); - this.reloadTimer = -1; - } - }; - _proto.destroy = function destroy() { - this.unregisterListeners(); - this.stopLoad(); - // @ts-ignore - this.hls = null; - this.levels = this.audioTracks = this.subtitleTracks = null; - }; - _proto.removeLevel = function removeLevel(levelToRemove) { - var levels = this.levels; - if (levels) { - this.levels = levels.filter(function (level) { - return level !== levelToRemove; - }); - } - }; - _proto.onManifestLoading = function onManifestLoading() { - this.stopLoad(); - this.enabled = true; - this.timeToLoad = 300; - this.updated = 0; - this.uri = null; - this.pathwayId = '.'; - this.levels = this.audioTracks = this.subtitleTracks = null; - }; - _proto.onManifestLoaded = function onManifestLoaded(event, data) { - var contentSteering = data.contentSteering; - if (contentSteering === null) { - return; - } - this.pathwayId = contentSteering.pathwayId; - this.uri = contentSteering.uri; - if (this.started) { - this.startLoad(); - } - }; - _proto.onManifestParsed = function onManifestParsed(event, data) { - this.audioTracks = data.audioTracks; - this.subtitleTracks = data.subtitleTracks; - }; - _proto.onError = function onError(event, data) { - var errorAction = data.errorAction; - if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox && errorAction.flags === ErrorActionFlags.MoveAllAlternatesMatchingHost) { - var levels = this.levels; - var pathwayPriority = this.pathwayPriority; - var errorPathway = this.pathwayId; - if (data.context) { - var _data$context = data.context, - groupId = _data$context.groupId, - _pathwayId = _data$context.pathwayId, - type = _data$context.type; - if (groupId && levels) { - errorPathway = this.getPathwayForGroupId(groupId, type, errorPathway); - } else if (_pathwayId) { - errorPathway = _pathwayId; - } - } - if (!(errorPathway in this.penalizedPathways)) { - this.penalizedPathways[errorPathway] = performance.now(); - } - if (!pathwayPriority && levels) { - // If PATHWAY-PRIORITY was not provided, list pathways for error handling - pathwayPriority = levels.reduce(function (pathways, level) { - if (pathways.indexOf(level.pathwayId) === -1) { - pathways.push(level.pathwayId); - } - return pathways; - }, []); - } - if (pathwayPriority && pathwayPriority.length > 1) { - this.updatePathwayPriority(pathwayPriority); - errorAction.resolved = this.pathwayId !== errorPathway; - } - if (!errorAction.resolved) { - logger.warn("Could not resolve " + data.details + " (\"" + data.error.message + "\") with content-steering for Pathway: " + errorPathway + " levels: " + (levels ? levels.length : levels) + " priorities: " + JSON.stringify(pathwayPriority) + " penalized: " + JSON.stringify(this.penalizedPathways)); - } - } - }; - _proto.filterParsedLevels = function filterParsedLevels(levels) { - // Filter levels to only include those that are in the initial pathway - this.levels = levels; - var pathwayLevels = this.getLevelsForPathway(this.pathwayId); - if (pathwayLevels.length === 0) { - var _pathwayId2 = levels[0].pathwayId; - this.log("No levels found in Pathway " + this.pathwayId + ". Setting initial Pathway to \"" + _pathwayId2 + "\""); - pathwayLevels = this.getLevelsForPathway(_pathwayId2); - this.pathwayId = _pathwayId2; - } - if (pathwayLevels.length !== levels.length) { - this.log("Found " + pathwayLevels.length + "/" + levels.length + " levels in Pathway \"" + this.pathwayId + "\""); - return pathwayLevels; - } - return levels; - }; - _proto.getLevelsForPathway = function getLevelsForPathway(pathwayId) { - if (this.levels === null) { - return []; - } - return this.levels.filter(function (level) { - return pathwayId === level.pathwayId; - }); - }; - _proto.updatePathwayPriority = function updatePathwayPriority(pathwayPriority) { - this.pathwayPriority = pathwayPriority; - var levels; - - // Evaluate if we should remove the pathway from the penalized list - var penalizedPathways = this.penalizedPathways; - var now = performance.now(); - Object.keys(penalizedPathways).forEach(function (pathwayId) { - if (now - penalizedPathways[pathwayId] > PATHWAY_PENALTY_DURATION_MS) { - delete penalizedPathways[pathwayId]; - } - }); - for (var i = 0; i < pathwayPriority.length; i++) { - var _pathwayId3 = pathwayPriority[i]; - if (_pathwayId3 in penalizedPathways) { - continue; - } - if (_pathwayId3 === this.pathwayId) { - return; - } - var selectedIndex = this.hls.nextLoadLevel; - var selectedLevel = this.hls.levels[selectedIndex]; - levels = this.getLevelsForPathway(_pathwayId3); - if (levels.length > 0) { - this.log("Setting Pathway to \"" + _pathwayId3 + "\""); - this.pathwayId = _pathwayId3; - reassignFragmentLevelIndexes(levels); - this.hls.trigger(Events.LEVELS_UPDATED, { - levels: levels - }); - // Set LevelController's level to trigger LEVEL_SWITCHING which loads playlist if needed - var levelAfterChange = this.hls.levels[selectedIndex]; - if (selectedLevel && levelAfterChange && this.levels) { - if (levelAfterChange.attrs['STABLE-VARIANT-ID'] !== selectedLevel.attrs['STABLE-VARIANT-ID'] && levelAfterChange.bitrate !== selectedLevel.bitrate) { - this.log("Unstable Pathways change from bitrate " + selectedLevel.bitrate + " to " + levelAfterChange.bitrate); - } - this.hls.nextLoadLevel = selectedIndex; - } - break; - } - } - }; - _proto.getPathwayForGroupId = function getPathwayForGroupId(groupId, type, defaultPathway) { - var levels = this.getLevelsForPathway(defaultPathway).concat(this.levels || []); - for (var i = 0; i < levels.length; i++) { - if (type === PlaylistContextType.AUDIO_TRACK && levels[i].hasAudioGroup(groupId) || type === PlaylistContextType.SUBTITLE_TRACK && levels[i].hasSubtitleGroup(groupId)) { - return levels[i].pathwayId; - } - } - return defaultPathway; - }; - _proto.clonePathways = function clonePathways(pathwayClones) { - var _this = this; - var levels = this.levels; - if (!levels) { - return; - } - var audioGroupCloneMap = {}; - var subtitleGroupCloneMap = {}; - pathwayClones.forEach(function (pathwayClone) { - var cloneId = pathwayClone.ID, - baseId = pathwayClone['BASE-ID'], - uriReplacement = pathwayClone['URI-REPLACEMENT']; - if (levels.some(function (level) { - return level.pathwayId === cloneId; - })) { - return; - } - var clonedVariants = _this.getLevelsForPathway(baseId).map(function (baseLevel) { - var attributes = new AttrList(baseLevel.attrs); - attributes['PATHWAY-ID'] = cloneId; - var clonedAudioGroupId = attributes.AUDIO && attributes.AUDIO + "_clone_" + cloneId; - var clonedSubtitleGroupId = attributes.SUBTITLES && attributes.SUBTITLES + "_clone_" + cloneId; - if (clonedAudioGroupId) { - audioGroupCloneMap[attributes.AUDIO] = clonedAudioGroupId; - attributes.AUDIO = clonedAudioGroupId; - } - if (clonedSubtitleGroupId) { - subtitleGroupCloneMap[attributes.SUBTITLES] = clonedSubtitleGroupId; - attributes.SUBTITLES = clonedSubtitleGroupId; - } - var url = performUriReplacement(baseLevel.uri, attributes['STABLE-VARIANT-ID'], 'PER-VARIANT-URIS', uriReplacement); - var clonedLevel = new Level({ - attrs: attributes, - audioCodec: baseLevel.audioCodec, - bitrate: baseLevel.bitrate, - height: baseLevel.height, - name: baseLevel.name, - url: url, - videoCodec: baseLevel.videoCodec, - width: baseLevel.width - }); - if (baseLevel.audioGroups) { - for (var i = 1; i < baseLevel.audioGroups.length; i++) { - clonedLevel.addGroupId('audio', baseLevel.audioGroups[i] + "_clone_" + cloneId); - } - } - if (baseLevel.subtitleGroups) { - for (var _i = 1; _i < baseLevel.subtitleGroups.length; _i++) { - clonedLevel.addGroupId('text', baseLevel.subtitleGroups[_i] + "_clone_" + cloneId); - } - } - return clonedLevel; - }); - levels.push.apply(levels, clonedVariants); - cloneRenditionGroups(_this.audioTracks, audioGroupCloneMap, uriReplacement, cloneId); - cloneRenditionGroups(_this.subtitleTracks, subtitleGroupCloneMap, uriReplacement, cloneId); - }); - }; - _proto.loadSteeringManifest = function loadSteeringManifest(uri) { - var _this2 = this; - var config = this.hls.config; - var Loader = config.loader; - if (this.loader) { - this.loader.destroy(); - } - this.loader = new Loader(config); - var url; - try { - url = new self.URL(uri); - } catch (error) { - this.enabled = false; - this.log("Failed to parse Steering Manifest URI: " + uri); - return; - } - if (url.protocol !== 'data:') { - var throughput = (this.hls.bandwidthEstimate || config.abrEwmaDefaultEstimate) | 0; - url.searchParams.set('_HLS_pathway', this.pathwayId); - url.searchParams.set('_HLS_throughput', '' + throughput); - } - var context = { - responseType: 'json', - url: url.href - }; - var loadPolicy = config.steeringManifestLoadPolicy.default; - var legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {}; - var loaderConfig = { - loadPolicy: loadPolicy, - timeout: loadPolicy.maxLoadTimeMs, - maxRetry: legacyRetryCompatibility.maxNumRetry || 0, - retryDelay: legacyRetryCompatibility.retryDelayMs || 0, - maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0 - }; - var callbacks = { - onSuccess: function onSuccess(response, stats, context, networkDetails) { - _this2.log("Loaded steering manifest: \"" + url + "\""); - var steeringData = response.data; - if (steeringData.VERSION !== 1) { - _this2.log("Steering VERSION " + steeringData.VERSION + " not supported!"); - return; - } - _this2.updated = performance.now(); - _this2.timeToLoad = steeringData.TTL; - var reloadUri = steeringData['RELOAD-URI'], - pathwayClones = steeringData['PATHWAY-CLONES'], - pathwayPriority = steeringData['PATHWAY-PRIORITY']; - if (reloadUri) { - try { - _this2.uri = new self.URL(reloadUri, url).href; - } catch (error) { - _this2.enabled = false; - _this2.log("Failed to parse Steering Manifest RELOAD-URI: " + reloadUri); - return; - } - } - _this2.scheduleRefresh(_this2.uri || context.url); - if (pathwayClones) { - _this2.clonePathways(pathwayClones); - } - var loadedSteeringData = { - steeringManifest: steeringData, - url: url.toString() - }; - _this2.hls.trigger(Events.STEERING_MANIFEST_LOADED, loadedSteeringData); - if (pathwayPriority) { - _this2.updatePathwayPriority(pathwayPriority); - } - }, - onError: function onError(error, context, networkDetails, stats) { - _this2.log("Error loading steering manifest: " + error.code + " " + error.text + " (" + context.url + ")"); - _this2.stopLoad(); - if (error.code === 410) { - _this2.enabled = false; - _this2.log("Steering manifest " + context.url + " no longer available"); - return; - } - var ttl = _this2.timeToLoad * 1000; - if (error.code === 429) { - var loader = _this2.loader; - if (typeof (loader == null ? void 0 : loader.getResponseHeader) === 'function') { - var retryAfter = loader.getResponseHeader('Retry-After'); - if (retryAfter) { - ttl = parseFloat(retryAfter) * 1000; - } - } - _this2.log("Steering manifest " + context.url + " rate limited"); - return; - } - _this2.scheduleRefresh(_this2.uri || context.url, ttl); - }, - onTimeout: function onTimeout(stats, context, networkDetails) { - _this2.log("Timeout loading steering manifest (" + context.url + ")"); - _this2.scheduleRefresh(_this2.uri || context.url); - } - }; - this.log("Requesting steering manifest: " + url); - this.loader.load(context, loaderConfig, callbacks); - }; - _proto.scheduleRefresh = function scheduleRefresh(uri, ttlMs) { - var _this3 = this; - if (ttlMs === void 0) { - ttlMs = this.timeToLoad * 1000; - } - this.clearTimeout(); - this.reloadTimer = self.setTimeout(function () { - var _this3$hls; - var media = (_this3$hls = _this3.hls) == null ? void 0 : _this3$hls.media; - if (media && !media.ended) { - _this3.loadSteeringManifest(uri); - return; - } - _this3.scheduleRefresh(uri, _this3.timeToLoad * 1000); - }, ttlMs); - }; - return ContentSteeringController; - }(); - function cloneRenditionGroups(tracks, groupCloneMap, uriReplacement, cloneId) { - if (!tracks) { - return; - } - Object.keys(groupCloneMap).forEach(function (audioGroupId) { - var clonedTracks = tracks.filter(function (track) { - return track.groupId === audioGroupId; - }).map(function (track) { - var clonedTrack = _extends({}, track); - clonedTrack.details = undefined; - clonedTrack.attrs = new AttrList(clonedTrack.attrs); - clonedTrack.url = clonedTrack.attrs.URI = performUriReplacement(track.url, track.attrs['STABLE-RENDITION-ID'], 'PER-RENDITION-URIS', uriReplacement); - clonedTrack.groupId = clonedTrack.attrs['GROUP-ID'] = groupCloneMap[audioGroupId]; - clonedTrack.attrs['PATHWAY-ID'] = cloneId; - return clonedTrack; - }); - tracks.push.apply(tracks, clonedTracks); - }); - } - function performUriReplacement(uri, stableId, perOptionKey, uriReplacement) { - var host = uriReplacement.HOST, - params = uriReplacement.PARAMS, - perOptionUris = uriReplacement[perOptionKey]; - var perVariantUri; - if (stableId) { - perVariantUri = perOptionUris == null ? void 0 : perOptionUris[stableId]; - if (perVariantUri) { - uri = perVariantUri; - } - } - var url = new self.URL(uri); - if (host && !perVariantUri) { - url.host = host; - } - if (params) { - Object.keys(params).sort().forEach(function (key) { - if (key) { - url.searchParams.set(key, params[key]); - } - }); - } - return url.href; - } - - var AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im; - var XhrLoader = /*#__PURE__*/function () { - function XhrLoader(config) { - this.xhrSetup = void 0; - this.requestTimeout = void 0; - this.retryTimeout = void 0; - this.retryDelay = void 0; - this.config = null; - this.callbacks = null; - this.context = null; - this.loader = null; - this.stats = void 0; - this.xhrSetup = config ? config.xhrSetup || null : null; - this.stats = new LoadStats(); - this.retryDelay = 0; - } - var _proto = XhrLoader.prototype; - _proto.destroy = function destroy() { - this.callbacks = null; - this.abortInternal(); - this.loader = null; - this.config = null; - this.context = null; - this.xhrSetup = null; - }; - _proto.abortInternal = function abortInternal() { - var loader = this.loader; - self.clearTimeout(this.requestTimeout); - self.clearTimeout(this.retryTimeout); - if (loader) { - loader.onreadystatechange = null; - loader.onprogress = null; - if (loader.readyState !== 4) { - this.stats.aborted = true; - loader.abort(); - } - } - }; - _proto.abort = function abort() { - var _this$callbacks; - this.abortInternal(); - if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { - this.callbacks.onAbort(this.stats, this.context, this.loader); - } - }; - _proto.load = function load(context, config, callbacks) { - if (this.stats.loading.start) { - throw new Error('Loader can only be used once.'); - } - this.stats.loading.start = self.performance.now(); - this.context = context; - this.config = config; - this.callbacks = callbacks; - this.loadInternal(); - }; - _proto.loadInternal = function loadInternal() { - var _this = this; - var config = this.config, - context = this.context; - if (!config || !context) { - return; - } - var xhr = this.loader = new self.XMLHttpRequest(); - var stats = this.stats; - stats.loading.first = 0; - stats.loaded = 0; - stats.aborted = false; - var xhrSetup = this.xhrSetup; - if (xhrSetup) { - Promise.resolve().then(function () { - if (_this.loader !== xhr || _this.stats.aborted) return; - return xhrSetup(xhr, context.url); - }).catch(function (error) { - if (_this.loader !== xhr || _this.stats.aborted) return; - xhr.open('GET', context.url, true); - return xhrSetup(xhr, context.url); - }).then(function () { - if (_this.loader !== xhr || _this.stats.aborted) return; - _this.openAndSendXhr(xhr, context, config); - }).catch(function (error) { - // IE11 throws an exception on xhr.open if attempting to access an HTTP resource over HTTPS - _this.callbacks.onError({ - code: xhr.status, - text: error.message - }, context, xhr, stats); - return; - }); - } else { - this.openAndSendXhr(xhr, context, config); - } - }; - _proto.openAndSendXhr = function openAndSendXhr(xhr, context, config) { - if (!xhr.readyState) { - xhr.open('GET', context.url, true); - } - var headers = context.headers; - var _config$loadPolicy = config.loadPolicy, - maxTimeToFirstByteMs = _config$loadPolicy.maxTimeToFirstByteMs, - maxLoadTimeMs = _config$loadPolicy.maxLoadTimeMs; - if (headers) { - for (var header in headers) { - xhr.setRequestHeader(header, headers[header]); - } - } - if (context.rangeEnd) { - xhr.setRequestHeader('Range', 'bytes=' + context.rangeStart + '-' + (context.rangeEnd - 1)); - } - xhr.onreadystatechange = this.readystatechange.bind(this); - xhr.onprogress = this.loadprogress.bind(this); - xhr.responseType = context.responseType; - // setup timeout before we perform request - self.clearTimeout(this.requestTimeout); - config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs; - this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout); - xhr.send(); - }; - _proto.readystatechange = function readystatechange() { - var context = this.context, - xhr = this.loader, - stats = this.stats; - if (!context || !xhr) { - return; - } - var readyState = xhr.readyState; - var config = this.config; - - // don't proceed if xhr has been aborted - if (stats.aborted) { - return; - } - - // >= HEADERS_RECEIVED - if (readyState >= 2) { - if (stats.loading.first === 0) { - stats.loading.first = Math.max(self.performance.now(), stats.loading.start); - // readyState >= 2 AND readyState !==4 (readyState = HEADERS_RECEIVED || LOADING) rearm timeout as xhr not finished yet - if (config.timeout !== config.loadPolicy.maxLoadTimeMs) { - self.clearTimeout(this.requestTimeout); - config.timeout = config.loadPolicy.maxLoadTimeMs; - this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.loadPolicy.maxLoadTimeMs - (stats.loading.first - stats.loading.start)); - } - } - if (readyState === 4) { - self.clearTimeout(this.requestTimeout); - xhr.onreadystatechange = null; - xhr.onprogress = null; - var _status = xhr.status; - // http status between 200 to 299 are all successful - var useResponse = xhr.responseType !== 'text'; - if (_status >= 200 && _status < 300 && (useResponse && xhr.response || xhr.responseText !== null)) { - stats.loading.end = Math.max(self.performance.now(), stats.loading.first); - var data = useResponse ? xhr.response : xhr.responseText; - var len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length; - stats.loaded = stats.total = len; - stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first); - if (!this.callbacks) { - return; - } - var onProgress = this.callbacks.onProgress; - if (onProgress) { - onProgress(stats, context, data, xhr); - } - if (!this.callbacks) { - return; - } - var response = { - url: xhr.responseURL, - data: data, - code: _status - }; - this.callbacks.onSuccess(response, stats, context, xhr); - } else { - var retryConfig = config.loadPolicy.errorRetry; - var retryCount = stats.retry; - // if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error - var _response = { - url: context.url, - data: undefined, - code: _status - }; - if (shouldRetry(retryConfig, retryCount, false, _response)) { - this.retry(retryConfig); - } else { - logger.error(_status + " while loading " + context.url); - this.callbacks.onError({ - code: _status, - text: xhr.statusText - }, context, xhr, stats); - } - } - } - } - }; - _proto.loadtimeout = function loadtimeout() { - if (!this.config) return; - var retryConfig = this.config.loadPolicy.timeoutRetry; - var retryCount = this.stats.retry; - if (shouldRetry(retryConfig, retryCount, true)) { - this.retry(retryConfig); - } else { - var _this$context; - logger.warn("timeout while loading " + ((_this$context = this.context) == null ? void 0 : _this$context.url)); - var callbacks = this.callbacks; - if (callbacks) { - this.abortInternal(); - callbacks.onTimeout(this.stats, this.context, this.loader); - } - } - }; - _proto.retry = function retry(retryConfig) { - var context = this.context, - stats = this.stats; - this.retryDelay = getRetryDelay(retryConfig, stats.retry); - stats.retry++; - logger.warn((status ? 'HTTP Status ' + status : 'Timeout') + " while loading " + (context == null ? void 0 : context.url) + ", retrying " + stats.retry + "/" + retryConfig.maxNumRetry + " in " + this.retryDelay + "ms"); - // abort and reset internal state - this.abortInternal(); - this.loader = null; - // schedule retry - self.clearTimeout(this.retryTimeout); - this.retryTimeout = self.setTimeout(this.loadInternal.bind(this), this.retryDelay); - }; - _proto.loadprogress = function loadprogress(event) { - var stats = this.stats; - stats.loaded = event.loaded; - if (event.lengthComputable) { - stats.total = event.total; - } - }; - _proto.getCacheAge = function getCacheAge() { - var result = null; - if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) { - var ageHeader = this.loader.getResponseHeader('age'); - result = ageHeader ? parseFloat(ageHeader) : null; - } - return result; - }; - _proto.getResponseHeader = function getResponseHeader(name) { - if (this.loader && new RegExp("^" + name + ":\\s*[\\d.]+\\s*$", 'im').test(this.loader.getAllResponseHeaders())) { - return this.loader.getResponseHeader(name); - } - return null; - }; - return XhrLoader; - }(); - - function fetchSupported() { - if ( - // @ts-ignore - self.fetch && self.AbortController && self.ReadableStream && self.Request) { - try { - new self.ReadableStream({}); // eslint-disable-line no-new - return true; - } catch (e) { - /* noop */ - } - } - return false; - } - var BYTERANGE = /(\d+)-(\d+)\/(\d+)/; - var FetchLoader = /*#__PURE__*/function () { - function FetchLoader(config /* HlsConfig */) { - this.fetchSetup = void 0; - this.requestTimeout = void 0; - this.request = null; - this.response = null; - this.controller = void 0; - this.context = null; - this.config = null; - this.callbacks = null; - this.stats = void 0; - this.loader = null; - this.fetchSetup = config.fetchSetup || getRequest; - this.controller = new self.AbortController(); - this.stats = new LoadStats(); - } - var _proto = FetchLoader.prototype; - _proto.destroy = function destroy() { - this.loader = this.callbacks = this.context = this.config = this.request = null; - this.abortInternal(); - this.response = null; - // @ts-ignore - this.fetchSetup = this.controller = this.stats = null; - }; - _proto.abortInternal = function abortInternal() { - if (this.controller && !this.stats.loading.end) { - this.stats.aborted = true; - this.controller.abort(); - } - }; - _proto.abort = function abort() { - var _this$callbacks; - this.abortInternal(); - if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { - this.callbacks.onAbort(this.stats, this.context, this.response); - } - }; - _proto.load = function load(context, config, callbacks) { - var _this = this; - var stats = this.stats; - if (stats.loading.start) { - throw new Error('Loader can only be used once.'); - } - stats.loading.start = self.performance.now(); - var initParams = getRequestParameters(context, this.controller.signal); - var onProgress = callbacks.onProgress; - var isArrayBuffer = context.responseType === 'arraybuffer'; - var LENGTH = isArrayBuffer ? 'byteLength' : 'length'; - var _config$loadPolicy = config.loadPolicy, - maxTimeToFirstByteMs = _config$loadPolicy.maxTimeToFirstByteMs, - maxLoadTimeMs = _config$loadPolicy.maxLoadTimeMs; - this.context = context; - this.config = config; - this.callbacks = callbacks; - this.request = this.fetchSetup(context, initParams); - self.clearTimeout(this.requestTimeout); - config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs; - this.requestTimeout = self.setTimeout(function () { - _this.abortInternal(); - callbacks.onTimeout(stats, context, _this.response); - }, config.timeout); - self.fetch(this.request).then(function (response) { - _this.response = _this.loader = response; - var first = Math.max(self.performance.now(), stats.loading.start); - self.clearTimeout(_this.requestTimeout); - config.timeout = maxLoadTimeMs; - _this.requestTimeout = self.setTimeout(function () { - _this.abortInternal(); - callbacks.onTimeout(stats, context, _this.response); - }, maxLoadTimeMs - (first - stats.loading.start)); - if (!response.ok) { - var status = response.status, - statusText = response.statusText; - throw new FetchError(statusText || 'fetch, bad network response', status, response); - } - stats.loading.first = first; - stats.total = getContentLength(response.headers) || stats.total; - if (onProgress && isFiniteNumber(config.highWaterMark)) { - return _this.loadProgressively(response, stats, context, config.highWaterMark, onProgress); - } - if (isArrayBuffer) { - return response.arrayBuffer(); - } - if (context.responseType === 'json') { - return response.json(); - } - return response.text(); - }).then(function (responseData) { - var response = _this.response; - if (!response) { - throw new Error('loader destroyed'); - } - self.clearTimeout(_this.requestTimeout); - stats.loading.end = Math.max(self.performance.now(), stats.loading.first); - var total = responseData[LENGTH]; - if (total) { - stats.loaded = stats.total = total; - } - var loaderResponse = { - url: response.url, - data: responseData, - code: response.status - }; - if (onProgress && !isFiniteNumber(config.highWaterMark)) { - onProgress(stats, context, responseData, response); - } - callbacks.onSuccess(loaderResponse, stats, context, response); - }).catch(function (error) { - self.clearTimeout(_this.requestTimeout); - if (stats.aborted) { - return; - } - // CORS errors result in an undefined code. Set it to 0 here to align with XHR's behavior - // when destroying, 'error' itself can be undefined - var code = !error ? 0 : error.code || 0; - var text = !error ? null : error.message; - callbacks.onError({ - code: code, - text: text - }, context, error ? error.details : null, stats); - }); - }; - _proto.getCacheAge = function getCacheAge() { - var result = null; - if (this.response) { - var ageHeader = this.response.headers.get('age'); - result = ageHeader ? parseFloat(ageHeader) : null; - } - return result; - }; - _proto.getResponseHeader = function getResponseHeader(name) { - return this.response ? this.response.headers.get(name) : null; - }; - _proto.loadProgressively = function loadProgressively(response, stats, context, highWaterMark, onProgress) { - if (highWaterMark === void 0) { - highWaterMark = 0; - } - var chunkCache = new ChunkCache(); - var reader = response.body.getReader(); - var pump = function pump() { - return reader.read().then(function (data) { - if (data.done) { - if (chunkCache.dataLength) { - onProgress(stats, context, chunkCache.flush(), response); - } - return Promise.resolve(new ArrayBuffer(0)); - } - var chunk = data.value; - var len = chunk.length; - stats.loaded += len; - if (len < highWaterMark || chunkCache.dataLength) { - // The current chunk is too small to to be emitted or the cache already has data - // Push it to the cache - chunkCache.push(chunk); - if (chunkCache.dataLength >= highWaterMark) { - // flush in order to join the typed arrays - onProgress(stats, context, chunkCache.flush(), response); - } - } else { - // If there's nothing cached already, and the chache is large enough - // just emit the progress event - onProgress(stats, context, chunk, response); - } - return pump(); - }).catch(function () { - /* aborted */ - return Promise.reject(); - }); - }; - return pump(); - }; - return FetchLoader; - }(); - function getRequestParameters(context, signal) { - var initParams = { - method: 'GET', - mode: 'cors', - credentials: 'same-origin', - signal: signal, - headers: new self.Headers(_extends({}, context.headers)) - }; - if (context.rangeEnd) { - initParams.headers.set('Range', 'bytes=' + context.rangeStart + '-' + String(context.rangeEnd - 1)); - } - return initParams; - } - function getByteRangeLength(byteRangeHeader) { - var result = BYTERANGE.exec(byteRangeHeader); - if (result) { - return parseInt(result[2]) - parseInt(result[1]) + 1; - } - } - function getContentLength(headers) { - var contentRange = headers.get('Content-Range'); - if (contentRange) { - var byteRangeLength = getByteRangeLength(contentRange); - if (isFiniteNumber(byteRangeLength)) { - return byteRangeLength; - } - } - var contentLength = headers.get('Content-Length'); - if (contentLength) { - return parseInt(contentLength); - } - } - function getRequest(context, initParams) { - return new self.Request(context.url, initParams); - } - var FetchError = /*#__PURE__*/function (_Error) { - _inheritsLoose(FetchError, _Error); - function FetchError(message, code, details) { - var _this2; - _this2 = _Error.call(this, message) || this; - _this2.code = void 0; - _this2.details = void 0; - _this2.code = code; - _this2.details = details; - return _this2; - } - return FetchError; - }( /*#__PURE__*/_wrapNativeSuper(Error)); - - var WHITESPACE_CHAR = /\s/; - var Cues = { - newCue: function newCue(track, startTime, endTime, captionScreen) { - var result = []; - var row; - // the type data states this is VTTCue, but it can potentially be a TextTrackCue on old browsers - var cue; - var indenting; - var indent; - var text; - var Cue = self.VTTCue || self.TextTrackCue; - for (var r = 0; r < captionScreen.rows.length; r++) { - row = captionScreen.rows[r]; - indenting = true; - indent = 0; - text = ''; - if (!row.isEmpty()) { - var _track$cues; - for (var c = 0; c < row.chars.length; c++) { - if (WHITESPACE_CHAR.test(row.chars[c].uchar) && indenting) { - indent++; - } else { - text += row.chars[c].uchar; - indenting = false; - } - } - // To be used for cleaning-up orphaned roll-up captions - row.cueStartTime = startTime; - - // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE - if (startTime === endTime) { - endTime += 0.0001; - } - if (indent >= 16) { - indent--; - } else { - indent++; - } - var cueText = fixLineBreaks(text.trim()); - var id = generateCueId(startTime, endTime, cueText); - - // If this cue already exists in the track do not push it - if (!(track != null && (_track$cues = track.cues) != null && _track$cues.getCueById(id))) { - cue = new Cue(startTime, endTime, cueText); - cue.id = id; - cue.line = r + 1; - cue.align = 'left'; - // Clamp the position between 10 and 80 percent (CEA-608 PAC indent code) - // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/608toVTT.html#positioning-in-cea-608 - // Firefox throws an exception and captions break with out of bounds 0-100 values - cue.position = 10 + Math.min(80, Math.floor(indent * 8 / 32) * 10); - result.push(cue); - } - } - } - if (track && result.length) { - // Sort bottom cues in reverse order so that they render in line order when overlapping in Chrome - result.sort(function (cueA, cueB) { - if (cueA.line === 'auto' || cueB.line === 'auto') { - return 0; - } - if (cueA.line > 8 && cueB.line > 8) { - return cueB.line - cueA.line; - } - return cueA.line - cueB.line; - }); - result.forEach(function (cue) { - return addCueToTrack(track, cue); - }); - } - return result; - } - }; - - /** - * @deprecated use fragLoadPolicy.default - */ - - /** - * @deprecated use manifestLoadPolicy.default and playlistLoadPolicy.default - */ - - var defaultLoadPolicy = { - maxTimeToFirstByteMs: 8000, - maxLoadTimeMs: 20000, - timeoutRetry: null, - errorRetry: null - }; - - /** - * @ignore - * If possible, keep hlsDefaultConfig shallow - * It is cloned whenever a new Hls instance is created, by keeping the config - * shallow the properties are cloned, and we don't end up manipulating the default - */ - var hlsDefaultConfig = _objectSpread2(_objectSpread2({ - autoStartLoad: true, - // used by stream-controller - startPosition: -1, - // used by stream-controller - defaultAudioCodec: undefined, - // used by stream-controller - debug: false, - // used by logger - capLevelOnFPSDrop: false, - // used by fps-controller - capLevelToPlayerSize: false, - // used by cap-level-controller - ignoreDevicePixelRatio: false, - // used by cap-level-controller - preferManagedMediaSource: true, - initialLiveManifestSize: 1, - // used by stream-controller - maxBufferLength: 30, - // used by stream-controller - backBufferLength: Infinity, - // used by buffer-controller - frontBufferFlushThreshold: Infinity, - maxBufferSize: 60 * 1000 * 1000, - // used by stream-controller - maxBufferHole: 0.1, - // used by stream-controller - highBufferWatchdogPeriod: 2, - // used by stream-controller - nudgeOffset: 0.1, - // used by stream-controller - nudgeMaxRetry: 3, - // used by stream-controller - maxFragLookUpTolerance: 0.25, - // used by stream-controller - liveSyncDurationCount: 3, - // used by latency-controller - liveMaxLatencyDurationCount: Infinity, - // used by latency-controller - liveSyncDuration: undefined, - // used by latency-controller - liveMaxLatencyDuration: undefined, - // used by latency-controller - maxLiveSyncPlaybackRate: 1, - // used by latency-controller - liveDurationInfinity: false, - // used by buffer-controller - /** - * @deprecated use backBufferLength - */ - liveBackBufferLength: null, - // used by buffer-controller - maxMaxBufferLength: 600, - // used by stream-controller - enableWorker: true, - // used by transmuxer - workerPath: null, - // used by transmuxer - enableSoftwareAES: true, - // used by decrypter - startLevel: undefined, - // used by level-controller - startFragPrefetch: false, - // used by stream-controller - fpsDroppedMonitoringPeriod: 5000, - // used by fps-controller - fpsDroppedMonitoringThreshold: 0.2, - // used by fps-controller - appendErrorMaxRetry: 3, - // used by buffer-controller - loader: XhrLoader, - // loader: FetchLoader, - fLoader: undefined, - // used by fragment-loader - pLoader: undefined, - // used by playlist-loader - xhrSetup: undefined, - // used by xhr-loader - licenseXhrSetup: undefined, - // used by eme-controller - licenseResponseCallback: undefined, - // used by eme-controller - abrController: AbrController, - bufferController: BufferController, - capLevelController: CapLevelController, - errorController: ErrorController, - fpsController: FPSController, - stretchShortVideoTrack: false, - // used by mp4-remuxer - maxAudioFramesDrift: 1, - // used by mp4-remuxer - forceKeyFrameOnDiscontinuity: true, - // used by ts-demuxer - abrEwmaFastLive: 3, - // used by abr-controller - abrEwmaSlowLive: 9, - // used by abr-controller - abrEwmaFastVoD: 3, - // used by abr-controller - abrEwmaSlowVoD: 9, - // used by abr-controller - abrEwmaDefaultEstimate: 5e5, - // 500 kbps // used by abr-controller - abrEwmaDefaultEstimateMax: 5e6, - // 5 mbps - abrBandWidthFactor: 0.95, - // used by abr-controller - abrBandWidthUpFactor: 0.7, - // used by abr-controller - abrMaxWithRealBitrate: false, - // used by abr-controller - maxStarvationDelay: 4, - // used by abr-controller - maxLoadingDelay: 4, - // used by abr-controller - minAutoBitrate: 0, - // used by hls - emeEnabled: false, - // used by eme-controller - widevineLicenseUrl: undefined, - // used by eme-controller - drmSystems: {}, - // used by eme-controller - drmSystemOptions: {}, - // used by eme-controller - requestMediaKeySystemAccessFunc: requestMediaKeySystemAccess , - // used by eme-controller - testBandwidth: true, - progressive: false, - lowLatencyMode: true, - cmcd: undefined, - enableDateRangeMetadataCues: true, - enableEmsgMetadataCues: true, - enableID3MetadataCues: true, - useMediaCapabilities: true, - certLoadPolicy: { - default: defaultLoadPolicy - }, - keyLoadPolicy: { - default: { - maxTimeToFirstByteMs: 8000, - maxLoadTimeMs: 20000, - timeoutRetry: { - maxNumRetry: 1, - retryDelayMs: 1000, - maxRetryDelayMs: 20000, - backoff: 'linear' - }, - errorRetry: { - maxNumRetry: 8, - retryDelayMs: 1000, - maxRetryDelayMs: 20000, - backoff: 'linear' - } - } - }, - manifestLoadPolicy: { - default: { - maxTimeToFirstByteMs: Infinity, - maxLoadTimeMs: 20000, - timeoutRetry: { - maxNumRetry: 2, - retryDelayMs: 0, - maxRetryDelayMs: 0 - }, - errorRetry: { - maxNumRetry: 1, - retryDelayMs: 1000, - maxRetryDelayMs: 8000 - } - } - }, - playlistLoadPolicy: { - default: { - maxTimeToFirstByteMs: 10000, - maxLoadTimeMs: 20000, - timeoutRetry: { - maxNumRetry: 2, - retryDelayMs: 0, - maxRetryDelayMs: 0 - }, - errorRetry: { - maxNumRetry: 2, - retryDelayMs: 1000, - maxRetryDelayMs: 8000 - } - } - }, - fragLoadPolicy: { - default: { - maxTimeToFirstByteMs: 10000, - maxLoadTimeMs: 120000, - timeoutRetry: { - maxNumRetry: 4, - retryDelayMs: 0, - maxRetryDelayMs: 0 - }, - errorRetry: { - maxNumRetry: 6, - retryDelayMs: 1000, - maxRetryDelayMs: 8000 - } - } - }, - steeringManifestLoadPolicy: { - default: { - maxTimeToFirstByteMs: 10000, - maxLoadTimeMs: 20000, - timeoutRetry: { - maxNumRetry: 2, - retryDelayMs: 0, - maxRetryDelayMs: 0 - }, - errorRetry: { - maxNumRetry: 1, - retryDelayMs: 1000, - maxRetryDelayMs: 8000 - } - } - }, - // These default settings are deprecated in favor of the above policies - // and are maintained for backwards compatibility - manifestLoadingTimeOut: 10000, - manifestLoadingMaxRetry: 1, - manifestLoadingRetryDelay: 1000, - manifestLoadingMaxRetryTimeout: 64000, - levelLoadingTimeOut: 10000, - levelLoadingMaxRetry: 4, - levelLoadingRetryDelay: 1000, - levelLoadingMaxRetryTimeout: 64000, - fragLoadingTimeOut: 20000, - fragLoadingMaxRetry: 6, - fragLoadingRetryDelay: 1000, - fragLoadingMaxRetryTimeout: 64000 - }, timelineConfig()), {}, { - subtitleStreamController: SubtitleStreamController , - subtitleTrackController: SubtitleTrackController , - timelineController: TimelineController , - audioStreamController: AudioStreamController , - audioTrackController: AudioTrackController , - emeController: EMEController , - cmcdController: CMCDController , - contentSteeringController: ContentSteeringController - }); - function timelineConfig() { - return { - cueHandler: Cues, - // used by timeline-controller - enableWebVTT: true, - // used by timeline-controller - enableIMSC1: true, - // used by timeline-controller - enableCEA708Captions: true, - // used by timeline-controller - captionsTextTrack1Label: 'English', - // used by timeline-controller - captionsTextTrack1LanguageCode: 'en', - // used by timeline-controller - captionsTextTrack2Label: 'Spanish', - // used by timeline-controller - captionsTextTrack2LanguageCode: 'es', - // used by timeline-controller - captionsTextTrack3Label: 'Unknown CC', - // used by timeline-controller - captionsTextTrack3LanguageCode: '', - // used by timeline-controller - captionsTextTrack4Label: 'Unknown CC', - // used by timeline-controller - captionsTextTrack4LanguageCode: '', - // used by timeline-controller - renderTextTracksNatively: true - }; - } - - /** - * @ignore - */ - function mergeConfig(defaultConfig, userConfig) { - if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) { - throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration"); - } - if (userConfig.liveMaxLatencyDurationCount !== undefined && (userConfig.liveSyncDurationCount === undefined || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) { - throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"'); - } - if (userConfig.liveMaxLatencyDuration !== undefined && (userConfig.liveSyncDuration === undefined || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) { - throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"'); - } - var defaultsCopy = deepCpy(defaultConfig); - - // Backwards compatibility with deprecated config values - var deprecatedSettingTypes = ['manifest', 'level', 'frag']; - var deprecatedSettings = ['TimeOut', 'MaxRetry', 'RetryDelay', 'MaxRetryTimeout']; - deprecatedSettingTypes.forEach(function (type) { - var policyName = (type === 'level' ? 'playlist' : type) + "LoadPolicy"; - var policyNotSet = userConfig[policyName] === undefined; - var report = []; - deprecatedSettings.forEach(function (setting) { - var deprecatedSetting = type + "Loading" + setting; - var value = userConfig[deprecatedSetting]; - if (value !== undefined && policyNotSet) { - report.push(deprecatedSetting); - var settings = defaultsCopy[policyName].default; - userConfig[policyName] = { - default: settings - }; - switch (setting) { - case 'TimeOut': - settings.maxLoadTimeMs = value; - settings.maxTimeToFirstByteMs = value; - break; - case 'MaxRetry': - settings.errorRetry.maxNumRetry = value; - settings.timeoutRetry.maxNumRetry = value; - break; - case 'RetryDelay': - settings.errorRetry.retryDelayMs = value; - settings.timeoutRetry.retryDelayMs = value; - break; - case 'MaxRetryTimeout': - settings.errorRetry.maxRetryDelayMs = value; - settings.timeoutRetry.maxRetryDelayMs = value; - break; - } - } - }); - if (report.length) { - logger.warn("hls.js config: \"" + report.join('", "') + "\" setting(s) are deprecated, use \"" + policyName + "\": " + JSON.stringify(userConfig[policyName])); - } - }); - return _objectSpread2(_objectSpread2({}, defaultsCopy), userConfig); - } - function deepCpy(obj) { - if (obj && typeof obj === 'object') { - if (Array.isArray(obj)) { - return obj.map(deepCpy); - } - return Object.keys(obj).reduce(function (result, key) { - result[key] = deepCpy(obj[key]); - return result; - }, {}); - } - return obj; - } - - /** - * @ignore - */ - function enableStreamingMode(config) { - var currentLoader = config.loader; - if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) { - // If a developer has configured their own loader, respect that choice - logger.log('[config]: Custom loader detected, cannot enable progressive streaming'); - config.progressive = false; - } else { - var canStreamProgressively = fetchSupported(); - if (canStreamProgressively) { - config.loader = FetchLoader; - config.progressive = true; - config.enableSoftwareAES = true; - logger.log('[config]: Progressive streaming enabled, using FetchLoader'); - } - } - } - - var chromeOrFirefox; - var LevelController = /*#__PURE__*/function (_BasePlaylistControll) { - _inheritsLoose(LevelController, _BasePlaylistControll); - function LevelController(hls, contentSteeringController) { - var _this; - _this = _BasePlaylistControll.call(this, hls, '[level-controller]') || this; - _this._levels = []; - _this._firstLevel = -1; - _this._maxAutoLevel = -1; - _this._startLevel = void 0; - _this.currentLevel = null; - _this.currentLevelIndex = -1; - _this.manualLevelIndex = -1; - _this.steering = void 0; - _this.onParsedComplete = void 0; - _this.steering = contentSteeringController; - _this._registerListeners(); - return _this; - } - var _proto = LevelController.prototype; - _proto._registerListeners = function _registerListeners() { - var hls = this.hls; - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.on(Events.ERROR, this.onError, this); - }; - _proto._unregisterListeners = function _unregisterListeners() { - var hls = this.hls; - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); - hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.off(Events.ERROR, this.onError, this); - }; - _proto.destroy = function destroy() { - this._unregisterListeners(); - this.steering = null; - this.resetLevels(); - _BasePlaylistControll.prototype.destroy.call(this); - }; - _proto.stopLoad = function stopLoad() { - var levels = this._levels; - - // clean up live level details to force reload them, and reset load errors - levels.forEach(function (level) { - level.loadError = 0; - level.fragmentError = 0; - }); - _BasePlaylistControll.prototype.stopLoad.call(this); - }; - _proto.resetLevels = function resetLevels() { - this._startLevel = undefined; - this.manualLevelIndex = -1; - this.currentLevelIndex = -1; - this.currentLevel = null; - this._levels = []; - this._maxAutoLevel = -1; - }; - _proto.onManifestLoading = function onManifestLoading(event, data) { - this.resetLevels(); - }; - _proto.onManifestLoaded = function onManifestLoaded(event, data) { - var preferManagedMediaSource = this.hls.config.preferManagedMediaSource; - var levels = []; - var redundantSet = {}; - var generatePathwaySet = {}; - var resolutionFound = false; - var videoCodecFound = false; - var audioCodecFound = false; - data.levels.forEach(function (levelParsed) { - var _audioCodec, _videoCodec; - var attributes = levelParsed.attrs; - - // erase audio codec info if browser does not support mp4a.40.34. - // demuxer will autodetect codec and fallback to mpeg/audio - var audioCodec = levelParsed.audioCodec, - videoCodec = levelParsed.videoCodec; - if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) { - chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent)); - if (chromeOrFirefox) { - levelParsed.audioCodec = audioCodec = undefined; - } - } - if (audioCodec) { - levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource); - } - if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) { - videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec); - } - - // only keep levels with supported audio/video codecs - var width = levelParsed.width, - height = levelParsed.height, - unknownCodecs = levelParsed.unknownCodecs; - resolutionFound || (resolutionFound = !!(width && height)); - videoCodecFound || (videoCodecFound = !!videoCodec); - audioCodecFound || (audioCodecFound = !!audioCodec); - if (unknownCodecs != null && unknownCodecs.length || audioCodec && !areCodecsMediaSourceSupported(audioCodec, 'audio', preferManagedMediaSource) || videoCodec && !areCodecsMediaSourceSupported(videoCodec, 'video', preferManagedMediaSource)) { - return; - } - var CODECS = attributes.CODECS, - FRAMERATE = attributes['FRAME-RATE'], - HDCP = attributes['HDCP-LEVEL'], - PATHWAY = attributes['PATHWAY-ID'], - RESOLUTION = attributes.RESOLUTION, - VIDEO_RANGE = attributes['VIDEO-RANGE']; - var contentSteeringPrefix = (PATHWAY || '.') + "-"; - var levelKey = "" + contentSteeringPrefix + levelParsed.bitrate + "-" + RESOLUTION + "-" + FRAMERATE + "-" + CODECS + "-" + VIDEO_RANGE + "-" + HDCP; - if (!redundantSet[levelKey]) { - var level = new Level(levelParsed); - redundantSet[levelKey] = level; - generatePathwaySet[levelKey] = 1; - levels.push(level); - } else if (redundantSet[levelKey].uri !== levelParsed.url && !levelParsed.attrs['PATHWAY-ID']) { - // Assign Pathway IDs to Redundant Streams (default Pathways is ".". Redundant Streams "..", "...", and so on.) - // Content Steering controller to handles Pathway fallback on error - var pathwayCount = generatePathwaySet[levelKey] += 1; - levelParsed.attrs['PATHWAY-ID'] = new Array(pathwayCount + 1).join('.'); - var _level = new Level(levelParsed); - redundantSet[levelKey] = _level; - levels.push(_level); - } else { - redundantSet[levelKey].addGroupId('audio', attributes.AUDIO); - redundantSet[levelKey].addGroupId('text', attributes.SUBTITLES); - } - }); - this.filterAndSortMediaOptions(levels, data, resolutionFound, videoCodecFound, audioCodecFound); - }; - _proto.filterAndSortMediaOptions = function filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound) { - var _this2 = this; - var audioTracks = []; - var subtitleTracks = []; - var levels = filteredLevels; - - // remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled - if ((resolutionFound || videoCodecFound) && audioCodecFound) { - levels = levels.filter(function (_ref) { - var videoCodec = _ref.videoCodec, - videoRange = _ref.videoRange, - width = _ref.width, - height = _ref.height; - return (!!videoCodec || !!(width && height)) && isVideoRange(videoRange); - }); - } - if (levels.length === 0) { - // Dispatch error after MANIFEST_LOADED is done propagating - Promise.resolve().then(function () { - if (_this2.hls) { - if (data.levels.length) { - _this2.warn("One or more CODECS in variant not supported: " + JSON.stringify(data.levels[0].attrs)); - } - var error = new Error('no level with compatible codecs found in manifest'); - _this2.hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR, - fatal: true, - url: data.url, - error: error, - reason: error.message - }); - } - }); - return; - } - if (data.audioTracks) { - var preferManagedMediaSource = this.hls.config.preferManagedMediaSource; - audioTracks = data.audioTracks.filter(function (track) { - return !track.audioCodec || areCodecsMediaSourceSupported(track.audioCodec, 'audio', preferManagedMediaSource); - }); - // Assign ids after filtering as array indices by group-id - assignTrackIdsByGroup(audioTracks); - } - if (data.subtitles) { - subtitleTracks = data.subtitles; - assignTrackIdsByGroup(subtitleTracks); - } - // start bitrate is the first bitrate of the manifest - var unsortedLevels = levels.slice(0); - // sort levels from lowest to highest - levels.sort(function (a, b) { - if (a.attrs['HDCP-LEVEL'] !== b.attrs['HDCP-LEVEL']) { - return (a.attrs['HDCP-LEVEL'] || '') > (b.attrs['HDCP-LEVEL'] || '') ? 1 : -1; - } - // sort on height before bitrate for cap-level-controller - if (resolutionFound && a.height !== b.height) { - return a.height - b.height; - } - if (a.frameRate !== b.frameRate) { - return a.frameRate - b.frameRate; - } - if (a.videoRange !== b.videoRange) { - return VideoRangeValues.indexOf(a.videoRange) - VideoRangeValues.indexOf(b.videoRange); - } - if (a.videoCodec !== b.videoCodec) { - var valueA = videoCodecPreferenceValue(a.videoCodec); - var valueB = videoCodecPreferenceValue(b.videoCodec); - if (valueA !== valueB) { - return valueB - valueA; - } - } - if (a.uri === b.uri && a.codecSet !== b.codecSet) { - var _valueA = codecsSetSelectionPreferenceValue(a.codecSet); - var _valueB = codecsSetSelectionPreferenceValue(b.codecSet); - if (_valueA !== _valueB) { - return _valueB - _valueA; - } - } - if (a.averageBitrate !== b.averageBitrate) { - return a.averageBitrate - b.averageBitrate; - } - return 0; - }); - var firstLevelInPlaylist = unsortedLevels[0]; - if (this.steering) { - levels = this.steering.filterParsedLevels(levels); - if (levels.length !== unsortedLevels.length) { - for (var i = 0; i < unsortedLevels.length; i++) { - if (unsortedLevels[i].pathwayId === levels[0].pathwayId) { - firstLevelInPlaylist = unsortedLevels[i]; - break; - } - } - } - } - this._levels = levels; - - // find index of first level in sorted levels - for (var _i = 0; _i < levels.length; _i++) { - if (levels[_i] === firstLevelInPlaylist) { - var _this$hls$userConfig; - this._firstLevel = _i; - var firstLevelBitrate = firstLevelInPlaylist.bitrate; - var bandwidthEstimate = this.hls.bandwidthEstimate; - this.log("manifest loaded, " + levels.length + " level(s) found, first bitrate: " + firstLevelBitrate); - // Update default bwe to first variant bitrate as long it has not been configured or set - if (((_this$hls$userConfig = this.hls.userConfig) == null ? void 0 : _this$hls$userConfig.abrEwmaDefaultEstimate) === undefined) { - var startingBwEstimate = Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax); - if (startingBwEstimate > bandwidthEstimate && bandwidthEstimate === hlsDefaultConfig.abrEwmaDefaultEstimate) { - this.hls.bandwidthEstimate = startingBwEstimate; - } - } - break; - } - } - - // Audio is only alternate if manifest include a URI along with the audio group tag, - // and this is not an audio-only stream where levels contain audio-only - var audioOnly = audioCodecFound && !videoCodecFound; - var edata = { - levels: levels, - audioTracks: audioTracks, - subtitleTracks: subtitleTracks, - sessionData: data.sessionData, - sessionKeys: data.sessionKeys, - firstLevel: this._firstLevel, - stats: data.stats, - audio: audioCodecFound, - video: videoCodecFound, - altAudio: !audioOnly && audioTracks.some(function (t) { - return !!t.url; - }) - }; - this.hls.trigger(Events.MANIFEST_PARSED, edata); - - // Initiate loading after all controllers have received MANIFEST_PARSED - if (this.hls.config.autoStartLoad || this.hls.forceStartLoad) { - this.hls.startLoad(this.hls.config.startPosition); - } - }; - _proto.onError = function onError(event, data) { - if (data.fatal || !data.context) { - return; - } - if (data.context.type === PlaylistContextType.LEVEL && data.context.level === this.level) { - this.checkRetry(data); - } - } - - // reset errors on the successful load of a fragment - ; - _proto.onFragBuffered = function onFragBuffered(event, _ref2) { - var frag = _ref2.frag; - if (frag !== undefined && frag.type === PlaylistLevelType.MAIN) { - var el = frag.elementaryStreams; - if (!Object.keys(el).some(function (type) { - return !!el[type]; - })) { - return; - } - var level = this._levels[frag.level]; - if (level != null && level.loadError) { - this.log("Resetting level error count of " + level.loadError + " on frag buffered"); - level.loadError = 0; - } - } - }; - _proto.onLevelLoaded = function onLevelLoaded(event, data) { - var _data$deliveryDirecti2; - var level = data.level, - details = data.details; - var curLevel = this._levels[level]; - if (!curLevel) { - var _data$deliveryDirecti; - this.warn("Invalid level index " + level); - if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) { - details.deltaUpdateFailed = true; - } - return; - } - - // only process level loaded events matching with expected level - if (level === this.currentLevelIndex) { - // reset level load error counter on successful level loaded only if there is no issues with fragments - if (curLevel.fragmentError === 0) { - curLevel.loadError = 0; - } - this.playlistLoaded(level, data, curLevel.details); - } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) { - // received a delta playlist update that cannot be merged - details.deltaUpdateFailed = true; - } - }; - _proto.loadPlaylist = function loadPlaylist(hlsUrlParameters) { - _BasePlaylistControll.prototype.loadPlaylist.call(this); - var currentLevelIndex = this.currentLevelIndex; - var currentLevel = this.currentLevel; - if (currentLevel && this.shouldLoadPlaylist(currentLevel)) { - var url = currentLevel.uri; - if (hlsUrlParameters) { - try { - url = hlsUrlParameters.addDirectives(url); - } catch (error) { - this.warn("Could not construct new URL with HLS Delivery Directives: " + error); - } - } - var pathwayId = currentLevel.attrs['PATHWAY-ID']; - this.log("Loading level index " + currentLevelIndex + ((hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== undefined ? ' at sn ' + hlsUrlParameters.msn + ' part ' + hlsUrlParameters.part : '') + " with" + (pathwayId ? ' Pathway ' + pathwayId : '') + " " + url); - - // console.log('Current audio track group ID:', this.hls.audioTracks[this.hls.audioTrack].groupId); - // console.log('New video quality level audio group id:', levelObject.attrs.AUDIO, level); - this.clearTimer(); - this.hls.trigger(Events.LEVEL_LOADING, { - url: url, - level: currentLevelIndex, - pathwayId: currentLevel.attrs['PATHWAY-ID'], - id: 0, - // Deprecated Level urlId - deliveryDirectives: hlsUrlParameters || null - }); - } - }; - _proto.removeLevel = function removeLevel(levelIndex) { - var _this3 = this, - _this$currentLevel; - var levels = this._levels.filter(function (level, index) { - if (index !== levelIndex) { - return true; - } - if (_this3.steering) { - _this3.steering.removeLevel(level); - } - if (level === _this3.currentLevel) { - _this3.currentLevel = null; - _this3.currentLevelIndex = -1; - if (level.details) { - level.details.fragments.forEach(function (f) { - return f.level = -1; - }); - } - } - return false; - }); - reassignFragmentLevelIndexes(levels); - this._levels = levels; - if (this.currentLevelIndex > -1 && (_this$currentLevel = this.currentLevel) != null && _this$currentLevel.details) { - this.currentLevelIndex = this.currentLevel.details.fragments[0].level; - } - this.hls.trigger(Events.LEVELS_UPDATED, { - levels: levels - }); - }; - _proto.onLevelsUpdated = function onLevelsUpdated(event, _ref3) { - var levels = _ref3.levels; - this._levels = levels; - }; - _proto.checkMaxAutoUpdated = function checkMaxAutoUpdated() { - var _this$hls = this.hls, - autoLevelCapping = _this$hls.autoLevelCapping, - maxAutoLevel = _this$hls.maxAutoLevel, - maxHdcpLevel = _this$hls.maxHdcpLevel; - if (this._maxAutoLevel !== maxAutoLevel) { - this._maxAutoLevel = maxAutoLevel; - this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, { - autoLevelCapping: autoLevelCapping, - levels: this.levels, - maxAutoLevel: maxAutoLevel, - minAutoLevel: this.hls.minAutoLevel, - maxHdcpLevel: maxHdcpLevel - }); - } - }; - _createClass(LevelController, [{ - key: "levels", - get: function get() { - if (this._levels.length === 0) { - return null; - } - return this._levels; - } - }, { - key: "level", - get: function get() { - return this.currentLevelIndex; - }, - set: function set(newLevel) { - var levels = this._levels; - if (levels.length === 0) { - return; - } - // check if level idx is valid - if (newLevel < 0 || newLevel >= levels.length) { - // invalid level id given, trigger error - var error = new Error('invalid level idx'); - var fatal = newLevel < 0; - this.hls.trigger(Events.ERROR, { - type: ErrorTypes.OTHER_ERROR, - details: ErrorDetails.LEVEL_SWITCH_ERROR, - level: newLevel, - fatal: fatal, - error: error, - reason: error.message - }); - if (fatal) { - return; - } - newLevel = Math.min(newLevel, levels.length - 1); - } - var lastLevelIndex = this.currentLevelIndex; - var lastLevel = this.currentLevel; - var lastPathwayId = lastLevel ? lastLevel.attrs['PATHWAY-ID'] : undefined; - var level = levels[newLevel]; - var pathwayId = level.attrs['PATHWAY-ID']; - this.currentLevelIndex = newLevel; - this.currentLevel = level; - if (lastLevelIndex === newLevel && level.details && lastLevel && lastPathwayId === pathwayId) { - return; - } - this.log("Switching to level " + newLevel + " (" + (level.height ? level.height + 'p ' : '') + (level.videoRange ? level.videoRange + ' ' : '') + (level.codecSet ? level.codecSet + ' ' : '') + "@" + level.bitrate + ")" + (pathwayId ? ' with Pathway ' + pathwayId : '') + " from level " + lastLevelIndex + (lastPathwayId ? ' with Pathway ' + lastPathwayId : '')); - var levelSwitchingData = { - level: newLevel, - attrs: level.attrs, - details: level.details, - bitrate: level.bitrate, - averageBitrate: level.averageBitrate, - maxBitrate: level.maxBitrate, - realBitrate: level.realBitrate, - width: level.width, - height: level.height, - codecSet: level.codecSet, - audioCodec: level.audioCodec, - videoCodec: level.videoCodec, - audioGroups: level.audioGroups, - subtitleGroups: level.subtitleGroups, - loaded: level.loaded, - loadError: level.loadError, - fragmentError: level.fragmentError, - name: level.name, - id: level.id, - uri: level.uri, - url: level.url, - urlId: 0, - audioGroupIds: level.audioGroupIds, - textGroupIds: level.textGroupIds - }; - this.hls.trigger(Events.LEVEL_SWITCHING, levelSwitchingData); - // check if we need to load playlist for this level - var levelDetails = level.details; - if (!levelDetails || levelDetails.live) { - // level not retrieved yet, or live playlist we need to (re)load it - var hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details, levelDetails); - this.loadPlaylist(hlsUrlParameters); - } - } - }, { - key: "manualLevel", - get: function get() { - return this.manualLevelIndex; - }, - set: function set(newLevel) { - this.manualLevelIndex = newLevel; - if (this._startLevel === undefined) { - this._startLevel = newLevel; - } - if (newLevel !== -1) { - this.level = newLevel; - } - } - }, { - key: "firstLevel", - get: function get() { - return this._firstLevel; - }, - set: function set(newLevel) { - this._firstLevel = newLevel; - } - }, { - key: "startLevel", - get: function get() { - // Setting hls.startLevel (this._startLevel) overrides config.startLevel - if (this._startLevel === undefined) { - var configStartLevel = this.hls.config.startLevel; - if (configStartLevel !== undefined) { - return configStartLevel; - } - return this.hls.firstAutoLevel; - } - return this._startLevel; - }, - set: function set(newLevel) { - this._startLevel = newLevel; - } - }, { - key: "nextLoadLevel", - get: function get() { - if (this.manualLevelIndex !== -1) { - return this.manualLevelIndex; - } else { - return this.hls.nextAutoLevel; - } - }, - set: function set(nextLevel) { - this.level = nextLevel; - if (this.manualLevelIndex === -1) { - this.hls.nextAutoLevel = nextLevel; - } - } - }]); - return LevelController; - }(BasePlaylistController); - function assignTrackIdsByGroup(tracks) { - var groups = {}; - tracks.forEach(function (track) { - var groupId = track.groupId || ''; - track.id = groups[groupId] = groups[groupId] || 0; - groups[groupId]++; - }); - } - - var KeyLoader = /*#__PURE__*/function () { - function KeyLoader(config) { - this.config = void 0; - this.keyUriToKeyInfo = {}; - this.emeController = null; - this.config = config; - } - var _proto = KeyLoader.prototype; - _proto.abort = function abort(type) { - for (var uri in this.keyUriToKeyInfo) { - var loader = this.keyUriToKeyInfo[uri].loader; - if (loader) { - var _loader$context; - if (type && type !== ((_loader$context = loader.context) == null ? void 0 : _loader$context.frag.type)) { - return; - } - loader.abort(); - } - } - }; - _proto.detach = function detach() { - for (var uri in this.keyUriToKeyInfo) { - var keyInfo = this.keyUriToKeyInfo[uri]; - // Remove cached EME keys on detach - if (keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption) { - delete this.keyUriToKeyInfo[uri]; - } - } - }; - _proto.destroy = function destroy() { - this.detach(); - for (var uri in this.keyUriToKeyInfo) { - var loader = this.keyUriToKeyInfo[uri].loader; - if (loader) { - loader.destroy(); - } - } - this.keyUriToKeyInfo = {}; - }; - _proto.createKeyLoadError = function createKeyLoadError(frag, details, error, networkDetails, response) { - if (details === void 0) { - details = ErrorDetails.KEY_LOAD_ERROR; - } - return new LoadError({ - type: ErrorTypes.NETWORK_ERROR, - details: details, - fatal: false, - frag: frag, - response: response, - error: error, - networkDetails: networkDetails - }); - }; - _proto.loadClear = function loadClear(loadingFrag, encryptedFragments) { - var _this = this; - if (this.emeController && this.config.emeEnabled) { - // access key-system with nearest key on start (loaidng frag is unencrypted) - var sn = loadingFrag.sn, - cc = loadingFrag.cc; - var _loop = function _loop() { - var frag = encryptedFragments[i]; - if (cc <= frag.cc && (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn)) { - _this.emeController.selectKeySystemFormat(frag).then(function (keySystemFormat) { - frag.setKeyFormat(keySystemFormat); - }); - return 1; // break - } - }; - for (var i = 0; i < encryptedFragments.length; i++) { - if (_loop()) break; - } - } - }; - _proto.load = function load(frag) { - var _this2 = this; - if (!frag.decryptdata && frag.encrypted && this.emeController) { - // Multiple keys, but none selected, resolve in eme-controller - return this.emeController.selectKeySystemFormat(frag).then(function (keySystemFormat) { - return _this2.loadInternal(frag, keySystemFormat); - }); - } - return this.loadInternal(frag); - }; - _proto.loadInternal = function loadInternal(frag, keySystemFormat) { - var _keyInfo, _keyInfo2; - if (keySystemFormat) { - frag.setKeyFormat(keySystemFormat); - } - var decryptdata = frag.decryptdata; - if (!decryptdata) { - var error = new Error(keySystemFormat ? "Expected frag.decryptdata to be defined after setting format " + keySystemFormat : 'Missing decryption data on fragment in onKeyLoading'); - return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error)); - } - var uri = decryptdata.uri; - if (!uri) { - return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error("Invalid key URI: \"" + uri + "\""))); - } - var keyInfo = this.keyUriToKeyInfo[uri]; - if ((_keyInfo = keyInfo) != null && _keyInfo.decryptdata.key) { - decryptdata.key = keyInfo.decryptdata.key; - return Promise.resolve({ - frag: frag, - keyInfo: keyInfo - }); - } - // Return key load promise as long as it does not have a mediakey session with an unusable key status - if ((_keyInfo2 = keyInfo) != null && _keyInfo2.keyLoadPromise) { - var _keyInfo$mediaKeySess; - switch ((_keyInfo$mediaKeySess = keyInfo.mediaKeySessionContext) == null ? void 0 : _keyInfo$mediaKeySess.keyStatus) { - case undefined: - case 'status-pending': - case 'usable': - case 'usable-in-future': - return keyInfo.keyLoadPromise.then(function (keyLoadedData) { - // Return the correct fragment with updated decryptdata key and loaded keyInfo - decryptdata.key = keyLoadedData.keyInfo.decryptdata.key; - return { - frag: frag, - keyInfo: keyInfo - }; - }); - } - // If we have a key session and status and it is not pending or usable, continue - // This will go back to the eme-controller for expired keys to get a new keyLoadPromise - } - - // Load the key or return the loading promise - keyInfo = this.keyUriToKeyInfo[uri] = { - decryptdata: decryptdata, - keyLoadPromise: null, - loader: null, - mediaKeySessionContext: null - }; - switch (decryptdata.method) { - case 'ISO-23001-7': - case 'SAMPLE-AES': - case 'SAMPLE-AES-CENC': - case 'SAMPLE-AES-CTR': - if (decryptdata.keyFormat === 'identity') { - // loadKeyHTTP handles http(s) and data URLs - return this.loadKeyHTTP(keyInfo, frag); - } - return this.loadKeyEME(keyInfo, frag); - case 'AES-128': - return this.loadKeyHTTP(keyInfo, frag); - default: - return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error("Key supplied with unsupported METHOD: \"" + decryptdata.method + "\""))); - } - }; - _proto.loadKeyEME = function loadKeyEME(keyInfo, frag) { - var keyLoadedData = { - frag: frag, - keyInfo: keyInfo - }; - if (this.emeController && this.config.emeEnabled) { - var keySessionContextPromise = this.emeController.loadKey(keyLoadedData); - if (keySessionContextPromise) { - return (keyInfo.keyLoadPromise = keySessionContextPromise.then(function (keySessionContext) { - keyInfo.mediaKeySessionContext = keySessionContext; - return keyLoadedData; - })).catch(function (error) { - // Remove promise for license renewal or retry - keyInfo.keyLoadPromise = null; - throw error; - }); - } - } - return Promise.resolve(keyLoadedData); - }; - _proto.loadKeyHTTP = function loadKeyHTTP(keyInfo, frag) { - var _this3 = this; - var config = this.config; - var Loader = config.loader; - var keyLoader = new Loader(config); - frag.keyLoader = keyInfo.loader = keyLoader; - return keyInfo.keyLoadPromise = new Promise(function (resolve, reject) { - var loaderContext = { - keyInfo: keyInfo, - frag: frag, - responseType: 'arraybuffer', - url: keyInfo.decryptdata.uri - }; - - // maxRetry is 0 so that instead of retrying the same key on the same variant multiple times, - // key-loader will trigger an error and rely on stream-controller to handle retry logic. - // this will also align retry logic with fragment-loader - var loadPolicy = config.keyLoadPolicy.default; - var loaderConfig = { - loadPolicy: loadPolicy, - timeout: loadPolicy.maxLoadTimeMs, - maxRetry: 0, - retryDelay: 0, - maxRetryDelay: 0 - }; - var loaderCallbacks = { - onSuccess: function onSuccess(response, stats, context, networkDetails) { - var frag = context.frag, - keyInfo = context.keyInfo, - uri = context.url; - if (!frag.decryptdata || keyInfo !== _this3.keyUriToKeyInfo[uri]) { - return reject(_this3.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error('after key load, decryptdata unset or changed'), networkDetails)); - } - keyInfo.decryptdata.key = frag.decryptdata.key = new Uint8Array(response.data); - - // detach fragment key loader on load success - frag.keyLoader = null; - keyInfo.loader = null; - resolve({ - frag: frag, - keyInfo: keyInfo - }); - }, - onError: function onError(response, context, networkDetails, stats) { - _this3.resetLoader(context); - reject(_this3.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error("HTTP Error " + response.code + " loading key " + response.text), networkDetails, _objectSpread2({ - url: loaderContext.url, - data: undefined - }, response))); - }, - onTimeout: function onTimeout(stats, context, networkDetails) { - _this3.resetLoader(context); - reject(_this3.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error('key loading timed out'), networkDetails)); - }, - onAbort: function onAbort(stats, context, networkDetails) { - _this3.resetLoader(context); - reject(_this3.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error('key loading aborted'), networkDetails)); - } - }; - keyLoader.load(loaderContext, loaderConfig, loaderCallbacks); - }); - }; - _proto.resetLoader = function resetLoader(context) { - var frag = context.frag, - keyInfo = context.keyInfo, - uri = context.url; - var loader = keyInfo.loader; - if (frag.keyLoader === loader) { - frag.keyLoader = null; - keyInfo.loader = null; - } - delete this.keyUriToKeyInfo[uri]; - if (loader) { - loader.destroy(); - } - }; - return KeyLoader; - }(); - - function getSourceBuffer() { - return self.SourceBuffer || self.WebKitSourceBuffer; - } - function isMSESupported() { - var mediaSource = getMediaSource(); - if (!mediaSource) { - return false; - } - - // if SourceBuffer is exposed ensure its API is valid - // Older browsers do not expose SourceBuffer globally so checking SourceBuffer.prototype is impossible - var sourceBuffer = getSourceBuffer(); - return !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === 'function' && typeof sourceBuffer.prototype.remove === 'function'; - } - function isSupported() { - if (!isMSESupported()) { - return false; - } - var mediaSource = getMediaSource(); - return typeof (mediaSource == null ? void 0 : mediaSource.isTypeSupported) === 'function' && (['avc1.42E01E,mp4a.40.2', 'av01.0.01M.08', 'vp09.00.50.08'].some(function (codecsForVideoContainer) { - return mediaSource.isTypeSupported(mimeTypeForCodec(codecsForVideoContainer, 'video')); - }) || ['mp4a.40.2', 'fLaC'].some(function (codecForAudioContainer) { - return mediaSource.isTypeSupported(mimeTypeForCodec(codecForAudioContainer, 'audio')); - })); - } - function changeTypeSupported() { - var _sourceBuffer$prototy; - var sourceBuffer = getSourceBuffer(); - return typeof (sourceBuffer == null ? void 0 : (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === 'function'; - } - - var STALL_MINIMUM_DURATION_MS = 250; - var MAX_START_GAP_JUMP = 2.0; - var SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1; - var SKIP_BUFFER_RANGE_START = 0.05; - var GapController = /*#__PURE__*/function () { - function GapController(config, media, fragmentTracker, hls) { - this.config = void 0; - this.media = null; - this.fragmentTracker = void 0; - this.hls = void 0; - this.nudgeRetry = 0; - this.stallReported = false; - this.stalled = null; - this.moved = false; - this.seeking = false; - this.config = config; - this.media = media; - this.fragmentTracker = fragmentTracker; - this.hls = hls; - } - var _proto = GapController.prototype; - _proto.destroy = function destroy() { - this.media = null; - // @ts-ignore - this.hls = this.fragmentTracker = null; - } - - /** - * Checks if the playhead is stuck within a gap, and if so, attempts to free it. - * A gap is an unbuffered range between two buffered ranges (or the start and the first buffered range). - * - * @param lastCurrentTime - Previously read playhead position - */; - _proto.poll = function poll(lastCurrentTime, activeFrag) { - var config = this.config, - media = this.media, - stalled = this.stalled; - if (media === null) { - return; - } - var currentTime = media.currentTime, - seeking = media.seeking; - var seeked = this.seeking && !seeking; - var beginSeek = !this.seeking && seeking; - this.seeking = seeking; - - // The playhead is moving, no-op - if (currentTime !== lastCurrentTime) { - this.moved = true; - if (!seeking) { - this.nudgeRetry = 0; - } - if (stalled !== null) { - // The playhead is now moving, but was previously stalled - if (this.stallReported) { - var _stalledDuration = self.performance.now() - stalled; - logger.warn("playback not stuck anymore @" + currentTime + ", after " + Math.round(_stalledDuration) + "ms"); - this.stallReported = false; - } - this.stalled = null; - } - return; - } - - // Clear stalled state when beginning or finishing seeking so that we don't report stalls coming out of a seek - if (beginSeek || seeked) { - this.stalled = null; - return; - } - - // The playhead should not be moving - if (media.paused && !seeking || media.ended || media.playbackRate === 0 || !BufferHelper.getBuffered(media).length) { - this.nudgeRetry = 0; - return; - } - var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); - var nextStart = bufferInfo.nextStart || 0; - if (seeking) { - // Waiting for seeking in a buffered range to complete - var hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP; - // Next buffered range is too far ahead to jump to while still seeking - var noBufferGap = !nextStart || activeFrag && activeFrag.start <= currentTime || nextStart - currentTime > MAX_START_GAP_JUMP && !this.fragmentTracker.getPartialFragment(currentTime); - if (hasEnoughBuffer || noBufferGap) { - return; - } - // Reset moved state when seeking to a point in or before a gap - this.moved = false; - } - - // Skip start gaps if we haven't played, but the last poll detected the start of a stall - // The addition poll gives the browser a chance to jump the gap for us - if (!this.moved && this.stalled !== null) { - var _level$details; - // There is no playable buffer (seeked, waiting for buffer) - var isBuffered = bufferInfo.len > 0; - if (!isBuffered && !nextStart) { - return; - } - // Jump start gaps within jump threshold - var startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime; - - // When joining a live stream with audio tracks, account for live playlist window sliding by allowing - // a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment - // that begins over 1 target duration after the video start position. - var level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null; - var isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live; - var maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP; - var partialOrGap = this.fragmentTracker.getPartialFragment(currentTime); - if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) { - if (!media.paused) { - this._trySkipBufferHole(partialOrGap); - } - return; - } - } - - // Start tracking stall time - var tnow = self.performance.now(); - if (stalled === null) { - this.stalled = tnow; - return; - } - var stalledDuration = tnow - stalled; - if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) { - // Report stalling after trying to fix - this._reportStall(bufferInfo); - if (!this.media) { - return; - } - } - var bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole); - this._tryFixBufferStall(bufferedWithHoles, stalledDuration); - } - - /** - * Detects and attempts to fix known buffer stalling issues. - * @param bufferInfo - The properties of the current buffer. - * @param stalledDurationMs - The amount of time Hls.js has been stalling for. - * @private - */; - _proto._tryFixBufferStall = function _tryFixBufferStall(bufferInfo, stalledDurationMs) { - var config = this.config, - fragmentTracker = this.fragmentTracker, - media = this.media; - if (media === null) { - return; - } - var currentTime = media.currentTime; - var partial = fragmentTracker.getPartialFragment(currentTime); - if (partial) { - // Try to skip over the buffer hole caused by a partial fragment - // This method isn't limited by the size of the gap between buffered ranges - var targetTime = this._trySkipBufferHole(partial); - // we return here in this case, meaning - // the branch below only executes when we haven't seeked to a new position - if (targetTime || !this.media) { - return; - } - } - - // if we haven't had to skip over a buffer hole of a partial fragment - // we may just have to "nudge" the playlist as the browser decoding/rendering engine - // needs to cross some sort of threshold covering all source-buffers content - // to start playing properly. - if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) { - logger.warn('Trying to nudge playhead over buffer-hole'); - // Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds - // We only try to jump the hole if it's under the configured size - // Reset stalled so to rearm watchdog timer - this.stalled = null; - this._tryNudgeBuffer(); - } - } - - /** - * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period. - * @param bufferLen - The playhead distance from the end of the current buffer segment. - * @private - */; - _proto._reportStall = function _reportStall(bufferInfo) { - var hls = this.hls, - media = this.media, - stallReported = this.stallReported; - if (!stallReported && media) { - // Report stalled error once - this.stallReported = true; - var error = new Error("Playback stalling at @" + media.currentTime + " due to low buffer (" + JSON.stringify(bufferInfo) + ")"); - logger.warn(error.message); - hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.BUFFER_STALLED_ERROR, - fatal: false, - error: error, - buffer: bufferInfo.len - }); - } - } - - /** - * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments - * @param partial - The partial fragment found at the current time (where playback is stalling). - * @private - */; - _proto._trySkipBufferHole = function _trySkipBufferHole(partial) { - var config = this.config, - hls = this.hls, - media = this.media; - if (media === null) { - return 0; - } - - // Check if currentTime is between unbuffered regions of partial fragments - var currentTime = media.currentTime; - var bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); - var startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart; - if (startTime) { - var bufferStarved = bufferInfo.len <= config.maxBufferHole; - var waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3; - var gapLength = startTime - currentTime; - if (gapLength > 0 && (bufferStarved || waiting)) { - // Only allow large gaps to be skipped if it is a start gap, or all fragments in skip range are partial - if (gapLength > config.maxBufferHole) { - var fragmentTracker = this.fragmentTracker; - var startGap = false; - if (currentTime === 0) { - var startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN); - if (startFrag && startTime < startFrag.end) { - startGap = true; - } - } - if (!startGap) { - var startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN); - if (startProvisioned) { - var moreToLoad = false; - var pos = startProvisioned.end; - while (pos < startTime) { - var provisioned = fragmentTracker.getPartialFragment(pos); - if (provisioned) { - pos += provisioned.duration; - } else { - moreToLoad = true; - break; - } - } - if (moreToLoad) { - return 0; - } - } - } - } - var targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS); - logger.warn("skipping hole, adjusting currentTime from " + currentTime + " to " + targetTime); - this.moved = true; - this.stalled = null; - media.currentTime = targetTime; - if (partial && !partial.gap) { - var error = new Error("fragment loaded with buffer holes, seeking from " + currentTime + " to " + targetTime); - hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, - fatal: false, - error: error, - reason: error.message, - frag: partial - }); - } - return targetTime; - } - } - return 0; - } - - /** - * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount. - * @private - */; - _proto._tryNudgeBuffer = function _tryNudgeBuffer() { - var config = this.config, - hls = this.hls, - media = this.media, - nudgeRetry = this.nudgeRetry; - if (media === null) { - return; - } - var currentTime = media.currentTime; - this.nudgeRetry++; - if (nudgeRetry < config.nudgeMaxRetry) { - var targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset; - // playback stalled in buffered area ... let's nudge currentTime to try to overcome this - var error = new Error("Nudging 'currentTime' from " + currentTime + " to " + targetTime); - logger.warn(error.message); - media.currentTime = targetTime; - hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.BUFFER_NUDGE_ON_STALL, - error: error, - fatal: false - }); - } else { - var _error = new Error("Playhead still not moving while enough data buffered @" + currentTime + " after " + config.nudgeMaxRetry + " nudges"); - logger.error(_error.message); - hls.trigger(Events.ERROR, { - type: ErrorTypes.MEDIA_ERROR, - details: ErrorDetails.BUFFER_STALLED_ERROR, - error: _error, - fatal: true - }); - } - }; - return GapController; - }(); - - var TICK_INTERVAL = 100; // how often to tick in ms - var StreamController = /*#__PURE__*/function (_BaseStreamController) { - _inheritsLoose(StreamController, _BaseStreamController); - function StreamController(hls, fragmentTracker, keyLoader) { - var _this; - _this = _BaseStreamController.call(this, hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN) || this; - _this.audioCodecSwap = false; - _this.gapController = null; - _this.level = -1; - _this._forceStartLoad = false; - _this.altAudio = false; - _this.audioOnly = false; - _this.fragPlaying = null; - _this.onvplaying = null; - _this.onvseeked = null; - _this.fragLastKbps = 0; - _this.couldBacktrack = false; - _this.backtrackFragment = null; - _this.audioCodecSwitch = false; - _this.videoBuffer = null; - _this._registerListeners(); - return _this; - } - var _proto = StreamController.prototype; - _proto._registerListeners = function _registerListeners() { - var hls = this.hls; - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this); - hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this); - hls.on(Events.ERROR, this.onError, this); - hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); - hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); - hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this); - hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); - hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - }; - _proto._unregisterListeners = function _unregisterListeners() { - var hls = this.hls; - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this); - hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this); - hls.off(Events.ERROR, this.onError, this); - hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); - hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this); - hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this); - hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); - hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - }; - _proto.onHandlerDestroying = function onHandlerDestroying() { - this._unregisterListeners(); - _BaseStreamController.prototype.onHandlerDestroying.call(this); - }; - _proto.startLoad = function startLoad(startPosition) { - if (this.levels) { - var lastCurrentTime = this.lastCurrentTime, - hls = this.hls; - this.stopLoad(); - this.setInterval(TICK_INTERVAL); - this.level = -1; - if (!this.startFragRequested) { - // determine load level - var startLevel = hls.startLevel; - if (startLevel === -1) { - if (hls.config.testBandwidth && this.levels.length > 1) { - // -1 : guess start Level by doing a bitrate test by loading first fragment of lowest quality level - startLevel = 0; - this.bitrateTest = true; - } else { - startLevel = hls.firstAutoLevel; - } - } - // set new level to playlist loader : this will trigger start level load - // hls.nextLoadLevel remains until it is set to a new value or until a new frag is successfully loaded - hls.nextLoadLevel = startLevel; - this.level = hls.loadLevel; - this.loadedmetadata = false; - } - // if startPosition undefined but lastCurrentTime set, set startPosition to last currentTime - if (lastCurrentTime > 0 && startPosition === -1) { - this.log("Override startPosition with lastCurrentTime @" + lastCurrentTime.toFixed(3)); - startPosition = lastCurrentTime; - } - this.state = State.IDLE; - this.nextLoadPosition = this.startPosition = this.lastCurrentTime = startPosition; - this.tick(); - } else { - this._forceStartLoad = true; - this.state = State.STOPPED; - } - }; - _proto.stopLoad = function stopLoad() { - this._forceStartLoad = false; - _BaseStreamController.prototype.stopLoad.call(this); - }; - _proto.doTick = function doTick() { - switch (this.state) { - case State.WAITING_LEVEL: - { - var levels = this.levels, - level = this.level; - var currentLevel = levels == null ? void 0 : levels[level]; - var details = currentLevel == null ? void 0 : currentLevel.details; - if (details && (!details.live || this.levelLastLoaded === currentLevel)) { - if (this.waitForCdnTuneIn(details)) { - break; - } - this.state = State.IDLE; - break; - } else if (this.hls.nextLoadLevel !== this.level) { - this.state = State.IDLE; - break; - } - break; - } - case State.FRAG_LOADING_WAITING_RETRY: - { - var _this$media; - var now = self.performance.now(); - var retryDate = this.retryDate; - // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading - if (!retryDate || now >= retryDate || (_this$media = this.media) != null && _this$media.seeking) { - var _levels = this.levels, - _level = this.level; - var _currentLevel = _levels == null ? void 0 : _levels[_level]; - this.resetStartWhenNotLoaded(_currentLevel || null); - this.state = State.IDLE; - } - } - break; - } - if (this.state === State.IDLE) { - this.doTickIdle(); - } - this.onTickEnd(); - }; - _proto.onTickEnd = function onTickEnd() { - _BaseStreamController.prototype.onTickEnd.call(this); - this.checkBuffer(); - this.checkFragmentChanged(); - }; - _proto.doTickIdle = function doTickIdle() { - var hls = this.hls, - levelLastLoaded = this.levelLastLoaded, - levels = this.levels, - media = this.media; - - // if start level not parsed yet OR - // if video not attached AND start fragment already requested OR start frag prefetch not enabled - // exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment - if (levelLastLoaded === null || !media && (this.startFragRequested || !hls.config.startFragPrefetch)) { - return; - } - - // If the "main" level is audio-only but we are loading an alternate track in the same group, do not load anything - if (this.altAudio && this.audioOnly) { - return; - } - var level = hls.nextLoadLevel; - if (!(levels != null && levels[level])) { - return; - } - var levelInfo = levels[level]; - - // if buffer length is less than maxBufLen try to load a new fragment - - var bufferInfo = this.getMainFwdBufferInfo(); - if (bufferInfo === null) { - return; - } - var lastDetails = this.getLevelDetails(); - if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) { - var data = {}; - if (this.altAudio) { - data.type = 'video'; - } - this.hls.trigger(Events.BUFFER_EOS, data); - this.state = State.ENDED; - return; - } - - // set next load level : this will trigger a playlist load if needed - if (hls.loadLevel !== level && hls.manualLevel === -1) { - this.log("Adapting to level " + level + " from level " + this.level); - } - this.level = hls.nextLoadLevel = level; - var levelDetails = levelInfo.details; - // if level info not retrieved yet, switch state and wait for level retrieval - // if live playlist, ensure that new playlist has been refreshed to avoid loading/try to load - // a useless and outdated fragment (that might even introduce load error if it is already out of the live playlist) - if (!levelDetails || this.state === State.WAITING_LEVEL || levelDetails.live && this.levelLastLoaded !== levelInfo) { - this.level = level; - this.state = State.WAITING_LEVEL; - return; - } - var bufferLen = bufferInfo.len; - - // compute max Buffer Length that we could get from this load level, based on level bitrate. don't buffer more than 60 MB and more than 30s - var maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate); - - // Stay idle if we are still with buffer margins - if (bufferLen >= maxBufLen) { - return; - } - if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) { - this.backtrackFragment = null; - } - var targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end; - var frag = this.getNextFragment(targetBufferTime, levelDetails); - // Avoid backtracking by loading an earlier segment in streams with segments that do not start with a key frame (flagged by `couldBacktrack`) - if (this.couldBacktrack && !this.fragPrevious && frag && frag.sn !== 'initSegment' && this.fragmentTracker.getState(frag) !== FragmentState.OK) { - var _this$backtrackFragme; - var backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn; - var fragIdx = backtrackSn - levelDetails.startSN; - var backtrackFrag = levelDetails.fragments[fragIdx - 1]; - if (backtrackFrag && frag.cc === backtrackFrag.cc) { - frag = backtrackFrag; - this.fragmentTracker.removeFragment(backtrackFrag); - } - } else if (this.backtrackFragment && bufferInfo.len) { - this.backtrackFragment = null; - } - // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags - if (frag && this.isLoopLoading(frag, targetBufferTime)) { - var gapStart = frag.gap; - if (!gapStart) { - // Cleanup the fragment tracker before trying to find the next unbuffered fragment - var type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO; - var mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media; - if (mediaBuffer) { - this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); - } - } - frag = this.getNextFragmentLoopLoading(frag, levelDetails, bufferInfo, PlaylistLevelType.MAIN, maxBufLen); - } - if (!frag) { - return; - } - if (frag.initSegment && !frag.initSegment.data && !this.bitrateTest) { - frag = frag.initSegment; - } - this.loadFragment(frag, levelInfo, targetBufferTime); - }; - _proto.loadFragment = function loadFragment(frag, level, targetBufferTime) { - // Check if fragment is not loaded - var fragState = this.fragmentTracker.getState(frag); - this.fragCurrent = frag; - if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) { - if (frag.sn === 'initSegment') { - this._loadInitSegment(frag, level); - } else if (this.bitrateTest) { - this.log("Fragment " + frag.sn + " of level " + frag.level + " is being downloaded to test bitrate and will not be buffered"); - this._loadBitrateTestFrag(frag, level); - } else { - this.startFragRequested = true; - _BaseStreamController.prototype.loadFragment.call(this, frag, level, targetBufferTime); - } - } else { - this.clearTrackerIfNeeded(frag); - } - }; - _proto.getBufferedFrag = function getBufferedFrag(position) { - return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN); - }; - _proto.followingBufferedFrag = function followingBufferedFrag(frag) { - if (frag) { - // try to get range of next fragment (500ms after this range) - return this.getBufferedFrag(frag.end + 0.5); - } - return null; - } - - /* - on immediate level switch : - - pause playback if playing - - cancel any pending load request - - and trigger a buffer flush - */; - _proto.immediateLevelSwitch = function immediateLevelSwitch() { - this.abortCurrentFrag(); - this.flushMainBuffer(0, Number.POSITIVE_INFINITY); - } - - /** - * try to switch ASAP without breaking video playback: - * in order to ensure smooth but quick level switching, - * we need to find the next flushable buffer range - * we should take into account new segment fetch time - */; - _proto.nextLevelSwitch = function nextLevelSwitch() { - var levels = this.levels, - media = this.media; - // ensure that media is defined and that metadata are available (to retrieve currentTime) - if (media != null && media.readyState) { - var fetchdelay; - var fragPlayingCurrent = this.getAppendedFrag(media.currentTime); - if (fragPlayingCurrent && fragPlayingCurrent.start > 1) { - // flush buffer preceding current fragment (flush until current fragment start offset) - // minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ... - this.flushMainBuffer(0, fragPlayingCurrent.start - 1); - } - var levelDetails = this.getLevelDetails(); - if (levelDetails != null && levelDetails.live) { - var bufferInfo = this.getMainFwdBufferInfo(); - // Do not flush in live stream with low buffer - if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) { - return; - } - } - if (!media.paused && levels) { - // add a safety delay of 1s - var nextLevelId = this.hls.nextLoadLevel; - var nextLevel = levels[nextLevelId]; - var fragLastKbps = this.fragLastKbps; - if (fragLastKbps && this.fragCurrent) { - fetchdelay = this.fragCurrent.duration * nextLevel.maxBitrate / (1000 * fragLastKbps) + 1; - } else { - fetchdelay = 0; - } - } else { - fetchdelay = 0; - } - // this.log('fetchdelay:'+fetchdelay); - // find buffer range that will be reached once new fragment will be fetched - var bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay); - if (bufferedFrag) { - // we can flush buffer range following this one without stalling playback - var nextBufferedFrag = this.followingBufferedFrag(bufferedFrag); - if (nextBufferedFrag) { - // if we are here, we can also cancel any loading/demuxing in progress, as they are useless - this.abortCurrentFrag(); - // start flush position is in next buffered frag. Leave some padding for non-independent segments and smoother playback. - var maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start; - var fragDuration = nextBufferedFrag.duration; - var startPts = Math.max(bufferedFrag.end, maxStart + Math.min(Math.max(fragDuration - this.config.maxFragLookUpTolerance, fragDuration * (this.couldBacktrack ? 0.5 : 0.125)), fragDuration * (this.couldBacktrack ? 0.75 : 0.25))); - this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY); - } - } - } - }; - _proto.abortCurrentFrag = function abortCurrentFrag() { - var fragCurrent = this.fragCurrent; - this.fragCurrent = null; - this.backtrackFragment = null; - if (fragCurrent) { - fragCurrent.abortRequests(); - this.fragmentTracker.removeFragment(fragCurrent); - } - switch (this.state) { - case State.KEY_LOADING: - case State.FRAG_LOADING: - case State.FRAG_LOADING_WAITING_RETRY: - case State.PARSING: - case State.PARSED: - this.state = State.IDLE; - break; - } - this.nextLoadPosition = this.getLoadPosition(); - }; - _proto.flushMainBuffer = function flushMainBuffer(startOffset, endOffset) { - _BaseStreamController.prototype.flushMainBuffer.call(this, startOffset, endOffset, this.altAudio ? 'video' : null); - }; - _proto.onMediaAttached = function onMediaAttached(event, data) { - _BaseStreamController.prototype.onMediaAttached.call(this, event, data); - var media = data.media; - this.onvplaying = this.onMediaPlaying.bind(this); - this.onvseeked = this.onMediaSeeked.bind(this); - media.addEventListener('playing', this.onvplaying); - media.addEventListener('seeked', this.onvseeked); - this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls); - }; - _proto.onMediaDetaching = function onMediaDetaching() { - var media = this.media; - if (media && this.onvplaying && this.onvseeked) { - media.removeEventListener('playing', this.onvplaying); - media.removeEventListener('seeked', this.onvseeked); - this.onvplaying = this.onvseeked = null; - this.videoBuffer = null; - } - this.fragPlaying = null; - if (this.gapController) { - this.gapController.destroy(); - this.gapController = null; - } - _BaseStreamController.prototype.onMediaDetaching.call(this); - }; - _proto.onMediaPlaying = function onMediaPlaying() { - // tick to speed up FRAG_CHANGED triggering - this.tick(); - }; - _proto.onMediaSeeked = function onMediaSeeked() { - var media = this.media; - var currentTime = media ? media.currentTime : null; - if (isFiniteNumber(currentTime)) { - this.log("Media seeked to " + currentTime.toFixed(3)); - } - - // If seeked was issued before buffer was appended do not tick immediately - var bufferInfo = this.getMainFwdBufferInfo(); - if (bufferInfo === null || bufferInfo.len === 0) { - this.warn("Main forward buffer length on \"seeked\" event " + (bufferInfo ? bufferInfo.len : 'empty') + ")"); - return; - } - - // tick to speed up FRAG_CHANGED triggering - this.tick(); - }; - _proto.onManifestLoading = function onManifestLoading() { - // reset buffer on manifest loading - this.log('Trigger BUFFER_RESET'); - this.hls.trigger(Events.BUFFER_RESET, undefined); - this.fragmentTracker.removeAllFragments(); - this.couldBacktrack = false; - this.startPosition = this.lastCurrentTime = this.fragLastKbps = 0; - this.levels = this.fragPlaying = this.backtrackFragment = this.levelLastLoaded = null; - this.altAudio = this.audioOnly = this.startFragRequested = false; - }; - _proto.onManifestParsed = function onManifestParsed(event, data) { - // detect if we have different kind of audio codecs used amongst playlists - var aac = false; - var heaac = false; - data.levels.forEach(function (level) { - var codec = level.audioCodec; - if (codec) { - aac = aac || codec.indexOf('mp4a.40.2') !== -1; - heaac = heaac || codec.indexOf('mp4a.40.5') !== -1; - } - }); - this.audioCodecSwitch = aac && heaac && !changeTypeSupported(); - if (this.audioCodecSwitch) { - this.log('Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC'); - } - this.levels = data.levels; - this.startFragRequested = false; - }; - _proto.onLevelLoading = function onLevelLoading(event, data) { - var levels = this.levels; - if (!levels || this.state !== State.IDLE) { - return; - } - var level = levels[data.level]; - if (!level.details || level.details.live && this.levelLastLoaded !== level || this.waitForCdnTuneIn(level.details)) { - this.state = State.WAITING_LEVEL; - } - }; - _proto.onLevelLoaded = function onLevelLoaded(event, data) { - var _curLevel$details; - var levels = this.levels; - var newLevelId = data.level; - var newDetails = data.details; - var duration = newDetails.totalduration; - if (!levels) { - this.warn("Levels were reset while loading level " + newLevelId); - return; - } - this.log("Level " + newLevelId + " loaded [" + newDetails.startSN + "," + newDetails.endSN + "]" + (newDetails.lastPartSn ? "[part-" + newDetails.lastPartSn + "-" + newDetails.lastPartIndex + "]" : '') + ", cc [" + newDetails.startCC + ", " + newDetails.endCC + "] duration:" + duration); - var curLevel = levels[newLevelId]; - var fragCurrent = this.fragCurrent; - if (fragCurrent && (this.state === State.FRAG_LOADING || this.state === State.FRAG_LOADING_WAITING_RETRY)) { - if (fragCurrent.level !== data.level && fragCurrent.loader) { - this.abortCurrentFrag(); - } - } - var sliding = 0; - if (newDetails.live || (_curLevel$details = curLevel.details) != null && _curLevel$details.live) { - var _this$levelLastLoaded; - this.checkLiveUpdate(newDetails); - if (newDetails.deltaUpdateFailed) { - return; - } - sliding = this.alignPlaylists(newDetails, curLevel.details, (_this$levelLastLoaded = this.levelLastLoaded) == null ? void 0 : _this$levelLastLoaded.details); - } - // override level info - curLevel.details = newDetails; - this.levelLastLoaded = curLevel; - this.hls.trigger(Events.LEVEL_UPDATED, { - details: newDetails, - level: newLevelId - }); - - // only switch back to IDLE state if we were waiting for level to start downloading a new fragment - if (this.state === State.WAITING_LEVEL) { - if (this.waitForCdnTuneIn(newDetails)) { - // Wait for Low-Latency CDN Tune-in - return; - } - this.state = State.IDLE; - } - if (!this.startFragRequested) { - this.setStartPosition(newDetails, sliding); - } else if (newDetails.live) { - this.synchronizeToLiveEdge(newDetails); - } - - // trigger handler right now - this.tick(); - }; - _proto._handleFragmentLoadProgress = function _handleFragmentLoadProgress(data) { - var _frag$initSegment; - var frag = data.frag, - part = data.part, - payload = data.payload; - var levels = this.levels; - if (!levels) { - this.warn("Levels were reset while fragment load was in progress. Fragment " + frag.sn + " of level " + frag.level + " will not be buffered"); - return; - } - var currentLevel = levels[frag.level]; - var details = currentLevel.details; - if (!details) { - this.warn("Dropping fragment " + frag.sn + " of level " + frag.level + " after level details were reset"); - this.fragmentTracker.removeFragment(frag); - return; - } - var videoCodec = currentLevel.videoCodec; - - // time Offset is accurate if level PTS is known, or if playlist is not sliding (not live) - var accurateTimeOffset = details.PTSKnown || !details.live; - var initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data; - var audioCodec = this._getAudioCodec(currentLevel); - - // transmux the MPEG-TS data to ISO-BMFF segments - // this.log(`Transmuxing ${frag.sn} of [${details.startSN} ,${details.endSN}],level ${frag.level}, cc ${frag.cc}`); - var transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)); - var partIndex = part ? part.index : -1; - var partial = partIndex !== -1; - var chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial); - var initPTS = this.initPTS[frag.cc]; - transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS); - }; - _proto.onAudioTrackSwitching = function onAudioTrackSwitching(event, data) { - // if any URL found on new audio track, it is an alternate audio track - var fromAltAudio = this.altAudio; - var altAudio = !!data.url; - // if we switch on main audio, ensure that main fragment scheduling is synced with media.buffered - // don't do anything if we switch to alt audio: audio stream controller is handling it. - // we will just have to change buffer scheduling on audioTrackSwitched - if (!altAudio) { - if (this.mediaBuffer !== this.media) { - this.log('Switching on main audio, use media.buffered to schedule main fragment loading'); - this.mediaBuffer = this.media; - var fragCurrent = this.fragCurrent; - // we need to refill audio buffer from main: cancel any frag loading to speed up audio switch - if (fragCurrent) { - this.log('Switching to main audio track, cancel main fragment load'); - fragCurrent.abortRequests(); - this.fragmentTracker.removeFragment(fragCurrent); - } - // destroy transmuxer to force init segment generation (following audio switch) - this.resetTransmuxer(); - // switch to IDLE state to load new fragment - this.resetLoadingState(); - } else if (this.audioOnly) { - // Reset audio transmuxer so when switching back to main audio we're not still appending where we left off - this.resetTransmuxer(); - } - var hls = this.hls; - // If switching from alt to main audio, flush all audio and trigger track switched - if (fromAltAudio) { - hls.trigger(Events.BUFFER_FLUSHING, { - startOffset: 0, - endOffset: Number.POSITIVE_INFINITY, - type: null - }); - this.fragmentTracker.removeAllFragments(); - } - hls.trigger(Events.AUDIO_TRACK_SWITCHED, data); - } - }; - _proto.onAudioTrackSwitched = function onAudioTrackSwitched(event, data) { - var trackId = data.id; - var altAudio = !!this.hls.audioTracks[trackId].url; - if (altAudio) { - var videoBuffer = this.videoBuffer; - // if we switched on alternate audio, ensure that main fragment scheduling is synced with video sourcebuffer buffered - if (videoBuffer && this.mediaBuffer !== videoBuffer) { - this.log('Switching on alternate audio, use video.buffered to schedule main fragment loading'); - this.mediaBuffer = videoBuffer; - } - } - this.altAudio = altAudio; - this.tick(); - }; - _proto.onBufferCreated = function onBufferCreated(event, data) { - var tracks = data.tracks; - var mediaTrack; - var name; - var alternate = false; - for (var type in tracks) { - var track = tracks[type]; - if (track.id === 'main') { - name = type; - mediaTrack = track; - // keep video source buffer reference - if (type === 'video') { - var videoTrack = tracks[type]; - if (videoTrack) { - this.videoBuffer = videoTrack.buffer; - } - } - } else { - alternate = true; - } - } - if (alternate && mediaTrack) { - this.log("Alternate track found, use " + name + ".buffered to schedule main fragment loading"); - this.mediaBuffer = mediaTrack.buffer; - } else { - this.mediaBuffer = this.media; - } - }; - _proto.onFragBuffered = function onFragBuffered(event, data) { - var frag = data.frag, - part = data.part; - if (frag && frag.type !== PlaylistLevelType.MAIN) { - return; - } - if (this.fragContextChanged(frag)) { - // If a level switch was requested while a fragment was buffering, it will emit the FRAG_BUFFERED event upon completion - // Avoid setting state back to IDLE, since that will interfere with a level switch - this.warn("Fragment " + frag.sn + (part ? ' p: ' + part.index : '') + " of level " + frag.level + " finished buffering, but was aborted. state: " + this.state); - if (this.state === State.PARSED) { - this.state = State.IDLE; - } - return; - } - var stats = part ? part.stats : frag.stats; - this.fragLastKbps = Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first)); - if (frag.sn !== 'initSegment') { - this.fragPrevious = frag; - } - this.fragBufferedComplete(frag, part); - }; - _proto.onError = function onError(event, data) { - var _data$context; - if (data.fatal) { - this.state = State.ERROR; - return; - } - switch (data.details) { - case ErrorDetails.FRAG_GAP: - case ErrorDetails.FRAG_PARSING_ERROR: - case ErrorDetails.FRAG_DECRYPT_ERROR: - case ErrorDetails.FRAG_LOAD_ERROR: - case ErrorDetails.FRAG_LOAD_TIMEOUT: - case ErrorDetails.KEY_LOAD_ERROR: - case ErrorDetails.KEY_LOAD_TIMEOUT: - this.onFragmentOrKeyLoadError(PlaylistLevelType.MAIN, data); - break; - case ErrorDetails.LEVEL_LOAD_ERROR: - case ErrorDetails.LEVEL_LOAD_TIMEOUT: - case ErrorDetails.LEVEL_PARSING_ERROR: - // in case of non fatal error while loading level, if level controller is not retrying to load level, switch back to IDLE - if (!data.levelRetry && this.state === State.WAITING_LEVEL && ((_data$context = data.context) == null ? void 0 : _data$context.type) === PlaylistContextType.LEVEL) { - this.state = State.IDLE; - } - break; - case ErrorDetails.BUFFER_APPEND_ERROR: - case ErrorDetails.BUFFER_FULL_ERROR: - if (!data.parent || data.parent !== 'main') { - return; - } - if (data.details === ErrorDetails.BUFFER_APPEND_ERROR) { - this.resetLoadingState(); - return; - } - if (this.reduceLengthAndFlushBuffer(data)) { - this.flushMainBuffer(0, Number.POSITIVE_INFINITY); - } - break; - case ErrorDetails.INTERNAL_EXCEPTION: - this.recoverWorkerError(data); - break; - } - } - - // Checks the health of the buffer and attempts to resolve playback stalls. - ; - _proto.checkBuffer = function checkBuffer() { - var media = this.media, - gapController = this.gapController; - if (!media || !gapController || !media.readyState) { - // Exit early if we don't have media or if the media hasn't buffered anything yet (readyState 0) - return; - } - if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) { - // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers - var activeFrag = this.state !== State.IDLE ? this.fragCurrent : null; - gapController.poll(this.lastCurrentTime, activeFrag); - } - this.lastCurrentTime = media.currentTime; - }; - _proto.onFragLoadEmergencyAborted = function onFragLoadEmergencyAborted() { - this.state = State.IDLE; - // if loadedmetadata is not set, it means that we are emergency switch down on first frag - // in that case, reset startFragRequested flag - if (!this.loadedmetadata) { - this.startFragRequested = false; - this.nextLoadPosition = this.startPosition; - } - this.tickImmediate(); - }; - _proto.onBufferFlushed = function onBufferFlushed(event, _ref) { - var type = _ref.type; - if (type !== ElementaryStreamTypes.AUDIO || this.audioOnly && !this.altAudio) { - var mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media; - this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); - this.tick(); - } - }; - _proto.onLevelsUpdated = function onLevelsUpdated(event, data) { - if (this.level > -1 && this.fragCurrent) { - this.level = this.fragCurrent.level; - } - this.levels = data.levels; - }; - _proto.swapAudioCodec = function swapAudioCodec() { - this.audioCodecSwap = !this.audioCodecSwap; - } - - /** - * Seeks to the set startPosition if not equal to the mediaElement's current time. - */; - _proto.seekToStartPos = function seekToStartPos() { - var media = this.media; - if (!media) { - return; - } - var currentTime = media.currentTime; - var startPosition = this.startPosition; - // only adjust currentTime if different from startPosition or if startPosition not buffered - // at that stage, there should be only one buffered range, as we reach that code after first fragment has been buffered - if (startPosition >= 0 && currentTime < startPosition) { - if (media.seeking) { - this.log("could not seek to " + startPosition + ", already seeking at " + currentTime); - return; - } - var buffered = BufferHelper.getBuffered(media); - var bufferStart = buffered.length ? buffered.start(0) : 0; - var delta = bufferStart - startPosition; - if (delta > 0 && (delta < this.config.maxBufferHole || delta < this.config.maxFragLookUpTolerance)) { - this.log("adjusting start position by " + delta + " to match buffer start"); - startPosition += delta; - this.startPosition = startPosition; - } - this.log("seek to target start position " + startPosition + " from current time " + currentTime); - media.currentTime = startPosition; - } - }; - _proto._getAudioCodec = function _getAudioCodec(currentLevel) { - var audioCodec = this.config.defaultAudioCodec || currentLevel.audioCodec; - if (this.audioCodecSwap && audioCodec) { - this.log('Swapping audio codec'); - if (audioCodec.indexOf('mp4a.40.5') !== -1) { - audioCodec = 'mp4a.40.2'; - } else { - audioCodec = 'mp4a.40.5'; - } - } - return audioCodec; - }; - _proto._loadBitrateTestFrag = function _loadBitrateTestFrag(frag, level) { - var _this2 = this; - frag.bitrateTest = true; - this._doFragLoad(frag, level).then(function (data) { - var hls = _this2.hls; - if (!data || _this2.fragContextChanged(frag)) { - return; - } - level.fragmentError = 0; - _this2.state = State.IDLE; - _this2.startFragRequested = false; - _this2.bitrateTest = false; - var stats = frag.stats; - // Bitrate tests fragments are neither parsed nor buffered - stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now(); - hls.trigger(Events.FRAG_LOADED, data); - frag.bitrateTest = false; - }); - }; - _proto._handleTransmuxComplete = function _handleTransmuxComplete(transmuxResult) { - var _id3$samples; - var id = 'main'; - var hls = this.hls; - var remuxResult = transmuxResult.remuxResult, - chunkMeta = transmuxResult.chunkMeta; - var context = this.getCurrentContext(chunkMeta); - if (!context) { - this.resetWhenMissingContext(chunkMeta); - return; - } - var frag = context.frag, - part = context.part, - level = context.level; - var video = remuxResult.video, - text = remuxResult.text, - id3 = remuxResult.id3, - initSegment = remuxResult.initSegment; - var details = level.details; - // The audio-stream-controller handles audio buffering if Hls.js is playing an alternate audio track - var audio = this.altAudio ? undefined : remuxResult.audio; - - // Check if the current fragment has been aborted. We check this by first seeing if we're still playing the current level. - // If we are, subsequently check if the currently loading fragment (fragCurrent) has changed. - if (this.fragContextChanged(frag)) { - this.fragmentTracker.removeFragment(frag); - return; - } - this.state = State.PARSING; - if (initSegment) { - if (initSegment != null && initSegment.tracks) { - var mapFragment = frag.initSegment || frag; - this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta); - hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { - frag: mapFragment, - id: id, - tracks: initSegment.tracks - }); - } - - // This would be nice if Number.isFinite acted as a typeguard, but it doesn't. See: https://github.com/Microsoft/TypeScript/issues/10038 - var initPTS = initSegment.initPTS; - var timescale = initSegment.timescale; - if (isFiniteNumber(initPTS)) { - this.initPTS[frag.cc] = { - baseTime: initPTS, - timescale: timescale - }; - hls.trigger(Events.INIT_PTS_FOUND, { - frag: frag, - id: id, - initPTS: initPTS, - timescale: timescale - }); - } - } - - // Avoid buffering if backtracking this fragment - if (video && details && frag.sn !== 'initSegment') { - var prevFrag = details.fragments[frag.sn - 1 - details.startSN]; - var isFirstFragment = frag.sn === details.startSN; - var isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc; - if (remuxResult.independent !== false) { - var startPTS = video.startPTS, - endPTS = video.endPTS, - startDTS = video.startDTS, - endDTS = video.endDTS; - if (part) { - part.elementaryStreams[video.type] = { - startPTS: startPTS, - endPTS: endPTS, - startDTS: startDTS, - endDTS: endDTS - }; - } else { - if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) { - this.couldBacktrack = true; - } - if (video.dropped && video.independent) { - // Backtrack if dropped frames create a gap after currentTime - - var bufferInfo = this.getMainFwdBufferInfo(); - var targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole; - var startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS; - if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) { - this.backtrack(frag); - return; - } else if (isFirstInDiscontinuity) { - // Mark segment with a gap to avoid loop loading - frag.gap = true; - } - // Set video stream start to fragment start so that truncated samples do not distort the timeline, and mark it partial - frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true); - } else if (isFirstFragment && startPTS > MAX_START_GAP_JUMP) { - // Mark segment with a gap to skip large start gap - frag.gap = true; - } - } - frag.setElementaryStreamInfo(video.type, startPTS, endPTS, startDTS, endDTS); - if (this.backtrackFragment) { - this.backtrackFragment = frag; - } - this.bufferFragmentData(video, frag, part, chunkMeta, isFirstFragment || isFirstInDiscontinuity); - } else if (isFirstFragment || isFirstInDiscontinuity) { - // Mark segment with a gap to avoid loop loading - frag.gap = true; - } else { - this.backtrack(frag); - return; - } - } - if (audio) { - var _startPTS = audio.startPTS, - _endPTS = audio.endPTS, - _startDTS = audio.startDTS, - _endDTS = audio.endDTS; - if (part) { - part.elementaryStreams[ElementaryStreamTypes.AUDIO] = { - startPTS: _startPTS, - endPTS: _endPTS, - startDTS: _startDTS, - endDTS: _endDTS - }; - } - frag.setElementaryStreamInfo(ElementaryStreamTypes.AUDIO, _startPTS, _endPTS, _startDTS, _endDTS); - this.bufferFragmentData(audio, frag, part, chunkMeta); - } - if (details && id3 != null && (_id3$samples = id3.samples) != null && _id3$samples.length) { - var emittedID3 = { - id: id, - frag: frag, - details: details, - samples: id3.samples - }; - hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3); - } - if (details && text) { - var emittedText = { - id: id, - frag: frag, - details: details, - samples: text.samples - }; - hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText); - } - }; - _proto._bufferInitSegment = function _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) { - var _this3 = this; - if (this.state !== State.PARSING) { - return; - } - this.audioOnly = !!tracks.audio && !tracks.video; - - // if audio track is expected to come from audio stream controller, discard any coming from main - if (this.altAudio && !this.audioOnly) { - delete tracks.audio; - } - // include levelCodec in audio and video tracks - var audio = tracks.audio, - video = tracks.video, - audiovideo = tracks.audiovideo; - if (audio) { - var audioCodec = currentLevel.audioCodec; - var ua = navigator.userAgent.toLowerCase(); - if (this.audioCodecSwitch) { - if (audioCodec) { - if (audioCodec.indexOf('mp4a.40.5') !== -1) { - audioCodec = 'mp4a.40.2'; - } else { - audioCodec = 'mp4a.40.5'; - } - } - // In the case that AAC and HE-AAC audio codecs are signalled in manifest, - // force HE-AAC, as it seems that most browsers prefers it. - // don't force HE-AAC if mono stream, or in Firefox - var audioMetadata = audio.metadata; - if (audioMetadata && 'channelCount' in audioMetadata && (audioMetadata.channelCount || 1) !== 1 && ua.indexOf('firefox') === -1) { - audioCodec = 'mp4a.40.5'; - } - } - // HE-AAC is broken on Android, always signal audio codec as AAC even if variant manifest states otherwise - if (audioCodec && audioCodec.indexOf('mp4a.40.5') !== -1 && ua.indexOf('android') !== -1 && audio.container !== 'audio/mpeg') { - // Exclude mpeg audio - audioCodec = 'mp4a.40.2'; - this.log("Android: force audio codec to " + audioCodec); - } - if (currentLevel.audioCodec && currentLevel.audioCodec !== audioCodec) { - this.log("Swapping manifest audio codec \"" + currentLevel.audioCodec + "\" for \"" + audioCodec + "\""); - } - audio.levelCodec = audioCodec; - audio.id = 'main'; - this.log("Init audio buffer, container:" + audio.container + ", codecs[selected/level/parsed]=[" + (audioCodec || '') + "/" + (currentLevel.audioCodec || '') + "/" + audio.codec + "]"); - } - if (video) { - video.levelCodec = currentLevel.videoCodec; - video.id = 'main'; - this.log("Init video buffer, container:" + video.container + ", codecs[level/parsed]=[" + (currentLevel.videoCodec || '') + "/" + video.codec + "]"); - } - if (audiovideo) { - this.log("Init audiovideo buffer, container:" + audiovideo.container + ", codecs[level/parsed]=[" + currentLevel.codecs + "/" + audiovideo.codec + "]"); - } - this.hls.trigger(Events.BUFFER_CODECS, tracks); - // loop through tracks that are going to be provided to bufferController - Object.keys(tracks).forEach(function (trackName) { - var track = tracks[trackName]; - var initSegment = track.initSegment; - if (initSegment != null && initSegment.byteLength) { - _this3.hls.trigger(Events.BUFFER_APPENDING, { - type: trackName, - data: initSegment, - frag: frag, - part: null, - chunkMeta: chunkMeta, - parent: frag.type - }); - } - }); - // trigger handler right now - this.tickImmediate(); - }; - _proto.getMainFwdBufferInfo = function getMainFwdBufferInfo() { - return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN); - }; - _proto.backtrack = function backtrack(frag) { - this.couldBacktrack = true; - // Causes findFragments to backtrack through fragments to find the keyframe - this.backtrackFragment = frag; - this.resetTransmuxer(); - this.flushBufferGap(frag); - this.fragmentTracker.removeFragment(frag); - this.fragPrevious = null; - this.nextLoadPosition = frag.start; - this.state = State.IDLE; - }; - _proto.checkFragmentChanged = function checkFragmentChanged() { - var video = this.media; - var fragPlayingCurrent = null; - if (video && video.readyState > 1 && video.seeking === false) { - var currentTime = video.currentTime; - /* if video element is in seeked state, currentTime can only increase. - (assuming that playback rate is positive ...) - As sometimes currentTime jumps back to zero after a - media decode error, check this, to avoid seeking back to - wrong position after a media decode error - */ - - if (BufferHelper.isBuffered(video, currentTime)) { - fragPlayingCurrent = this.getAppendedFrag(currentTime); - } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) { - /* ensure that FRAG_CHANGED event is triggered at startup, - when first video frame is displayed and playback is paused. - add a tolerance of 100ms, in case current position is not buffered, - check if current pos+100ms is buffered and use that buffer range - for FRAG_CHANGED event reporting */ - fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1); - } - if (fragPlayingCurrent) { - this.backtrackFragment = null; - var fragPlaying = this.fragPlaying; - var fragCurrentLevel = fragPlayingCurrent.level; - if (!fragPlaying || fragPlayingCurrent.sn !== fragPlaying.sn || fragPlaying.level !== fragCurrentLevel) { - this.fragPlaying = fragPlayingCurrent; - this.hls.trigger(Events.FRAG_CHANGED, { - frag: fragPlayingCurrent - }); - if (!fragPlaying || fragPlaying.level !== fragCurrentLevel) { - this.hls.trigger(Events.LEVEL_SWITCHED, { - level: fragCurrentLevel - }); - } - } - } - } - }; - _createClass(StreamController, [{ - key: "nextLevel", - get: function get() { - var frag = this.nextBufferedFrag; - if (frag) { - return frag.level; - } - return -1; - } - }, { - key: "currentFrag", - get: function get() { - var media = this.media; - if (media) { - return this.fragPlaying || this.getAppendedFrag(media.currentTime); - } - return null; - } - }, { - key: "currentProgramDateTime", - get: function get() { - var media = this.media; - if (media) { - var currentTime = media.currentTime; - var frag = this.currentFrag; - if (frag && isFiniteNumber(currentTime) && isFiniteNumber(frag.programDateTime)) { - var epocMs = frag.programDateTime + (currentTime - frag.start) * 1000; - return new Date(epocMs); - } - } - return null; - } - }, { - key: "currentLevel", - get: function get() { - var frag = this.currentFrag; - if (frag) { - return frag.level; - } - return -1; - } - }, { - key: "nextBufferedFrag", - get: function get() { - var frag = this.currentFrag; - if (frag) { - return this.followingBufferedFrag(frag); - } - return null; - } - }, { - key: "forceStartLoad", - get: function get() { - return this._forceStartLoad; - } - }]); - return StreamController; - }(BaseStreamController); - - /** - * The `Hls` class is the core of the HLS.js library used to instantiate player instances. - * @public - */ - var Hls = /*#__PURE__*/function () { - /** - * Check if the required MediaSource Extensions are available. - */ - Hls.isMSESupported = function isMSESupported$1() { - return isMSESupported(); - } - - /** - * Check if MediaSource Extensions are available and isTypeSupported checks pass for any baseline codecs. - */; - Hls.isSupported = function isSupported$1() { - return isSupported(); - } - - /** - * Get the MediaSource global used for MSE playback (ManagedMediaSource, MediaSource, or WebKitMediaSource). - */; - Hls.getMediaSource = function getMediaSource$1() { - return getMediaSource(); - }; - /** - * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`. - * @param userConfig - Configuration options applied over `Hls.DefaultConfig` - */ - function Hls(userConfig) { - if (userConfig === void 0) { - userConfig = {}; - } - /** - * The runtime configuration used by the player. At instantiation this is combination of `hls.userConfig` merged over `Hls.DefaultConfig`. - */ - this.config = void 0; - /** - * The configuration object provided on player instantiation. - */ - this.userConfig = void 0; - this.coreComponents = void 0; - this.networkControllers = void 0; - this.started = false; - this._emitter = new EventEmitter(); - this._autoLevelCapping = -1; - this._maxHdcpLevel = null; - this.abrController = void 0; - this.bufferController = void 0; - this.capLevelController = void 0; - this.latencyController = void 0; - this.levelController = void 0; - this.streamController = void 0; - this.audioTrackController = void 0; - this.subtitleTrackController = void 0; - this.emeController = void 0; - this.cmcdController = void 0; - this._media = null; - this.url = null; - this.triggeringException = void 0; - enableLogs(userConfig.debug || false, 'Hls instance'); - var config = this.config = mergeConfig(Hls.DefaultConfig, userConfig); - this.userConfig = userConfig; - if (config.progressive) { - enableStreamingMode(config); - } - - // core controllers and network loaders - var ConfigAbrController = config.abrController, - ConfigBufferController = config.bufferController, - ConfigCapLevelController = config.capLevelController, - ConfigErrorController = config.errorController, - ConfigFpsController = config.fpsController; - var errorController = new ConfigErrorController(this); - var abrController = this.abrController = new ConfigAbrController(this); - var bufferController = this.bufferController = new ConfigBufferController(this); - var capLevelController = this.capLevelController = new ConfigCapLevelController(this); - var fpsController = new ConfigFpsController(this); - var playListLoader = new PlaylistLoader(this); - var id3TrackController = new ID3TrackController(this); - var ConfigContentSteeringController = config.contentSteeringController; - // ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first - var contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null; - var levelController = this.levelController = new LevelController(this, contentSteering); - // FragmentTracker must be defined before StreamController because the order of event handling is important - var fragmentTracker = new FragmentTracker(this); - var keyLoader = new KeyLoader(this.config); - var streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader); - - // Cap level controller uses streamController to flush the buffer - capLevelController.setStreamController(streamController); - // fpsController uses streamController to switch when frames are being dropped - fpsController.setStreamController(streamController); - var networkControllers = [playListLoader, levelController, streamController]; - if (contentSteering) { - networkControllers.splice(1, 0, contentSteering); - } - this.networkControllers = networkControllers; - var coreComponents = [abrController, bufferController, capLevelController, fpsController, id3TrackController, fragmentTracker]; - this.audioTrackController = this.createController(config.audioTrackController, networkControllers); - var AudioStreamControllerClass = config.audioStreamController; - if (AudioStreamControllerClass) { - networkControllers.push(new AudioStreamControllerClass(this, fragmentTracker, keyLoader)); - } - // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important - this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers); - var SubtitleStreamControllerClass = config.subtitleStreamController; - if (SubtitleStreamControllerClass) { - networkControllers.push(new SubtitleStreamControllerClass(this, fragmentTracker, keyLoader)); - } - this.createController(config.timelineController, coreComponents); - keyLoader.emeController = this.emeController = this.createController(config.emeController, coreComponents); - this.cmcdController = this.createController(config.cmcdController, coreComponents); - this.latencyController = this.createController(LatencyController, coreComponents); - this.coreComponents = coreComponents; - - // Error controller handles errors before and after all other controllers - // This listener will be invoked after all other controllers error listeners - networkControllers.push(errorController); - var onErrorOut = errorController.onErrorOut; - if (typeof onErrorOut === 'function') { - this.on(Events.ERROR, onErrorOut, errorController); - } - } - var _proto = Hls.prototype; - _proto.createController = function createController(ControllerClass, components) { - if (ControllerClass) { - var controllerInstance = new ControllerClass(this); - if (components) { - components.push(controllerInstance); - } - return controllerInstance; - } - return null; - } - - // Delegate the EventEmitter through the public API of Hls.js - ; - _proto.on = function on(event, listener, context) { - if (context === void 0) { - context = this; - } - this._emitter.on(event, listener, context); - }; - _proto.once = function once(event, listener, context) { - if (context === void 0) { - context = this; - } - this._emitter.once(event, listener, context); - }; - _proto.removeAllListeners = function removeAllListeners(event) { - this._emitter.removeAllListeners(event); - }; - _proto.off = function off(event, listener, context, once) { - if (context === void 0) { - context = this; - } - this._emitter.off(event, listener, context, once); - }; - _proto.listeners = function listeners(event) { - return this._emitter.listeners(event); - }; - _proto.emit = function emit(event, name, eventObject) { - return this._emitter.emit(event, name, eventObject); - }; - _proto.trigger = function trigger(event, eventObject) { - if (this.config.debug) { - return this.emit(event, event, eventObject); - } else { - try { - return this.emit(event, event, eventObject); - } catch (error) { - logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error); - // Prevent recursion in error event handlers that throw #5497 - if (!this.triggeringException) { - this.triggeringException = true; - var fatal = event === Events.ERROR; - this.trigger(Events.ERROR, { - type: ErrorTypes.OTHER_ERROR, - details: ErrorDetails.INTERNAL_EXCEPTION, - fatal: fatal, - event: event, - error: error - }); - this.triggeringException = false; - } - } - } - return false; - }; - _proto.listenerCount = function listenerCount(event) { - return this._emitter.listenerCount(event); - } - - /** - * Dispose of the instance - */; - _proto.destroy = function destroy() { - logger.log('destroy'); - this.trigger(Events.DESTROYING, undefined); - this.detachMedia(); - this.removeAllListeners(); - this._autoLevelCapping = -1; - this.url = null; - this.networkControllers.forEach(function (component) { - return component.destroy(); - }); - this.networkControllers.length = 0; - this.coreComponents.forEach(function (component) { - return component.destroy(); - }); - this.coreComponents.length = 0; - // Remove any references that could be held in config options or callbacks - var config = this.config; - config.xhrSetup = config.fetchSetup = undefined; - // @ts-ignore - this.userConfig = null; - } - - /** - * Attaches Hls.js to a media element - */; - _proto.attachMedia = function attachMedia(media) { - logger.log('attachMedia'); - this._media = media; - this.trigger(Events.MEDIA_ATTACHING, { - media: media - }); - } - - /** - * Detach Hls.js from the media - */; - _proto.detachMedia = function detachMedia() { - logger.log('detachMedia'); - this.trigger(Events.MEDIA_DETACHING, undefined); - this._media = null; - } - - /** - * Set the source URL. Can be relative or absolute. - */; - _proto.loadSource = function loadSource(url) { - this.stopLoad(); - var media = this.media; - var loadedSource = this.url; - var loadingSource = this.url = urlToolkitExports.buildAbsoluteURL(self.location.href, url, { - alwaysNormalize: true - }); - this._autoLevelCapping = -1; - this._maxHdcpLevel = null; - logger.log("loadSource:" + loadingSource); - if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) { - this.detachMedia(); - this.attachMedia(media); - } - // when attaching to a source URL, trigger a playlist load - this.trigger(Events.MANIFEST_LOADING, { - url: url - }); - } - - /** - * Start loading data from the stream source. - * Depending on default config, client starts loading automatically when a source is set. - * - * @param startPosition - Set the start position to stream from. - * Defaults to -1 (None: starts from earliest point) - */; - _proto.startLoad = function startLoad(startPosition) { - if (startPosition === void 0) { - startPosition = -1; - } - logger.log("startLoad(" + startPosition + ")"); - this.started = true; - this.networkControllers.forEach(function (controller) { - controller.startLoad(startPosition); - }); - } - - /** - * Stop loading of any stream data. - */; - _proto.stopLoad = function stopLoad() { - logger.log('stopLoad'); - this.started = false; - this.networkControllers.forEach(function (controller) { - controller.stopLoad(); - }); - } - - /** - * Resumes stream controller segment loading if previously started. - */; - _proto.resumeBuffering = function resumeBuffering() { - if (this.started) { - this.networkControllers.forEach(function (controller) { - if ('fragmentLoader' in controller) { - controller.startLoad(-1); - } - }); - } - } - - /** - * Stops stream controller segment loading without changing 'started' state like stopLoad(). - * This allows for media buffering to be paused without interupting playlist loading. - */; - _proto.pauseBuffering = function pauseBuffering() { - this.networkControllers.forEach(function (controller) { - if ('fragmentLoader' in controller) { - controller.stopLoad(); - } - }); - } - - /** - * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1) - */; - _proto.swapAudioCodec = function swapAudioCodec() { - logger.log('swapAudioCodec'); - this.streamController.swapAudioCodec(); - } - - /** - * When the media-element fails, this allows to detach and then re-attach it - * as one call (convenience method). - * - * Automatic recovery of media-errors by this process is configurable. - */; - _proto.recoverMediaError = function recoverMediaError() { - logger.log('recoverMediaError'); - var media = this._media; - this.detachMedia(); - if (media) { - this.attachMedia(media); - } - }; - _proto.removeLevel = function removeLevel(levelIndex) { - this.levelController.removeLevel(levelIndex); - } - - /** - * @returns an array of levels (variants) sorted by HDCP-LEVEL, RESOLUTION (height), FRAME-RATE, CODECS, VIDEO-RANGE, and BANDWIDTH - */; - /** - * Find and select the best matching audio track, making a level switch when a Group change is necessary. - * Updates `hls.config.audioPreference`. Returns the selected track, or null when no matching track is found. - */ - _proto.setAudioOption = function setAudioOption(audioOption) { - var _this$audioTrackContr; - return (_this$audioTrackContr = this.audioTrackController) == null ? void 0 : _this$audioTrackContr.setAudioOption(audioOption); - } - /** - * Find and select the best matching subtitle track, making a level switch when a Group change is necessary. - * Updates `hls.config.subtitlePreference`. Returns the selected track, or null when no matching track is found. - */; - _proto.setSubtitleOption = function setSubtitleOption(subtitleOption) { - var _this$subtitleTrackCo; - (_this$subtitleTrackCo = this.subtitleTrackController) == null ? void 0 : _this$subtitleTrackCo.setSubtitleOption(subtitleOption); - return null; - } - - /** - * Get the complete list of audio tracks across all media groups - */; - _createClass(Hls, [{ - key: "levels", - get: function get() { - var levels = this.levelController.levels; - return levels ? levels : []; - } - - /** - * Index of quality level (variant) currently played - */ - }, { - key: "currentLevel", - get: function get() { - return this.streamController.currentLevel; - } - - /** - * Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection. - */, - set: function set(newLevel) { - logger.log("set currentLevel:" + newLevel); - this.levelController.manualLevel = newLevel; - this.streamController.immediateLevelSwitch(); - } - - /** - * Index of next quality level loaded as scheduled by stream controller. - */ - }, { - key: "nextLevel", - get: function get() { - return this.streamController.nextLevel; - } - - /** - * Set quality level index for next loaded data. - * This will switch the video quality asap, without interrupting playback. - * May abort current loading of data, and flush parts of buffer (outside currently played fragment region). - * @param newLevel - Pass -1 for automatic level selection - */, - set: function set(newLevel) { - logger.log("set nextLevel:" + newLevel); - this.levelController.manualLevel = newLevel; - this.streamController.nextLevelSwitch(); - } - - /** - * Return the quality level of the currently or last (of none is loaded currently) segment - */ - }, { - key: "loadLevel", - get: function get() { - return this.levelController.level; - } - - /** - * Set quality level index for next loaded data in a conservative way. - * This will switch the quality without flushing, but interrupt current loading. - * Thus the moment when the quality switch will appear in effect will only be after the already existing buffer. - * @param newLevel - Pass -1 for automatic level selection - */, - set: function set(newLevel) { - logger.log("set loadLevel:" + newLevel); - this.levelController.manualLevel = newLevel; - } - - /** - * get next quality level loaded - */ - }, { - key: "nextLoadLevel", - get: function get() { - return this.levelController.nextLoadLevel; - } - - /** - * Set quality level of next loaded segment in a fully "non-destructive" way. - * Same as `loadLevel` but will wait for next switch (until current loading is done). - */, - set: function set(level) { - this.levelController.nextLoadLevel = level; - } - - /** - * Return "first level": like a default level, if not set, - * falls back to index of first level referenced in manifest - */ - }, { - key: "firstLevel", - get: function get() { - return Math.max(this.levelController.firstLevel, this.minAutoLevel); - } - - /** - * Sets "first-level", see getter. - */, - set: function set(newLevel) { - logger.log("set firstLevel:" + newLevel); - this.levelController.firstLevel = newLevel; - } - - /** - * Return the desired start level for the first fragment that will be loaded. - * The default value of -1 indicates automatic start level selection. - * Setting hls.nextAutoLevel without setting a startLevel will result in - * the nextAutoLevel value being used for one fragment load. - */ - }, { - key: "startLevel", - get: function get() { - var startLevel = this.levelController.startLevel; - if (startLevel === -1 && this.abrController.forcedAutoLevel > -1) { - return this.abrController.forcedAutoLevel; - } - return startLevel; - } - - /** - * set start level (level of first fragment that will be played back) - * if not overrided by user, first level appearing in manifest will be used as start level - * if -1 : automatic start level selection, playback will start from level matching download bandwidth - * (determined from download of first segment) - */, - set: function set(newLevel) { - logger.log("set startLevel:" + newLevel); - // if not in automatic start level detection, ensure startLevel is greater than minAutoLevel - if (newLevel !== -1) { - newLevel = Math.max(newLevel, this.minAutoLevel); - } - this.levelController.startLevel = newLevel; - } - - /** - * Whether level capping is enabled. - * Default value is set via `config.capLevelToPlayerSize`. - */ - }, { - key: "capLevelToPlayerSize", - get: function get() { - return this.config.capLevelToPlayerSize; - } - - /** - * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called. - */, - set: function set(shouldStartCapping) { - var newCapLevelToPlayerSize = !!shouldStartCapping; - if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) { - if (newCapLevelToPlayerSize) { - this.capLevelController.startCapping(); // If capping occurs, nextLevelSwitch will happen based on size. - } else { - this.capLevelController.stopCapping(); - this.autoLevelCapping = -1; - this.streamController.nextLevelSwitch(); // Now we're uncapped, get the next level asap. - } - this.config.capLevelToPlayerSize = newCapLevelToPlayerSize; - } - } - - /** - * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) - */ - }, { - key: "autoLevelCapping", - get: function get() { - return this._autoLevelCapping; - } - - /** - * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned. - */, - set: - /** - * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) - */ - function set(newLevel) { - if (this._autoLevelCapping !== newLevel) { - logger.log("set autoLevelCapping:" + newLevel); - this._autoLevelCapping = newLevel; - this.levelController.checkMaxAutoUpdated(); - } - } - }, { - key: "bandwidthEstimate", - get: function get() { - var bwEstimator = this.abrController.bwEstimator; - if (!bwEstimator) { - return NaN; - } - return bwEstimator.getEstimate(); - }, - set: function set(abrEwmaDefaultEstimate) { - this.abrController.resetEstimator(abrEwmaDefaultEstimate); - } - - /** - * get time to first byte estimate - * @type {number} - */ - }, { - key: "ttfbEstimate", - get: function get() { - var bwEstimator = this.abrController.bwEstimator; - if (!bwEstimator) { - return NaN; - } - return bwEstimator.getEstimateTTFB(); - } - }, { - key: "maxHdcpLevel", - get: function get() { - return this._maxHdcpLevel; - }, - set: function set(value) { - if (isHdcpLevel(value) && this._maxHdcpLevel !== value) { - this._maxHdcpLevel = value; - this.levelController.checkMaxAutoUpdated(); - } - } - - /** - * True when automatic level selection enabled - */ - }, { - key: "autoLevelEnabled", - get: function get() { - return this.levelController.manualLevel === -1; - } - - /** - * Level set manually (if any) - */ - }, { - key: "manualLevel", - get: function get() { - return this.levelController.manualLevel; - } - - /** - * min level selectable in auto mode according to config.minAutoBitrate - */ - }, { - key: "minAutoLevel", - get: function get() { - var levels = this.levels, - minAutoBitrate = this.config.minAutoBitrate; - if (!levels) return 0; - var len = levels.length; - for (var i = 0; i < len; i++) { - if (levels[i].maxBitrate >= minAutoBitrate) { - return i; - } - } - return 0; - } - - /** - * max level selectable in auto mode according to autoLevelCapping - */ - }, { - key: "maxAutoLevel", - get: function get() { - var levels = this.levels, - autoLevelCapping = this.autoLevelCapping, - maxHdcpLevel = this.maxHdcpLevel; - var maxAutoLevel; - if (autoLevelCapping === -1 && levels != null && levels.length) { - maxAutoLevel = levels.length - 1; - } else { - maxAutoLevel = autoLevelCapping; - } - if (maxHdcpLevel) { - for (var i = maxAutoLevel; i--;) { - var hdcpLevel = levels[i].attrs['HDCP-LEVEL']; - if (hdcpLevel && hdcpLevel <= maxHdcpLevel) { - return i; - } - } - } - return maxAutoLevel; - } - }, { - key: "firstAutoLevel", - get: function get() { - return this.abrController.firstAutoLevel; - } - - /** - * next automatically selected quality level - */ - }, { - key: "nextAutoLevel", - get: function get() { - return this.abrController.nextAutoLevel; - } - - /** - * this setter is used to force next auto level. - * this is useful to force a switch down in auto mode: - * in case of load error on level N, hls.js can set nextAutoLevel to N-1 for example) - * forced value is valid for one fragment. upon successful frag loading at forced level, - * this value will be resetted to -1 by ABR controller. - */, - set: function set(nextLevel) { - this.abrController.nextAutoLevel = nextLevel; - } - - /** - * get the datetime value relative to media.currentTime for the active level Program Date Time if present - */ - }, { - key: "playingDate", - get: function get() { - return this.streamController.currentProgramDateTime; - } - }, { - key: "mainForwardBufferInfo", - get: function get() { - return this.streamController.getMainFwdBufferInfo(); - } - }, { - key: "allAudioTracks", - get: function get() { - var audioTrackController = this.audioTrackController; - return audioTrackController ? audioTrackController.allAudioTracks : []; - } - - /** - * Get the list of selectable audio tracks - */ - }, { - key: "audioTracks", - get: function get() { - var audioTrackController = this.audioTrackController; - return audioTrackController ? audioTrackController.audioTracks : []; - } - - /** - * index of the selected audio track (index in audio track lists) - */ - }, { - key: "audioTrack", - get: function get() { - var audioTrackController = this.audioTrackController; - return audioTrackController ? audioTrackController.audioTrack : -1; - } - - /** - * selects an audio track, based on its index in audio track lists - */, - set: function set(audioTrackId) { - var audioTrackController = this.audioTrackController; - if (audioTrackController) { - audioTrackController.audioTrack = audioTrackId; - } - } - - /** - * get the complete list of subtitle tracks across all media groups - */ - }, { - key: "allSubtitleTracks", - get: function get() { - var subtitleTrackController = this.subtitleTrackController; - return subtitleTrackController ? subtitleTrackController.allSubtitleTracks : []; - } - - /** - * get alternate subtitle tracks list from playlist - */ - }, { - key: "subtitleTracks", - get: function get() { - var subtitleTrackController = this.subtitleTrackController; - return subtitleTrackController ? subtitleTrackController.subtitleTracks : []; - } - - /** - * index of the selected subtitle track (index in subtitle track lists) - */ - }, { - key: "subtitleTrack", - get: function get() { - var subtitleTrackController = this.subtitleTrackController; - return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1; - }, - set: - /** - * select an subtitle track, based on its index in subtitle track lists - */ - function set(subtitleTrackId) { - var subtitleTrackController = this.subtitleTrackController; - if (subtitleTrackController) { - subtitleTrackController.subtitleTrack = subtitleTrackId; - } - } - - /** - * Whether subtitle display is enabled or not - */ - }, { - key: "media", - get: function get() { - return this._media; - } - }, { - key: "subtitleDisplay", - get: function get() { - var subtitleTrackController = this.subtitleTrackController; - return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false; - } - - /** - * Enable/disable subtitle display rendering - */, - set: function set(value) { - var subtitleTrackController = this.subtitleTrackController; - if (subtitleTrackController) { - subtitleTrackController.subtitleDisplay = value; - } - } - - /** - * get mode for Low-Latency HLS loading - */ - }, { - key: "lowLatencyMode", - get: function get() { - return this.config.lowLatencyMode; - } - - /** - * Enable/disable Low-Latency HLS part playlist and segment loading, and start live streams at playlist PART-HOLD-BACK rather than HOLD-BACK. - */, - set: function set(mode) { - this.config.lowLatencyMode = mode; - } - - /** - * Position (in seconds) of live sync point (ie edge of live position minus safety delay defined by ```hls.config.liveSyncDuration```) - * @returns null prior to loading live Playlist - */ - }, { - key: "liveSyncPosition", - get: function get() { - return this.latencyController.liveSyncPosition; - } - - /** - * Estimated position (in seconds) of live edge (ie edge of live playlist plus time sync playlist advanced) - * @returns 0 before first playlist is loaded - */ - }, { - key: "latency", - get: function get() { - return this.latencyController.latency; - } - - /** - * maximum distance from the edge before the player seeks forward to ```hls.liveSyncPosition``` - * configured using ```liveMaxLatencyDurationCount``` (multiple of target duration) or ```liveMaxLatencyDuration``` - * @returns 0 before first playlist is loaded - */ - }, { - key: "maxLatency", - get: function get() { - return this.latencyController.maxLatency; - } - - /** - * target distance from the edge as calculated by the latency controller - */ - }, { - key: "targetLatency", - get: function get() { - return this.latencyController.targetLatency; - } - - /** - * the rate at which the edge of the current live playlist is advancing or 1 if there is none - */ - }, { - key: "drift", - get: function get() { - return this.latencyController.drift; - } - - /** - * set to true when startLoad is called before MANIFEST_PARSED event - */ - }, { - key: "forceStartLoad", - get: function get() { - return this.streamController.forceStartLoad; - } - }], [{ - key: "version", - get: - /** - * Get the video-dev/hls.js package version. - */ - function get() { - return "1.5.14"; - } - }, { - key: "Events", - get: function get() { - return Events; - } - }, { - key: "ErrorTypes", - get: function get() { - return ErrorTypes; - } - }, { - key: "ErrorDetails", - get: function get() { - return ErrorDetails; - } - - /** - * Get the default configuration applied to new instances. - */ - }, { - key: "DefaultConfig", - get: function get() { - if (!Hls.defaultConfig) { - return hlsDefaultConfig; - } - return Hls.defaultConfig; - } - - /** - * Replace the default configuration applied to new instances. - */, - set: function set(defaultConfig) { - Hls.defaultConfig = defaultConfig; - } - }]); - return Hls; - }(); - Hls.defaultConfig = void 0; - - return Hls; - -})); -})(false); -//# sourceMappingURL=hls.js.map +!function t(e){var r,i;r=this,i=function(){"use strict";function r(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);e&&(i=i.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,i)}return r}function i(t){for(var e=1;e<arguments.length;e++){var i=null!=arguments[e]?arguments[e]:{};e%2?r(Object(i),!0).forEach((function(e){var r,a,s;r=t,a=e,s=i[e],(a=n(a))in r?Object.defineProperty(r,a,{value:s,enumerable:!0,configurable:!0,writable:!0}):r[a]=s})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(i)):r(Object(i)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(i,e))}))}return t}function n(t){var e=function(t,e){if("object"!=typeof t||!t)return t;var r=t[Symbol.toPrimitive];if(void 0!==r){var i=r.call(t,e||"default");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===e?String:Number)(t)}(t,"string");return"symbol"==typeof e?e:String(e)}function a(t,e){for(var r=0;r<e.length;r++){var i=e[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,n(i.key),i)}}function s(t,e,r){return e&&a(t.prototype,e),r&&a(t,r),Object.defineProperty(t,"prototype",{writable:!1}),t}function o(){return o=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var r=arguments[e];for(var i in r)Object.prototype.hasOwnProperty.call(r,i)&&(t[i]=r[i])}return t},o.apply(this,arguments)}function l(t,e){t.prototype=Object.create(e.prototype),t.prototype.constructor=t,h(t,e)}function u(t){return u=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)},u(t)}function h(t,e){return h=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},h(t,e)}function d(t,e,r){return d=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(t){return!1}}()?Reflect.construct.bind():function(t,e,r){var i=[null];i.push.apply(i,e);var n=new(Function.bind.apply(t,i));return r&&h(n,r.prototype),n},d.apply(null,arguments)}function c(t){var e="function"==typeof Map?new Map:void 0;return c=function(t){if(null===t||!function(t){try{return-1!==Function.toString.call(t).indexOf("[native code]")}catch(e){return"function"==typeof t}}(t))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==e){if(e.has(t))return e.get(t);e.set(t,r)}function r(){return d(t,arguments,u(this).constructor)}return r.prototype=Object.create(t.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),h(r,t)},c(t)}function f(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,i=new Array(e);r<e;r++)i[r]=t[r];return i}function g(t,e){var r="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(r)return(r=r.call(t)).next.bind(r);if(Array.isArray(t)||(r=function(t,e){if(t){if("string"==typeof t)return f(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?f(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){r&&(t=r);var i=0;return function(){return i>=t.length?{done:!0}:{done:!1,value:t[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function v(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var m={exports:{}};!function(t,e){var r,i,n,a,s;r=/^(?=((?:[a-zA-Z0-9+\-.]+:)?))\1(?=((?:\/\/[^\/?#]*)?))\2(?=((?:(?:[^?#\/]*\/)*[^;?#\/]*)?))\3((?:;[^?#]*)?)(\?[^#]*)?(#[^]*)?$/,i=/^(?=([^\/?#]*))\1([^]*)$/,n=/(?:\/|^)\.(?=\/)/g,a=/(?:\/|^)\.\.\/(?!\.\.\/)[^\/]*(?=\/)/g,s={buildAbsoluteURL:function(t,e,r){if(r=r||{},t=t.trim(),!(e=e.trim())){if(!r.alwaysNormalize)return t;var n=s.parseURL(t);if(!n)throw new Error("Error trying to parse base URL.");return n.path=s.normalizePath(n.path),s.buildURLFromParts(n)}var a=s.parseURL(e);if(!a)throw new Error("Error trying to parse relative URL.");if(a.scheme)return r.alwaysNormalize?(a.path=s.normalizePath(a.path),s.buildURLFromParts(a)):e;var o=s.parseURL(t);if(!o)throw new Error("Error trying to parse base URL.");if(!o.netLoc&&o.path&&"/"!==o.path[0]){var l=i.exec(o.path);o.netLoc=l[1],o.path=l[2]}o.netLoc&&!o.path&&(o.path="/");var u={scheme:o.scheme,netLoc:a.netLoc,path:null,params:a.params,query:a.query,fragment:a.fragment};if(!a.netLoc&&(u.netLoc=o.netLoc,"/"!==a.path[0]))if(a.path){var h=o.path,d=h.substring(0,h.lastIndexOf("/")+1)+a.path;u.path=s.normalizePath(d)}else u.path=o.path,a.params||(u.params=o.params,a.query||(u.query=o.query));return null===u.path&&(u.path=r.alwaysNormalize?s.normalizePath(a.path):a.path),s.buildURLFromParts(u)},parseURL:function(t){var e=r.exec(t);return e?{scheme:e[1]||"",netLoc:e[2]||"",path:e[3]||"",params:e[4]||"",query:e[5]||"",fragment:e[6]||""}:null},normalizePath:function(t){for(t=t.split("").reverse().join("").replace(n,"");t.length!==(t=t.replace(a,"")).length;);return t.split("").reverse().join("")},buildURLFromParts:function(t){return t.scheme+t.netLoc+t.path+t.params+t.query+t.fragment}},t.exports=s}(m);var p=m.exports,y=Number.isFinite||function(t){return"number"==typeof t&&isFinite(t)},E=Number.isSafeInteger||function(t){return"number"==typeof t&&Math.abs(t)<=T},T=Number.MAX_SAFE_INTEGER||9007199254740991,S=function(t){return t.MEDIA_ATTACHING="hlsMediaAttaching",t.MEDIA_ATTACHED="hlsMediaAttached",t.MEDIA_DETACHING="hlsMediaDetaching",t.MEDIA_DETACHED="hlsMediaDetached",t.BUFFER_RESET="hlsBufferReset",t.BUFFER_CODECS="hlsBufferCodecs",t.BUFFER_CREATED="hlsBufferCreated",t.BUFFER_APPENDING="hlsBufferAppending",t.BUFFER_APPENDED="hlsBufferAppended",t.BUFFER_EOS="hlsBufferEos",t.BUFFER_FLUSHING="hlsBufferFlushing",t.BUFFER_FLUSHED="hlsBufferFlushed",t.MANIFEST_LOADING="hlsManifestLoading",t.MANIFEST_LOADED="hlsManifestLoaded",t.MANIFEST_PARSED="hlsManifestParsed",t.LEVEL_SWITCHING="hlsLevelSwitching",t.LEVEL_SWITCHED="hlsLevelSwitched",t.LEVEL_LOADING="hlsLevelLoading",t.LEVEL_LOADED="hlsLevelLoaded",t.LEVEL_UPDATED="hlsLevelUpdated",t.LEVEL_PTS_UPDATED="hlsLevelPtsUpdated",t.LEVELS_UPDATED="hlsLevelsUpdated",t.AUDIO_TRACKS_UPDATED="hlsAudioTracksUpdated",t.AUDIO_TRACK_SWITCHING="hlsAudioTrackSwitching",t.AUDIO_TRACK_SWITCHED="hlsAudioTrackSwitched",t.AUDIO_TRACK_LOADING="hlsAudioTrackLoading",t.AUDIO_TRACK_LOADED="hlsAudioTrackLoaded",t.SUBTITLE_TRACKS_UPDATED="hlsSubtitleTracksUpdated",t.SUBTITLE_TRACKS_CLEARED="hlsSubtitleTracksCleared",t.SUBTITLE_TRACK_SWITCH="hlsSubtitleTrackSwitch",t.SUBTITLE_TRACK_LOADING="hlsSubtitleTrackLoading",t.SUBTITLE_TRACK_LOADED="hlsSubtitleTrackLoaded",t.SUBTITLE_FRAG_PROCESSED="hlsSubtitleFragProcessed",t.CUES_PARSED="hlsCuesParsed",t.NON_NATIVE_TEXT_TRACKS_FOUND="hlsNonNativeTextTracksFound",t.INIT_PTS_FOUND="hlsInitPtsFound",t.FRAG_LOADING="hlsFragLoading",t.FRAG_LOAD_EMERGENCY_ABORTED="hlsFragLoadEmergencyAborted",t.FRAG_LOADED="hlsFragLoaded",t.FRAG_DECRYPTED="hlsFragDecrypted",t.FRAG_PARSING_INIT_SEGMENT="hlsFragParsingInitSegment",t.FRAG_PARSING_USERDATA="hlsFragParsingUserdata",t.FRAG_PARSING_METADATA="hlsFragParsingMetadata",t.FRAG_PARSED="hlsFragParsed",t.FRAG_BUFFERED="hlsFragBuffered",t.FRAG_CHANGED="hlsFragChanged",t.FPS_DROP="hlsFpsDrop",t.FPS_DROP_LEVEL_CAPPING="hlsFpsDropLevelCapping",t.MAX_AUTO_LEVEL_UPDATED="hlsMaxAutoLevelUpdated",t.ERROR="hlsError",t.DESTROYING="hlsDestroying",t.KEY_LOADING="hlsKeyLoading",t.KEY_LOADED="hlsKeyLoaded",t.LIVE_BACK_BUFFER_REACHED="hlsLiveBackBufferReached",t.BACK_BUFFER_REACHED="hlsBackBufferReached",t.STEERING_MANIFEST_LOADED="hlsSteeringManifestLoaded",t}({}),L=function(t){return t.NETWORK_ERROR="networkError",t.MEDIA_ERROR="mediaError",t.KEY_SYSTEM_ERROR="keySystemError",t.MUX_ERROR="muxError",t.OTHER_ERROR="otherError",t}({}),A=function(t){return t.KEY_SYSTEM_NO_KEYS="keySystemNoKeys",t.KEY_SYSTEM_NO_ACCESS="keySystemNoAccess",t.KEY_SYSTEM_NO_SESSION="keySystemNoSession",t.KEY_SYSTEM_NO_CONFIGURED_LICENSE="keySystemNoConfiguredLicense",t.KEY_SYSTEM_LICENSE_REQUEST_FAILED="keySystemLicenseRequestFailed",t.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED="keySystemServerCertificateRequestFailed",t.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED="keySystemServerCertificateUpdateFailed",t.KEY_SYSTEM_SESSION_UPDATE_FAILED="keySystemSessionUpdateFailed",t.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED="keySystemStatusOutputRestricted",t.KEY_SYSTEM_STATUS_INTERNAL_ERROR="keySystemStatusInternalError",t.MANIFEST_LOAD_ERROR="manifestLoadError",t.MANIFEST_LOAD_TIMEOUT="manifestLoadTimeOut",t.MANIFEST_PARSING_ERROR="manifestParsingError",t.MANIFEST_INCOMPATIBLE_CODECS_ERROR="manifestIncompatibleCodecsError",t.LEVEL_EMPTY_ERROR="levelEmptyError",t.LEVEL_LOAD_ERROR="levelLoadError",t.LEVEL_LOAD_TIMEOUT="levelLoadTimeOut",t.LEVEL_PARSING_ERROR="levelParsingError",t.LEVEL_SWITCH_ERROR="levelSwitchError",t.AUDIO_TRACK_LOAD_ERROR="audioTrackLoadError",t.AUDIO_TRACK_LOAD_TIMEOUT="audioTrackLoadTimeOut",t.SUBTITLE_LOAD_ERROR="subtitleTrackLoadError",t.SUBTITLE_TRACK_LOAD_TIMEOUT="subtitleTrackLoadTimeOut",t.FRAG_LOAD_ERROR="fragLoadError",t.FRAG_LOAD_TIMEOUT="fragLoadTimeOut",t.FRAG_DECRYPT_ERROR="fragDecryptError",t.FRAG_PARSING_ERROR="fragParsingError",t.FRAG_GAP="fragGap",t.REMUX_ALLOC_ERROR="remuxAllocError",t.KEY_LOAD_ERROR="keyLoadError",t.KEY_LOAD_TIMEOUT="keyLoadTimeOut",t.BUFFER_ADD_CODEC_ERROR="bufferAddCodecError",t.BUFFER_INCOMPATIBLE_CODECS_ERROR="bufferIncompatibleCodecsError",t.BUFFER_APPEND_ERROR="bufferAppendError",t.BUFFER_APPENDING_ERROR="bufferAppendingError",t.BUFFER_STALLED_ERROR="bufferStalledError",t.BUFFER_FULL_ERROR="bufferFullError",t.BUFFER_SEEK_OVER_HOLE="bufferSeekOverHole",t.BUFFER_NUDGE_ON_STALL="bufferNudgeOnStall",t.INTERNAL_EXCEPTION="internalException",t.INTERNAL_ABORTED="aborted",t.UNKNOWN="unknown",t}({}),R=function(){},b={trace:R,debug:R,log:R,warn:R,info:R,error:R},k=b;function D(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),i=1;i<e;i++)r[i-1]=arguments[i];r.forEach((function(e){k[e]=t[e]?t[e].bind(t):function(t){var e=self.console[t];return e?e.bind(self.console,"["+t+"] >"):R}(e)}))}function I(t,e){if("object"==typeof console&&!0===t||"object"==typeof t){D(t,"debug","log","info","warn","error");try{k.log('Debug logs enabled for "'+e+'" in hls.js version 1.5.15')}catch(t){k=b}}else k=b}var w=k,C=/^(\d+)x(\d+)$/,_=/(.+?)=(".*?"|.*?)(?:,|$)/g,x=function(){function t(e){"string"==typeof e&&(e=t.parseAttrList(e)),o(this,e)}var e=t.prototype;return e.decimalInteger=function(t){var e=parseInt(this[t],10);return e>Number.MAX_SAFE_INTEGER?1/0:e},e.hexadecimalInteger=function(t){if(this[t]){var e=(this[t]||"0x").slice(2);e=(1&e.length?"0":"")+e;for(var r=new Uint8Array(e.length/2),i=0;i<e.length/2;i++)r[i]=parseInt(e.slice(2*i,2*i+2),16);return r}return null},e.hexadecimalIntegerAsNumber=function(t){var e=parseInt(this[t],16);return e>Number.MAX_SAFE_INTEGER?1/0:e},e.decimalFloatingPoint=function(t){return parseFloat(this[t])},e.optionalFloat=function(t,e){var r=this[t];return r?parseFloat(r):e},e.enumeratedString=function(t){return this[t]},e.bool=function(t){return"YES"===this[t]},e.decimalResolution=function(t){var e=C.exec(this[t]);if(null!==e)return{width:parseInt(e[1],10),height:parseInt(e[2],10)}},t.parseAttrList=function(t){var e,r={};for(_.lastIndex=0;null!==(e=_.exec(t));){var i=e[2];0===i.indexOf('"')&&i.lastIndexOf('"')===i.length-1&&(i=i.slice(1,-1)),r[e[1].trim()]=i}return r},s(t,[{key:"clientAttrs",get:function(){return Object.keys(this).filter((function(t){return"X-"===t.substring(0,2)}))}}]),t}();function P(t){return"SCTE35-OUT"===t||"SCTE35-IN"===t}var F=function(){function t(t,e){if(this.attr=void 0,this._startDate=void 0,this._endDate=void 0,this._badValueForSameId=void 0,e){var r=e.attr;for(var i in r)if(Object.prototype.hasOwnProperty.call(t,i)&&t[i]!==r[i]){w.warn('DATERANGE tag attribute: "'+i+'" does not match for tags with ID: "'+t.ID+'"'),this._badValueForSameId=i;break}t=o(new x({}),r,t)}if(this.attr=t,this._startDate=new Date(t["START-DATE"]),"END-DATE"in this.attr){var n=new Date(this.attr["END-DATE"]);y(n.getTime())&&(this._endDate=n)}}return s(t,[{key:"id",get:function(){return this.attr.ID}},{key:"class",get:function(){return this.attr.CLASS}},{key:"startDate",get:function(){return this._startDate}},{key:"endDate",get:function(){if(this._endDate)return this._endDate;var t=this.duration;return null!==t?new Date(this._startDate.getTime()+1e3*t):null}},{key:"duration",get:function(){if("DURATION"in this.attr){var t=this.attr.decimalFloatingPoint("DURATION");if(y(t))return t}else if(this._endDate)return(this._endDate.getTime()-this._startDate.getTime())/1e3;return null}},{key:"plannedDuration",get:function(){return"PLANNED-DURATION"in this.attr?this.attr.decimalFloatingPoint("PLANNED-DURATION"):null}},{key:"endOnNext",get:function(){return this.attr.bool("END-ON-NEXT")}},{key:"isValid",get:function(){return!!this.id&&!this._badValueForSameId&&y(this.startDate.getTime())&&(null===this.duration||this.duration>=0)&&(!this.endOnNext||!!this.class)}}]),t}(),M=function(){this.aborted=!1,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}},O="audio",N="video",U="audiovideo",B=function(){function t(t){var e;this._byteRange=null,this._url=null,this.baseurl=void 0,this.relurl=void 0,this.elementaryStreams=((e={})[O]=null,e[N]=null,e[U]=null,e),this.baseurl=t}return t.prototype.setByteRange=function(t,e){var r,i=t.split("@",2);r=1===i.length?(null==e?void 0:e.byteRangeEndOffset)||0:parseInt(i[1]),this._byteRange=[r,parseInt(i[0])+r]},s(t,[{key:"byteRange",get:function(){return this._byteRange?this._byteRange:[]}},{key:"byteRangeStartOffset",get:function(){return this.byteRange[0]}},{key:"byteRangeEndOffset",get:function(){return this.byteRange[1]}},{key:"url",get:function(){return!this._url&&this.baseurl&&this.relurl&&(this._url=p.buildAbsoluteURL(this.baseurl,this.relurl,{alwaysNormalize:!0})),this._url||""},set:function(t){this._url=t}}]),t}(),G=function(t){function e(e,r){var i;return(i=t.call(this,r)||this)._decryptdata=null,i.rawProgramDateTime=null,i.programDateTime=null,i.tagList=[],i.duration=0,i.sn=0,i.levelkeys=void 0,i.type=void 0,i.loader=null,i.keyLoader=null,i.level=-1,i.cc=0,i.startPTS=void 0,i.endPTS=void 0,i.startDTS=void 0,i.endDTS=void 0,i.start=0,i.deltaPTS=void 0,i.maxStartPTS=void 0,i.minEndPTS=void 0,i.stats=new M,i.data=void 0,i.bitrateTest=!1,i.title=null,i.initSegment=null,i.endList=void 0,i.gap=void 0,i.urlId=0,i.type=e,i}l(e,t);var r=e.prototype;return r.setKeyFormat=function(t){if(this.levelkeys){var e=this.levelkeys[t];e&&!this._decryptdata&&(this._decryptdata=e.getDecryptData(this.sn))}},r.abortRequests=function(){var t,e;null==(t=this.loader)||t.abort(),null==(e=this.keyLoader)||e.abort()},r.setElementaryStreamInfo=function(t,e,r,i,n,a){void 0===a&&(a=!1);var s=this.elementaryStreams,o=s[t];o?(o.startPTS=Math.min(o.startPTS,e),o.endPTS=Math.max(o.endPTS,r),o.startDTS=Math.min(o.startDTS,i),o.endDTS=Math.max(o.endDTS,n)):s[t]={startPTS:e,endPTS:r,startDTS:i,endDTS:n,partial:a}},r.clearElementaryStreamInfo=function(){var t=this.elementaryStreams;t[O]=null,t[N]=null,t[U]=null},s(e,[{key:"decryptdata",get:function(){if(!this.levelkeys&&!this._decryptdata)return null;if(!this._decryptdata&&this.levelkeys&&!this.levelkeys.NONE){var t=this.levelkeys.identity;if(t)this._decryptdata=t.getDecryptData(this.sn);else{var e=Object.keys(this.levelkeys);if(1===e.length)return this._decryptdata=this.levelkeys[e[0]].getDecryptData(this.sn)}}return this._decryptdata}},{key:"end",get:function(){return this.start+this.duration}},{key:"endProgramDateTime",get:function(){if(null===this.programDateTime)return null;if(!y(this.programDateTime))return null;var t=y(this.duration)?this.duration:0;return this.programDateTime+1e3*t}},{key:"encrypted",get:function(){var t;if(null!=(t=this._decryptdata)&&t.encrypted)return!0;if(this.levelkeys){var e=Object.keys(this.levelkeys),r=e.length;if(r>1||1===r&&this.levelkeys[e[0]].encrypted)return!0}return!1}}]),e}(B),K=function(t){function e(e,r,i,n,a){var s;(s=t.call(this,i)||this).fragOffset=0,s.duration=0,s.gap=!1,s.independent=!1,s.relurl=void 0,s.fragment=void 0,s.index=void 0,s.stats=new M,s.duration=e.decimalFloatingPoint("DURATION"),s.gap=e.bool("GAP"),s.independent=e.bool("INDEPENDENT"),s.relurl=e.enumeratedString("URI"),s.fragment=r,s.index=n;var o=e.enumeratedString("BYTERANGE");return o&&s.setByteRange(o,a),a&&(s.fragOffset=a.fragOffset+a.duration),s}return l(e,t),s(e,[{key:"start",get:function(){return this.fragment.start+this.fragOffset}},{key:"end",get:function(){return this.start+this.duration}},{key:"loaded",get:function(){var t=this.elementaryStreams;return!!(t.audio||t.video||t.audiovideo)}}]),e}(B),H=function(){function t(t){this.PTSKnown=!1,this.alignedSliding=!1,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=!0,this.ageHeader=0,this.advancedDateTime=void 0,this.updated=!0,this.advanced=!0,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=!1,this.canSkipUntil=0,this.canSkipDateRanges=!1,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=!1,this.fragments=[],this.encryptedFragments=[],this.dateRanges={},this.url=t}return t.prototype.reloaded=function(t){if(!t)return this.advanced=!0,void(this.updated=!0);var e=this.lastPartSn-t.lastPartSn,r=this.lastPartIndex-t.lastPartIndex;this.updated=this.endSN!==t.endSN||!!r||!!e||!this.live,this.advanced=this.endSN>t.endSN||e>0||0===e&&r>0,this.updated||this.advanced?this.misses=Math.floor(.6*t.misses):this.misses=t.misses+1,this.availabilityDelay=t.availabilityDelay},s(t,[{key:"hasProgramDateTime",get:function(){return!!this.fragments.length&&y(this.fragments[this.fragments.length-1].programDateTime)}},{key:"levelTargetDuration",get:function(){return this.averagetargetduration||this.targetduration||10}},{key:"drift",get:function(){var t=this.driftEndTime-this.driftStartTime;return t>0?1e3*(this.driftEnd-this.driftStart)/t:1}},{key:"edge",get:function(){return this.partEnd||this.fragmentEnd}},{key:"partEnd",get:function(){var t;return null!=(t=this.partList)&&t.length?this.partList[this.partList.length-1].end:this.fragmentEnd}},{key:"fragmentEnd",get:function(){var t;return null!=(t=this.fragments)&&t.length?this.fragments[this.fragments.length-1].end:0}},{key:"age",get:function(){return this.advancedDateTime?Math.max(Date.now()-this.advancedDateTime,0)/1e3:0}},{key:"lastPartIndex",get:function(){var t;return null!=(t=this.partList)&&t.length?this.partList[this.partList.length-1].index:-1}},{key:"lastPartSn",get:function(){var t;return null!=(t=this.partList)&&t.length?this.partList[this.partList.length-1].fragment.sn:this.endSN}}]),t}();function V(t){return Uint8Array.from(atob(t),(function(t){return t.charCodeAt(0)}))}function Y(t){var e,r,i=t.split(":"),n=null;if("data"===i[0]&&2===i.length){var a=i[1].split(";"),s=a[a.length-1].split(",");if(2===s.length){var o="base64"===s[0],l=s[1];o?(a.splice(-1,1),n=V(l)):(e=W(l).subarray(0,16),(r=new Uint8Array(16)).set(e,16-e.length),n=r)}}return n}function W(t){return Uint8Array.from(unescape(encodeURIComponent(t)),(function(t){return t.charCodeAt(0)}))}var j="undefined"!=typeof self?self:void 0,q={CLEARKEY:"org.w3.clearkey",FAIRPLAY:"com.apple.fps",PLAYREADY:"com.microsoft.playready",WIDEVINE:"com.widevine.alpha"},X="org.w3.clearkey",z="com.apple.streamingkeydelivery",Q="com.microsoft.playready",J="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed";function $(t){switch(t){case z:return q.FAIRPLAY;case Q:return q.PLAYREADY;case J:return q.WIDEVINE;case X:return q.CLEARKEY}}var Z="1077efecc0b24d02ace33c1e52e2fb4b",tt="e2719d58a985b3c9781ab030af78d30e",et="9a04f07998404286ab92e65be0885f95",rt="edef8ba979d64acea3c827dcd51d21ed";function it(t){return t===rt?q.WIDEVINE:t===et?q.PLAYREADY:t===Z||t===tt?q.CLEARKEY:void 0}function nt(t){switch(t){case q.FAIRPLAY:return z;case q.PLAYREADY:return Q;case q.WIDEVINE:return J;case q.CLEARKEY:return X}}function at(t){var e=t.drmSystems,r=t.widevineLicenseUrl,i=e?[q.FAIRPLAY,q.WIDEVINE,q.PLAYREADY,q.CLEARKEY].filter((function(t){return!!e[t]})):[];return!i[q.WIDEVINE]&&r&&i.push(q.WIDEVINE),i}var st,ot=null!=j&&null!=(st=j.navigator)&&st.requestMediaKeySystemAccess?self.navigator.requestMediaKeySystemAccess.bind(self.navigator):null;function lt(t,e,r){return Uint8Array.prototype.slice?t.slice(e,r):new Uint8Array(Array.prototype.slice.call(t,e,r))}var ut,ht=function(t,e){return e+10<=t.length&&73===t[e]&&68===t[e+1]&&51===t[e+2]&&t[e+3]<255&&t[e+4]<255&&t[e+6]<128&&t[e+7]<128&&t[e+8]<128&&t[e+9]<128},dt=function(t,e){return e+10<=t.length&&51===t[e]&&68===t[e+1]&&73===t[e+2]&&t[e+3]<255&&t[e+4]<255&&t[e+6]<128&&t[e+7]<128&&t[e+8]<128&&t[e+9]<128},ct=function(t,e){for(var r=e,i=0;ht(t,e);)i+=10,i+=ft(t,e+6),dt(t,e+10)&&(i+=10),e+=i;if(i>0)return t.subarray(r,r+i)},ft=function(t,e){var r=0;return r=(127&t[e])<<21,r|=(127&t[e+1])<<14,r|=(127&t[e+2])<<7,r|=127&t[e+3]},gt=function(t,e){return ht(t,e)&&ft(t,e+6)+10<=t.length-e},vt=function(t){for(var e=yt(t),r=0;r<e.length;r++){var i=e[r];if(mt(i))return At(i)}},mt=function(t){return t&&"PRIV"===t.key&&"com.apple.streaming.transportStreamTimestamp"===t.info},pt=function(t){var e=String.fromCharCode(t[0],t[1],t[2],t[3]),r=ft(t,4);return{type:e,size:r,data:t.subarray(10,10+r)}},yt=function(t){for(var e=0,r=[];ht(t,e);){for(var i=ft(t,e+6),n=(e+=10)+i;e+8<n;){var a=pt(t.subarray(e)),s=Et(a);s&&r.push(s),e+=a.size+10}dt(t,e)&&(e+=10)}return r},Et=function(t){return"PRIV"===t.type?Tt(t):"W"===t.type[0]?Lt(t):St(t)},Tt=function(t){if(!(t.size<2)){var e=Rt(t.data,!0),r=new Uint8Array(t.data.subarray(e.length+1));return{key:t.type,info:e,data:r.buffer}}},St=function(t){if(!(t.size<2)){if("TXXX"===t.type){var e=1,r=Rt(t.data.subarray(e),!0);e+=r.length+1;var i=Rt(t.data.subarray(e));return{key:t.type,info:r,data:i}}var n=Rt(t.data.subarray(1));return{key:t.type,data:n}}},Lt=function(t){if("WXXX"===t.type){if(t.size<2)return;var e=1,r=Rt(t.data.subarray(e),!0);e+=r.length+1;var i=Rt(t.data.subarray(e));return{key:t.type,info:r,data:i}}var n=Rt(t.data);return{key:t.type,data:n}},At=function(t){if(8===t.data.byteLength){var e=new Uint8Array(t.data),r=1&e[3],i=(e[4]<<23)+(e[5]<<15)+(e[6]<<7)+e[7];return i/=45,r&&(i+=47721858.84),Math.round(i)}},Rt=function(t,e){void 0===e&&(e=!1);var r=bt();if(r){var i=r.decode(t);if(e){var n=i.indexOf("\0");return-1!==n?i.substring(0,n):i}return i.replace(/\0/g,"")}for(var a,s,o,l=t.length,u="",h=0;h<l;){if(0===(a=t[h++])&&e)return u;if(0!==a&&3!==a)switch(a>>4){case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:u+=String.fromCharCode(a);break;case 12:case 13:s=t[h++],u+=String.fromCharCode((31&a)<<6|63&s);break;case 14:s=t[h++],o=t[h++],u+=String.fromCharCode((15&a)<<12|(63&s)<<6|(63&o)<<0)}}return u};function bt(){if(!navigator.userAgent.includes("PlayStation 4"))return ut||void 0===self.TextDecoder||(ut=new self.TextDecoder("utf-8")),ut}var kt={hexDump:function(t){for(var e="",r=0;r<t.length;r++){var i=t[r].toString(16);i.length<2&&(i="0"+i),e+=i}return e}},Dt=Math.pow(2,32)-1,It=[].push,wt={video:1,audio:2,id3:3,text:4};function Ct(t){return String.fromCharCode.apply(null,t)}function _t(t,e){var r=t[e]<<8|t[e+1];return r<0?65536+r:r}function xt(t,e){var r=Ft(t,e);return r<0?4294967296+r:r}function Pt(t,e){var r=xt(t,e);return r*=Math.pow(2,32),r+=xt(t,e+4)}function Ft(t,e){return t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3]}function Mt(t,e,r){t[e]=r>>24,t[e+1]=r>>16&255,t[e+2]=r>>8&255,t[e+3]=255&r}function Ot(t,e){var r=[];if(!e.length)return r;for(var i=t.byteLength,n=0;n<i;){var a=xt(t,n),s=a>1?n+a:i;if(Ct(t.subarray(n+4,n+8))===e[0])if(1===e.length)r.push(t.subarray(n+8,s));else{var o=Ot(t.subarray(n+8,s),e.slice(1));o.length&&It.apply(r,o)}n=s}return r}function Nt(t){var e=[],r=t[0],i=8,n=xt(t,i);i+=4;var a=0,s=0;0===r?(a=xt(t,i),s=xt(t,i+4),i+=8):(a=Pt(t,i),s=Pt(t,i+8),i+=16),i+=2;var o=t.length+s,l=_t(t,i);i+=2;for(var u=0;u<l;u++){var h=i,d=xt(t,h);h+=4;var c=2147483647&d;if(1==(2147483648&d)>>>31)return w.warn("SIDX has hierarchical references (not supported)"),null;var f=xt(t,h);h+=4,e.push({referenceSize:c,subsegmentDuration:f,info:{duration:f/n,start:o,end:o+c-1}}),o+=c,i=h+=4}return{earliestPresentationTime:a,timescale:n,version:r,referencesCount:l,references:e}}function Ut(t){for(var e=[],r=Ot(t,["moov","trak"]),n=0;n<r.length;n++){var a=r[n],s=Ot(a,["tkhd"])[0];if(s){var o=s[0],l=xt(s,0===o?12:20),u=Ot(a,["mdia","mdhd"])[0];if(u){var h=xt(u,0===(o=u[0])?12:20),d=Ot(a,["mdia","hdlr"])[0];if(d){var c=Ct(d.subarray(8,12)),f={soun:O,vide:N}[c];if(f){var g=Bt(Ot(a,["mdia","minf","stbl","stsd"])[0]);e[l]={timescale:h,type:f},e[f]=i({timescale:h,id:l},g)}}}}}return Ot(t,["moov","mvex","trex"]).forEach((function(t){var r=xt(t,4),i=e[r];i&&(i.default={duration:xt(t,12),flags:xt(t,20)})})),e}function Bt(t){var e=t.subarray(8),r=e.subarray(86),i=Ct(e.subarray(4,8)),n=i,a="enca"===i||"encv"===i;if(a){var s=Ot(e,[i])[0];Ot(s.subarray("enca"===i?28:78),["sinf"]).forEach((function(t){var e=Ot(t,["schm"])[0];if(e){var r=Ct(e.subarray(4,8));if("cbcs"===r||"cenc"===r){var i=Ot(t,["frma"])[0];i&&(n=Ct(i))}}}))}switch(n){case"avc1":case"avc2":case"avc3":case"avc4":var o=Ot(r,["avcC"])[0];n+="."+Kt(o[1])+Kt(o[2])+Kt(o[3]);break;case"mp4a":var l=Ot(e,[i])[0],u=Ot(l.subarray(28),["esds"])[0];if(u&&u.length>12){var h=4;if(3!==u[h++])break;h=Gt(u,h),h+=2;var d=u[h++];if(128&d&&(h+=2),64&d&&(h+=u[h++]),4!==u[h++])break;h=Gt(u,h);var c=u[h++];if(64!==c)break;if(n+="."+Kt(c),h+=12,5!==u[h++])break;h=Gt(u,h);var f=u[h++],g=(248&f)>>3;31===g&&(g+=1+((7&f)<<3)+((224&u[h])>>5)),n+="."+g}break;case"hvc1":case"hev1":var v=Ot(r,["hvcC"])[0],m=v[1],p=["","A","B","C"][m>>6],y=31&m,E=xt(v,2),T=(32&m)>>5?"H":"L",S=v[12],L=v.subarray(6,12);n+="."+p+y,n+="."+E.toString(16).toUpperCase(),n+="."+T+S;for(var A="",R=L.length;R--;){var b=L[R];(b||A)&&(A="."+b.toString(16).toUpperCase()+A)}n+=A;break;case"dvh1":case"dvhe":var k=Ot(r,["dvcC"])[0],D=k[2]>>1&127,I=k[2]<<5&32|k[3]>>3&31;n+="."+Ht(D)+"."+Ht(I);break;case"vp09":var w=Ot(r,["vpcC"])[0],C=w[4],_=w[5],x=w[6]>>4&15;n+="."+Ht(C)+"."+Ht(_)+"."+Ht(x);break;case"av01":var P=Ot(r,["av1C"])[0],F=P[1]>>>5,M=31&P[1],O=P[2]>>>7?"H":"M",N=(64&P[2])>>6,U=(32&P[2])>>5,B=2===F&&N?U?12:10:N?10:8,G=(16&P[2])>>4,K=(8&P[2])>>3,H=(4&P[2])>>2,V=3&P[2];n+="."+F+"."+Ht(M)+O+"."+Ht(B)+"."+G+"."+K+H+V+"."+Ht(1)+"."+Ht(1)+"."+Ht(1)+".0"}return{codec:n,encrypted:a}}function Gt(t,e){for(var r=e+5;128&t[e++]&&e<r;);return e}function Kt(t){return("0"+t.toString(16).toUpperCase()).slice(-2)}function Ht(t){return(t<10?"0":"")+t}function Vt(t){var e=Ot(t,["schm"])[0];if(e){var r=Ct(e.subarray(4,8));if("cbcs"===r||"cenc"===r)return Ot(t,["schi","tenc"])[0]}return null}function Yt(t){var e=xt(t,0),r=8;1&e&&(r+=4),4&e&&(r+=4);for(var i=0,n=xt(t,4),a=0;a<n;a++)256&e&&(i+=xt(t,r),r+=4),512&e&&(r+=4),1024&e&&(r+=4),2048&e&&(r+=4);return i}function Wt(t,e){var r=new Uint8Array(t.length+e.length);return r.set(t),r.set(e,t.length),r}function jt(t,e){var r=[],i=e.samples,n=e.timescale,a=e.id,s=!1;return Ot(i,["moof"]).map((function(o){var l=o.byteOffset-8;Ot(o,["traf"]).map((function(o){var u=Ot(o,["tfdt"]).map((function(t){var e=t[0],r=xt(t,4);return 1===e&&(r*=Math.pow(2,32),r+=xt(t,8)),r/n}))[0];return void 0!==u&&(t=u),Ot(o,["tfhd"]).map((function(u){var h=xt(u,4),d=16777215&xt(u,0),c=0,f=0!=(16&d),g=0,v=0!=(32&d),m=8;h===a&&(0!=(1&d)&&(m+=8),0!=(2&d)&&(m+=4),0!=(8&d)&&(c=xt(u,m),m+=4),f&&(g=xt(u,m),m+=4),v&&(m+=4),"video"===e.type&&(s=function(t){if(!t)return!1;var e=t.indexOf("."),r=e<0?t:t.substring(0,e);return"hvc1"===r||"hev1"===r||"dvh1"===r||"dvhe"===r}(e.codec)),Ot(o,["trun"]).map((function(a){var o=a[0],u=16777215&xt(a,0),h=0!=(1&u),d=0,f=0!=(4&u),v=0!=(256&u),m=0,p=0!=(512&u),y=0,E=0!=(1024&u),T=0!=(2048&u),S=0,L=xt(a,4),A=8;h&&(d=xt(a,A),A+=4),f&&(A+=4);for(var R=d+l,b=0;b<L;b++){if(v?(m=xt(a,A),A+=4):m=c,p?(y=xt(a,A),A+=4):y=g,E&&(A+=4),T&&(S=0===o?xt(a,A):Ft(a,A),A+=4),e.type===N)for(var k=0;k<y;){var D=xt(i,R);qt(s,i[R+=4])&&Xt(i.subarray(R,R+D),s?2:1,t+S/n,r),R+=D,k+=D+4}t+=m/n}})))}))}))})),r}function qt(t,e){if(t){var r=e>>1&63;return 39===r||40===r}return 6==(31&e)}function Xt(t,e,r,i){var n=zt(t),a=0;a+=e;for(var s=0,o=0,l=0;a<n.length;){s=0;do{if(a>=n.length)break;s+=l=n[a++]}while(255===l);o=0;do{if(a>=n.length)break;o+=l=n[a++]}while(255===l);var u=n.length-a,h=a;if(o<u)a+=o;else if(o>u){w.error("Malformed SEI payload. "+o+" is too small, only "+u+" bytes left to parse.");break}if(4===s){if(181===n[h++]){var d=_t(n,h);if(h+=2,49===d){var c=xt(n,h);if(h+=4,1195456820===c){var f=n[h++];if(3===f){var g=n[h++],v=64&g,m=v?2+3*(31&g):0,p=new Uint8Array(m);if(v){p[0]=g;for(var y=1;y<m;y++)p[y]=n[h++]}i.push({type:f,payloadType:s,pts:r,bytes:p})}}}}}else if(5===s&&o>16){for(var E=[],T=0;T<16;T++){var S=n[h++].toString(16);E.push(1==S.length?"0"+S:S),3!==T&&5!==T&&7!==T&&9!==T||E.push("-")}for(var L=o-16,A=new Uint8Array(L),R=0;R<L;R++)A[R]=n[h++];i.push({payloadType:s,pts:r,uuid:E.join(""),userData:Rt(A),userDataBytes:A})}}}function zt(t){for(var e=t.byteLength,r=[],i=1;i<e-2;)0===t[i]&&0===t[i+1]&&3===t[i+2]?(r.push(i+2),i+=2):i++;if(0===r.length)return t;var n=e-r.length,a=new Uint8Array(n),s=0;for(i=0;i<n;s++,i++)s===r[0]&&(s++,r.shift()),a[i]=t[s];return a}function Qt(t,e,r){if(16!==t.byteLength)throw new RangeError("Invalid system id");var i,n,a;if(e){i=1,n=new Uint8Array(16*e.length);for(var s=0;s<e.length;s++){var o=e[s];if(16!==o.byteLength)throw new RangeError("Invalid key");n.set(o,16*s)}}else i=0,n=new Uint8Array;i>0?(a=new Uint8Array(4),e.length>0&&new DataView(a.buffer).setUint32(0,e.length,!1)):a=new Uint8Array;var l=new Uint8Array(4);return r&&r.byteLength>0&&new DataView(l.buffer).setUint32(0,r.byteLength,!1),function(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),i=1;i<e;i++)r[i-1]=arguments[i];for(var n=r.length,a=8,s=n;s--;)a+=r[s].byteLength;var o=new Uint8Array(a);for(o[0]=a>>24&255,o[1]=a>>16&255,o[2]=a>>8&255,o[3]=255&a,o.set(t,4),s=0,a=8;s<n;s++)o.set(r[s],a),a+=r[s].byteLength;return o}([112,115,115,104],new Uint8Array([i,0,0,0]),t,a,n,l,r||new Uint8Array)}function Jt(t){var e=t.getUint32(0),r=t.byteOffset,i=t.byteLength;if(i<e)return{offset:r,size:i};if(1886614376!==t.getUint32(4))return{offset:r,size:e};var n=t.getUint32(8)>>>24;if(0!==n&&1!==n)return{offset:r,size:e};var a=t.buffer,s=kt.hexDump(new Uint8Array(a,r+12,16)),o=t.getUint32(28),l=null,u=null;if(0===n){if(e-32<o||o<22)return{offset:r,size:e};u=new Uint8Array(a,r+32,o)}else if(1===n){if(!o||i<r+32+16*o+16)return{offset:r,size:e};l=[];for(var h=0;h<o;h++)l.push(new Uint8Array(a,r+32+16*h,16))}return{version:n,systemId:s,kids:l,data:u,offset:r,size:e}}var $t={},Zt=function(){function t(t,e,r,i,n){void 0===i&&(i=[1]),void 0===n&&(n=null),this.uri=void 0,this.method=void 0,this.keyFormat=void 0,this.keyFormatVersions=void 0,this.encrypted=void 0,this.isCommonEncryption=void 0,this.iv=null,this.key=null,this.keyId=null,this.pssh=null,this.method=t,this.uri=e,this.keyFormat=r,this.keyFormatVersions=i,this.iv=n,this.encrypted=!!t&&"NONE"!==t,this.isCommonEncryption=this.encrypted&&"AES-128"!==t}t.clearKeyUriToKeyIdMap=function(){$t={}};var e=t.prototype;return e.isSupported=function(){if(this.method){if("AES-128"===this.method||"NONE"===this.method)return!0;if("identity"===this.keyFormat)return"SAMPLE-AES"===this.method;switch(this.keyFormat){case z:case J:case Q:case X:return-1!==["ISO-23001-7","SAMPLE-AES","SAMPLE-AES-CENC","SAMPLE-AES-CTR"].indexOf(this.method)}}return!1},e.getDecryptData=function(e){if(!this.encrypted||!this.uri)return null;if("AES-128"===this.method&&this.uri&&!this.iv){"number"!=typeof e&&("AES-128"!==this.method||this.iv||w.warn('missing IV for initialization segment with method="'+this.method+'" - compliance issue'),e=0);var r=function(t){for(var e=new Uint8Array(16),r=12;r<16;r++)e[r]=t>>8*(15-r)&255;return e}(e);return new t(this.method,this.uri,"identity",this.keyFormatVersions,r)}var i=Y(this.uri);if(i)switch(this.keyFormat){case J:this.pssh=i,i.length>=22&&(this.keyId=i.subarray(i.length-22,i.length-6));break;case Q:var n=new Uint8Array([154,4,240,121,152,64,66,134,171,146,230,91,224,136,95,149]);this.pssh=Qt(n,null,i);var a=new Uint16Array(i.buffer,i.byteOffset,i.byteLength/2),s=String.fromCharCode.apply(null,Array.from(a)),o=s.substring(s.indexOf("<"),s.length),l=(new DOMParser).parseFromString(o,"text/xml").getElementsByTagName("KID")[0];if(l){var u=l.childNodes[0]?l.childNodes[0].nodeValue:l.getAttribute("VALUE");if(u){var h=V(u).subarray(0,16);!function(t){var e=function(t,e,r){var i=t[e];t[e]=t[r],t[r]=i};e(t,0,3),e(t,1,2),e(t,4,5),e(t,6,7)}(h),this.keyId=h}}break;default:var d=i.subarray(0,16);if(16!==d.length){var c=new Uint8Array(16);c.set(d,16-d.length),d=c}this.keyId=d}if(!this.keyId||16!==this.keyId.byteLength){var f=$t[this.uri];if(!f){var g=Object.keys($t).length%Number.MAX_SAFE_INTEGER;f=new Uint8Array(16),new DataView(f.buffer,12,4).setUint32(0,g),$t[this.uri]=f}this.keyId=f}return this},t}(),te=/\{\$([a-zA-Z0-9-_]+)\}/g;function ee(t){return te.test(t)}function re(t,e,r){if(null!==t.variableList||t.hasVariableRefs)for(var i=r.length;i--;){var n=r[i],a=e[n];a&&(e[n]=ie(t,a))}}function ie(t,e){if(null!==t.variableList||t.hasVariableRefs){var r=t.variableList;return e.replace(te,(function(e){var i=e.substring(2,e.length-1),n=null==r?void 0:r[i];return void 0===n?(t.playlistParsingError||(t.playlistParsingError=new Error('Missing preceding EXT-X-DEFINE tag for Variable Reference: "'+i+'"')),e):n}))}return e}function ne(t,e,r){var i,n,a=t.variableList;if(a||(t.variableList=a={}),"QUERYPARAM"in e){i=e.QUERYPARAM;try{var s=new self.URL(r).searchParams;if(!s.has(i))throw new Error('"'+i+'" does not match any query parameter in URI: "'+r+'"');n=s.get(i)}catch(e){t.playlistParsingError||(t.playlistParsingError=new Error("EXT-X-DEFINE QUERYPARAM: "+e.message))}}else i=e.NAME,n=e.VALUE;i in a?t.playlistParsingError||(t.playlistParsingError=new Error('EXT-X-DEFINE duplicate Variable Name declarations: "'+i+'"')):a[i]=n||""}function ae(t,e,r){var i=e.IMPORT;if(r&&i in r){var n=t.variableList;n||(t.variableList=n={}),n[i]=r[i]}else t.playlistParsingError||(t.playlistParsingError=new Error('EXT-X-DEFINE IMPORT attribute not found in Multivariant Playlist: "'+i+'"'))}function se(t){if(void 0===t&&(t=!0),"undefined"!=typeof self)return(t||!self.MediaSource)&&self.ManagedMediaSource||self.MediaSource||self.WebKitMediaSource}var oe={audio:{a3ds:1,"ac-3":.95,"ac-4":1,alac:.9,alaw:1,dra1:1,"dts+":1,"dts-":1,dtsc:1,dtse:1,dtsh:1,"ec-3":.9,enca:1,fLaC:.9,flac:.9,FLAC:.9,g719:1,g726:1,m4ae:1,mha1:1,mha2:1,mhm1:1,mhm2:1,mlpa:1,mp4a:1,"raw ":1,Opus:1,opus:1,samr:1,sawb:1,sawp:1,sevc:1,sqcp:1,ssmv:1,twos:1,ulaw:1},video:{avc1:1,avc2:1,avc3:1,avc4:1,avcp:1,av01:.8,drac:1,dva1:1,dvav:1,dvh1:.7,dvhe:.7,encv:1,hev1:.75,hvc1:.75,mjp2:1,mp4v:1,mvc1:1,mvc2:1,mvc3:1,mvc4:1,resv:1,rv60:1,s263:1,svc1:1,svc2:1,"vc-1":1,vp08:1,vp09:.9},text:{stpp:1,wvtt:1}};function le(t,e,r){return void 0===r&&(r=!0),!t.split(",").some((function(t){return!ue(t,e,r)}))}function ue(t,e,r){var i;void 0===r&&(r=!0);var n=se(r);return null!=(i=null==n?void 0:n.isTypeSupported(he(t,e)))&&i}function he(t,e){return e+'/mp4;codecs="'+t+'"'}function de(t){if(t){var e=t.substring(0,4);return oe.video[e]}return 2}function ce(t){return t.split(",").reduce((function(t,e){var r=oe.video[e];return r?(2*r+t)/(t?3:2):(oe.audio[e]+t)/(t?2:1)}),0)}var fe={},ge=/flac|opus/i;function ve(t,e){return void 0===e&&(e=!0),t.replace(ge,(function(t){return function(t,e){if(void 0===e&&(e=!0),fe[t])return fe[t];for(var r={flac:["flac","fLaC","FLAC"],opus:["opus","Opus"]}[t],i=0;i<r.length;i++)if(ue(r[i],"audio",e))return fe[t]=r[i],r[i];return t}(t.toLowerCase(),e)}))}function me(t,e){return t&&"mp4a"!==t?t:e?e.split(",")[0]:e}var pe=/#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g,ye=/#EXT-X-MEDIA:(.*)/g,Ee=/^#EXT(?:INF|-X-TARGETDURATION):/m,Te=new RegExp([/#EXTINF:\s*(\d*(?:\.\d+)?)(?:,(.*)\s+)?/.source,/(?!#) *(\S[^\r\n]*)/.source,/#EXT-X-BYTERANGE:*(.+)/.source,/#EXT-X-PROGRAM-DATE-TIME:(.+)/.source,/#.*/.source].join("|"),"g"),Se=new RegExp([/#(EXTM3U)/.source,/#EXT-X-(DATERANGE|DEFINE|KEY|MAP|PART|PART-INF|PLAYLIST-TYPE|PRELOAD-HINT|RENDITION-REPORT|SERVER-CONTROL|SKIP|START):(.+)/.source,/#EXT-X-(BITRATE|DISCONTINUITY-SEQUENCE|MEDIA-SEQUENCE|TARGETDURATION|VERSION): *(\d+)/.source,/#EXT-X-(DISCONTINUITY|ENDLIST|GAP|INDEPENDENT-SEGMENTS)/.source,/(#)([^:]*):(.*)/.source,/(#)(.*)(?:.*)\r?\n?/.source].join("|")),Le=function(){function t(){}return t.findGroup=function(t,e){for(var r=0;r<t.length;r++){var i=t[r];if(i.id===e)return i}},t.resolve=function(t,e){return p.buildAbsoluteURL(e,t,{alwaysNormalize:!0})},t.isMediaPlaylist=function(t){return Ee.test(t)},t.parseMasterPlaylist=function(e,r){var i,n={contentSteering:null,levels:[],playlistParsingError:null,sessionData:null,sessionKeys:null,startTimeOffset:null,variableList:null,hasVariableRefs:ee(e)},a=[];for(pe.lastIndex=0;null!=(i=pe.exec(e));)if(i[1]){var s,o=new x(i[1]);re(n,o,["CODECS","SUPPLEMENTAL-CODECS","ALLOWED-CPC","PATHWAY-ID","STABLE-VARIANT-ID","AUDIO","VIDEO","SUBTITLES","CLOSED-CAPTIONS","NAME"]);var l=ie(n,i[2]),u={attrs:o,bitrate:o.decimalInteger("BANDWIDTH")||o.decimalInteger("AVERAGE-BANDWIDTH"),name:o.NAME,url:t.resolve(l,r)},h=o.decimalResolution("RESOLUTION");h&&(u.width=h.width,u.height=h.height),be(o.CODECS,u),null!=(s=u.unknownCodecs)&&s.length||a.push(u),n.levels.push(u)}else if(i[3]){var d=i[3],c=i[4];switch(d){case"SESSION-DATA":var f=new x(c);re(n,f,["DATA-ID","LANGUAGE","VALUE","URI"]);var g=f["DATA-ID"];g&&(null===n.sessionData&&(n.sessionData={}),n.sessionData[g]=f);break;case"SESSION-KEY":var v=Ae(c,r,n);v.encrypted&&v.isSupported()?(null===n.sessionKeys&&(n.sessionKeys=[]),n.sessionKeys.push(v)):w.warn('[Keys] Ignoring invalid EXT-X-SESSION-KEY tag: "'+c+'"');break;case"DEFINE":var m=new x(c);re(n,m,["NAME","VALUE","QUERYPARAM"]),ne(n,m,r);break;case"CONTENT-STEERING":var p=new x(c);re(n,p,["SERVER-URI","PATHWAY-ID"]),n.contentSteering={uri:t.resolve(p["SERVER-URI"],r),pathwayId:p["PATHWAY-ID"]||"."};break;case"START":n.startTimeOffset=Re(c)}}var y=a.length>0&&a.length<n.levels.length;return n.levels=y?a:n.levels,0===n.levels.length&&(n.playlistParsingError=new Error("no levels found in manifest")),n},t.parseMasterPlaylistMedia=function(e,r,i){var n,a={},s=i.levels,o={AUDIO:s.map((function(t){return{id:t.attrs.AUDIO,audioCodec:t.audioCodec}})),SUBTITLES:s.map((function(t){return{id:t.attrs.SUBTITLES,textCodec:t.textCodec}})),"CLOSED-CAPTIONS":[]},l=0;for(ye.lastIndex=0;null!==(n=ye.exec(e));){var u=new x(n[1]),h=u.TYPE;if(h){var d=o[h],c=a[h]||[];a[h]=c,re(i,u,["URI","GROUP-ID","LANGUAGE","ASSOC-LANGUAGE","STABLE-RENDITION-ID","NAME","INSTREAM-ID","CHARACTERISTICS","CHANNELS"]);var f=u.LANGUAGE,g=u["ASSOC-LANGUAGE"],v=u.CHANNELS,m=u.CHARACTERISTICS,p=u["INSTREAM-ID"],y={attrs:u,bitrate:0,id:l++,groupId:u["GROUP-ID"]||"",name:u.NAME||f||"",type:h,default:u.bool("DEFAULT"),autoselect:u.bool("AUTOSELECT"),forced:u.bool("FORCED"),lang:f,url:u.URI?t.resolve(u.URI,r):""};if(g&&(y.assocLang=g),v&&(y.channels=v),m&&(y.characteristics=m),p&&(y.instreamId=p),null!=d&&d.length){var E=t.findGroup(d,y.groupId)||d[0];ke(y,E,"audioCodec"),ke(y,E,"textCodec")}c.push(y)}}return a},t.parseLevelPlaylist=function(t,e,r,i,n,a){var s,l,u,h=new H(e),d=h.fragments,c=null,f=0,g=0,v=0,m=0,p=null,E=new G(i,e),T=-1,S=!1,L=null;for(Te.lastIndex=0,h.m3u8=t,h.hasVariableRefs=ee(t);null!==(s=Te.exec(t));){S&&(S=!1,(E=new G(i,e)).start=v,E.sn=f,E.cc=m,E.level=r,c&&(E.initSegment=c,E.rawProgramDateTime=c.rawProgramDateTime,c.rawProgramDateTime=null,L&&(E.setByteRange(L),L=null)));var A=s[1];if(A){E.duration=parseFloat(A);var R=(" "+s[2]).slice(1);E.title=R||null,E.tagList.push(R?["INF",A,R]:["INF",A])}else if(s[3]){if(y(E.duration)){E.start=v,u&&we(E,u,h),E.sn=f,E.level=r,E.cc=m,d.push(E);var b=(" "+s[3]).slice(1);E.relurl=ie(h,b),De(E,p),p=E,v+=E.duration,f++,g=0,S=!0}}else if(s[4]){var k=(" "+s[4]).slice(1);p?E.setByteRange(k,p):E.setByteRange(k)}else if(s[5])E.rawProgramDateTime=(" "+s[5]).slice(1),E.tagList.push(["PROGRAM-DATE-TIME",E.rawProgramDateTime]),-1===T&&(T=d.length);else{if(!(s=s[0].match(Se))){w.warn("No matches on slow regex match for level playlist!");continue}for(l=1;l<s.length&&void 0===s[l];l++);var D=(" "+s[l]).slice(1),I=(" "+s[l+1]).slice(1),C=s[l+2]?(" "+s[l+2]).slice(1):"";switch(D){case"PLAYLIST-TYPE":h.type=I.toUpperCase();break;case"MEDIA-SEQUENCE":f=h.startSN=parseInt(I);break;case"SKIP":var _=new x(I);re(h,_,["RECENTLY-REMOVED-DATERANGES"]);var P=_.decimalInteger("SKIPPED-SEGMENTS");if(y(P)){h.skippedSegments=P;for(var M=P;M--;)d.unshift(null);f+=P}var O=_.enumeratedString("RECENTLY-REMOVED-DATERANGES");O&&(h.recentlyRemovedDateranges=O.split("\t"));break;case"TARGETDURATION":h.targetduration=Math.max(parseInt(I),1);break;case"VERSION":h.version=parseInt(I);break;case"INDEPENDENT-SEGMENTS":case"EXTM3U":break;case"ENDLIST":h.live=!1;break;case"#":(I||C)&&E.tagList.push(C?[I,C]:[I]);break;case"DISCONTINUITY":m++,E.tagList.push(["DIS"]);break;case"GAP":E.gap=!0,E.tagList.push([D]);break;case"BITRATE":E.tagList.push([D,I]);break;case"DATERANGE":var N=new x(I);re(h,N,["ID","CLASS","START-DATE","END-DATE","SCTE35-CMD","SCTE35-OUT","SCTE35-IN"]),re(h,N,N.clientAttrs);var U=new F(N,h.dateRanges[N.ID]);U.isValid||h.skippedSegments?h.dateRanges[U.id]=U:w.warn('Ignoring invalid DATERANGE tag: "'+I+'"'),E.tagList.push(["EXT-X-DATERANGE",I]);break;case"DEFINE":var B=new x(I);re(h,B,["NAME","VALUE","IMPORT","QUERYPARAM"]),"IMPORT"in B?ae(h,B,a):ne(h,B,e);break;case"DISCONTINUITY-SEQUENCE":m=parseInt(I);break;case"KEY":var V=Ae(I,e,h);if(V.isSupported()){if("NONE"===V.method){u=void 0;break}u||(u={}),u[V.keyFormat]&&(u=o({},u)),u[V.keyFormat]=V}else w.warn('[Keys] Ignoring invalid EXT-X-KEY tag: "'+I+'"');break;case"START":h.startTimeOffset=Re(I);break;case"MAP":var Y=new x(I);if(re(h,Y,["BYTERANGE","URI"]),E.duration){var W=new G(i,e);Ie(W,Y,r,u),c=W,E.initSegment=c,c.rawProgramDateTime&&!E.rawProgramDateTime&&(E.rawProgramDateTime=c.rawProgramDateTime)}else{var j=E.byteRangeEndOffset;if(j){var q=E.byteRangeStartOffset;L=j-q+"@"+q}else L=null;Ie(E,Y,r,u),c=E,S=!0}break;case"SERVER-CONTROL":var X=new x(I);h.canBlockReload=X.bool("CAN-BLOCK-RELOAD"),h.canSkipUntil=X.optionalFloat("CAN-SKIP-UNTIL",0),h.canSkipDateRanges=h.canSkipUntil>0&&X.bool("CAN-SKIP-DATERANGES"),h.partHoldBack=X.optionalFloat("PART-HOLD-BACK",0),h.holdBack=X.optionalFloat("HOLD-BACK",0);break;case"PART-INF":var z=new x(I);h.partTarget=z.decimalFloatingPoint("PART-TARGET");break;case"PART":var Q=h.partList;Q||(Q=h.partList=[]);var J=g>0?Q[Q.length-1]:void 0,$=g++,Z=new x(I);re(h,Z,["BYTERANGE","URI"]);var tt=new K(Z,E,e,$,J);Q.push(tt),E.duration+=tt.duration;break;case"PRELOAD-HINT":var et=new x(I);re(h,et,["URI"]),h.preloadHint=et;break;case"RENDITION-REPORT":var rt=new x(I);re(h,rt,["URI"]),h.renditionReports=h.renditionReports||[],h.renditionReports.push(rt);break;default:w.warn("line parsed but not handled: "+s)}}}p&&!p.relurl?(d.pop(),v-=p.duration,h.partList&&(h.fragmentHint=p)):h.partList&&(De(E,p),E.cc=m,h.fragmentHint=E,u&&we(E,u,h));var it=d.length,nt=d[0],at=d[it-1];if((v+=h.skippedSegments*h.targetduration)>0&&it&&at){h.averagetargetduration=v/it;var st=at.sn;h.endSN="initSegment"!==st?st:0,h.live||(at.endList=!0),nt&&(h.startCC=nt.cc)}else h.endSN=0,h.startCC=0;return h.fragmentHint&&(v+=h.fragmentHint.duration),h.totalduration=v,h.endCC=m,T>0&&function(t,e){for(var r=t[e],i=e;i--;){var n=t[i];if(!n)return;n.programDateTime=r.programDateTime-1e3*n.duration,r=n}}(d,T),h},t}();function Ae(t,e,r){var i,n,a=new x(t);re(r,a,["KEYFORMAT","KEYFORMATVERSIONS","URI","IV","URI"]);var s=null!=(i=a.METHOD)?i:"",o=a.URI,l=a.hexadecimalInteger("IV"),u=a.KEYFORMATVERSIONS,h=null!=(n=a.KEYFORMAT)?n:"identity";o&&a.IV&&!l&&w.error("Invalid IV: "+a.IV);var d=o?Le.resolve(o,e):"",c=(u||"1").split("/").map(Number).filter(Number.isFinite);return new Zt(s,d,h,c,l)}function Re(t){var e=new x(t).decimalFloatingPoint("TIME-OFFSET");return y(e)?e:null}function be(t,e){var r=(t||"").split(/[ ,]+/).filter((function(t){return t}));["video","audio","text"].forEach((function(t){var i=r.filter((function(e){return function(t,e){var r=oe[e];return!!r&&!!r[t.slice(0,4)]}(e,t)}));i.length&&(e[t+"Codec"]=i.join(","),r=r.filter((function(t){return-1===i.indexOf(t)})))})),e.unknownCodecs=r}function ke(t,e,r){var i=e[r];i&&(t[r]=i)}function De(t,e){t.rawProgramDateTime?t.programDateTime=Date.parse(t.rawProgramDateTime):null!=e&&e.programDateTime&&(t.programDateTime=e.endProgramDateTime),y(t.programDateTime)||(t.programDateTime=null,t.rawProgramDateTime=null)}function Ie(t,e,r,i){t.relurl=e.URI,e.BYTERANGE&&t.setByteRange(e.BYTERANGE),t.level=r,t.sn="initSegment",i&&(t.levelkeys=i),t.initSegment=null}function we(t,e,r){t.levelkeys=e;var i=r.encryptedFragments;i.length&&i[i.length-1].levelkeys===e||!Object.keys(e).some((function(t){return e[t].isCommonEncryption}))||i.push(t)}var Ce="manifest",_e="level",xe="audioTrack",Pe="subtitleTrack",Fe="main",Me="audio",Oe="subtitle";function Ne(t){switch(t.type){case xe:return Me;case Pe:return Oe;default:return Fe}}function Ue(t,e){var r=t.url;return void 0!==r&&0!==r.indexOf("data:")||(r=e.url),r}var Be=function(){function t(t){this.hls=void 0,this.loaders=Object.create(null),this.variableList=null,this.hls=t,this.registerListeners()}var e=t.prototype;return e.startLoad=function(t){},e.stopLoad=function(){this.destroyInternalLoaders()},e.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.AUDIO_TRACK_LOADING,this.onAudioTrackLoading,this),t.on(S.SUBTITLE_TRACK_LOADING,this.onSubtitleTrackLoading,this)},e.unregisterListeners=function(){var t=this.hls;t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_LOADING,this.onLevelLoading,this),t.off(S.AUDIO_TRACK_LOADING,this.onAudioTrackLoading,this),t.off(S.SUBTITLE_TRACK_LOADING,this.onSubtitleTrackLoading,this)},e.createInternalLoader=function(t){var e=this.hls.config,r=e.pLoader,i=e.loader,n=new(r||i)(e);return this.loaders[t.type]=n,n},e.getInternalLoader=function(t){return this.loaders[t.type]},e.resetInternalLoader=function(t){this.loaders[t]&&delete this.loaders[t]},e.destroyInternalLoaders=function(){for(var t in this.loaders){var e=this.loaders[t];e&&e.destroy(),this.resetInternalLoader(t)}},e.destroy=function(){this.variableList=null,this.unregisterListeners(),this.destroyInternalLoaders()},e.onManifestLoading=function(t,e){var r=e.url;this.variableList=null,this.load({id:null,level:0,responseType:"text",type:Ce,url:r,deliveryDirectives:null})},e.onLevelLoading=function(t,e){var r=e.id,i=e.level,n=e.pathwayId,a=e.url,s=e.deliveryDirectives;this.load({id:r,level:i,pathwayId:n,responseType:"text",type:_e,url:a,deliveryDirectives:s})},e.onAudioTrackLoading=function(t,e){var r=e.id,i=e.groupId,n=e.url,a=e.deliveryDirectives;this.load({id:r,groupId:i,level:null,responseType:"text",type:xe,url:n,deliveryDirectives:a})},e.onSubtitleTrackLoading=function(t,e){var r=e.id,i=e.groupId,n=e.url,a=e.deliveryDirectives;this.load({id:r,groupId:i,level:null,responseType:"text",type:Pe,url:n,deliveryDirectives:a})},e.load=function(t){var e,r,i,n=this,a=this.hls.config,s=this.getInternalLoader(t);if(s){var l=s.context;if(l&&l.url===t.url&&l.level===t.level)return void w.trace("[playlist-loader]: playlist request ongoing");w.log("[playlist-loader]: aborting previous loader for type: "+t.type),s.abort()}if(r=t.type===Ce?a.manifestLoadPolicy.default:o({},a.playlistLoadPolicy.default,{timeoutRetry:null,errorRetry:null}),s=this.createInternalLoader(t),y(null==(e=t.deliveryDirectives)?void 0:e.part)&&(t.type===_e&&null!==t.level?i=this.hls.levels[t.level].details:t.type===xe&&null!==t.id?i=this.hls.audioTracks[t.id].details:t.type===Pe&&null!==t.id&&(i=this.hls.subtitleTracks[t.id].details),i)){var u=i.partTarget,h=i.targetduration;if(u&&h){var d=1e3*Math.max(3*u,.8*h);r=o({},r,{maxTimeToFirstByteMs:Math.min(d,r.maxTimeToFirstByteMs),maxLoadTimeMs:Math.min(d,r.maxTimeToFirstByteMs)})}}var c=r.errorRetry||r.timeoutRetry||{},f={loadPolicy:r,timeout:r.maxLoadTimeMs,maxRetry:c.maxNumRetry||0,retryDelay:c.retryDelayMs||0,maxRetryDelay:c.maxRetryDelayMs||0},g={onSuccess:function(t,e,r,i){var a=n.getInternalLoader(r);n.resetInternalLoader(r.type);var s=t.data;0===s.indexOf("#EXTM3U")?(e.parsing.start=performance.now(),Le.isMediaPlaylist(s)?n.handleTrackOrLevelPlaylist(t,e,r,i||null,a):n.handleMasterPlaylist(t,e,r,i)):n.handleManifestParsingError(t,r,new Error("no EXTM3U delimiter"),i||null,e)},onError:function(t,e,r,i){n.handleNetworkError(e,r,!1,t,i)},onTimeout:function(t,e,r){n.handleNetworkError(e,r,!0,void 0,t)}};s.load(t,f,g)},e.handleMasterPlaylist=function(t,e,r,i){var n=this.hls,a=t.data,s=Ue(t,r),o=Le.parseMasterPlaylist(a,s);if(o.playlistParsingError)this.handleManifestParsingError(t,r,o.playlistParsingError,i,e);else{var l=o.contentSteering,u=o.levels,h=o.sessionData,d=o.sessionKeys,c=o.startTimeOffset,f=o.variableList;this.variableList=f;var g=Le.parseMasterPlaylistMedia(a,s,o),v=g.AUDIO,m=void 0===v?[]:v,p=g.SUBTITLES,y=g["CLOSED-CAPTIONS"];m.length&&(m.some((function(t){return!t.url}))||!u[0].audioCodec||u[0].attrs.AUDIO||(w.log("[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one"),m.unshift({type:"main",name:"main",groupId:"main",default:!1,autoselect:!1,forced:!1,id:-1,attrs:new x({}),bitrate:0,url:""}))),n.trigger(S.MANIFEST_LOADED,{levels:u,audioTracks:m,subtitles:p,captions:y,contentSteering:l,url:s,stats:e,networkDetails:i,sessionData:h,sessionKeys:d,startTimeOffset:c,variableList:f})}},e.handleTrackOrLevelPlaylist=function(t,e,r,i,n){var a=this.hls,s=r.id,o=r.level,l=r.type,u=Ue(t,r),h=y(o)?o:y(s)?s:0,d=Ne(r),c=Le.parseLevelPlaylist(t.data,u,h,d,0,this.variableList);if(l===Ce){var f={attrs:new x({}),bitrate:0,details:c,name:"",url:u};a.trigger(S.MANIFEST_LOADED,{levels:[f],audioTracks:[],url:u,stats:e,networkDetails:i,sessionData:null,sessionKeys:null,contentSteering:null,startTimeOffset:null,variableList:null})}e.parsing.end=performance.now(),r.levelDetails=c,this.handlePlaylistLoaded(c,t,e,r,i,n)},e.handleManifestParsingError=function(t,e,r,i,n){this.hls.trigger(S.ERROR,{type:L.NETWORK_ERROR,details:A.MANIFEST_PARSING_ERROR,fatal:e.type===Ce,url:t.url,err:r,error:r,reason:r.message,response:t,context:e,networkDetails:i,stats:n})},e.handleNetworkError=function(t,e,r,n,a){void 0===r&&(r=!1);var s="A network "+(r?"timeout":"error"+(n?" (status "+n.code+")":""))+" occurred while loading "+t.type;t.type===_e?s+=": "+t.level+" id: "+t.id:t.type!==xe&&t.type!==Pe||(s+=" id: "+t.id+' group-id: "'+t.groupId+'"');var o=new Error(s);w.warn("[playlist-loader]: "+s);var l=A.UNKNOWN,u=!1,h=this.getInternalLoader(t);switch(t.type){case Ce:l=r?A.MANIFEST_LOAD_TIMEOUT:A.MANIFEST_LOAD_ERROR,u=!0;break;case _e:l=r?A.LEVEL_LOAD_TIMEOUT:A.LEVEL_LOAD_ERROR,u=!1;break;case xe:l=r?A.AUDIO_TRACK_LOAD_TIMEOUT:A.AUDIO_TRACK_LOAD_ERROR,u=!1;break;case Pe:l=r?A.SUBTITLE_TRACK_LOAD_TIMEOUT:A.SUBTITLE_LOAD_ERROR,u=!1}h&&this.resetInternalLoader(t.type);var d={type:L.NETWORK_ERROR,details:l,fatal:u,url:t.url,loader:h,context:t,error:o,networkDetails:e,stats:a};if(n){var c=(null==e?void 0:e.url)||t.url;d.response=i({url:c,data:void 0},n)}this.hls.trigger(S.ERROR,d)},e.handlePlaylistLoaded=function(t,e,r,i,n,a){var s=this.hls,o=i.type,l=i.level,u=i.id,h=i.groupId,d=i.deliveryDirectives,c=Ue(e,i),f=Ne(i),g="number"==typeof i.level&&f===Fe?l:void 0;if(t.fragments.length){t.targetduration||(t.playlistParsingError=new Error("Missing Target Duration"));var v=t.playlistParsingError;if(v)s.trigger(S.ERROR,{type:L.NETWORK_ERROR,details:A.LEVEL_PARSING_ERROR,fatal:!1,url:c,error:v,reason:v.message,response:e,context:i,level:g,parent:f,networkDetails:n,stats:r});else switch(t.live&&a&&(a.getCacheAge&&(t.ageHeader=a.getCacheAge()||0),a.getCacheAge&&!isNaN(t.ageHeader)||(t.ageHeader=0)),o){case Ce:case _e:s.trigger(S.LEVEL_LOADED,{details:t,level:g||0,id:u||0,stats:r,networkDetails:n,deliveryDirectives:d});break;case xe:s.trigger(S.AUDIO_TRACK_LOADED,{details:t,id:u||0,groupId:h||"",stats:r,networkDetails:n,deliveryDirectives:d});break;case Pe:s.trigger(S.SUBTITLE_TRACK_LOADED,{details:t,id:u||0,groupId:h||"",stats:r,networkDetails:n,deliveryDirectives:d})}}else{var m=new Error("No Segments found in Playlist");s.trigger(S.ERROR,{type:L.NETWORK_ERROR,details:A.LEVEL_EMPTY_ERROR,fatal:!1,url:c,error:m,reason:m.message,response:e,context:i,level:g,parent:f,networkDetails:n,stats:r})}},t}();function Ge(t,e){var r;try{r=new Event("addtrack")}catch(t){(r=document.createEvent("Event")).initEvent("addtrack",!1,!1)}r.track=t,e.dispatchEvent(r)}function Ke(t,e){var r=t.mode;if("disabled"===r&&(t.mode="hidden"),t.cues&&!t.cues.getCueById(e.id))try{if(t.addCue(e),!t.cues.getCueById(e.id))throw new Error("addCue is failed for: "+e)}catch(r){w.debug("[texttrack-utils]: "+r);try{var i=new self.TextTrackCue(e.startTime,e.endTime,e.text);i.id=e.id,t.addCue(i)}catch(t){w.debug("[texttrack-utils]: Legacy TextTrackCue fallback failed: "+t)}}"disabled"===r&&(t.mode=r)}function He(t){var e=t.mode;if("disabled"===e&&(t.mode="hidden"),t.cues)for(var r=t.cues.length;r--;)t.removeCue(t.cues[r]);"disabled"===e&&(t.mode=e)}function Ve(t,e,r,i){var n=t.mode;if("disabled"===n&&(t.mode="hidden"),t.cues&&t.cues.length>0)for(var a=function(t,e,r){var i=[],n=function(t,e){if(e<t[0].startTime)return 0;var r=t.length-1;if(e>t[r].endTime)return-1;for(var i=0,n=r;i<=n;){var a=Math.floor((n+i)/2);if(e<t[a].startTime)n=a-1;else{if(!(e>t[a].startTime&&i<r))return a;i=a+1}}return t[i].startTime-e<e-t[n].startTime?i:n}(t,e);if(n>-1)for(var a=n,s=t.length;a<s;a++){var o=t[a];if(o.startTime>=e&&o.endTime<=r)i.push(o);else if(o.startTime>r)return i}return i}(t.cues,e,r),s=0;s<a.length;s++)i&&!i(a[s])||t.removeCue(a[s]);"disabled"===n&&(t.mode=n)}function Ye(t){for(var e=[],r=0;r<t.length;r++){var i=t[r];"subtitles"!==i.kind&&"captions"!==i.kind||!i.label||e.push(t[r])}return e}var We="org.id3",je="com.apple.quicktime.HLS",qe="https://aomedia.org/emsg/ID3";function Xe(){if("undefined"!=typeof self)return self.VTTCue||self.TextTrackCue}function ze(t,e,r,n,a){var s=new t(e,r,"");try{s.value=n,a&&(s.type=a)}catch(o){s=new t(e,r,JSON.stringify(a?i({type:a},n):n))}return s}var Qe=function(){var t=Xe();try{t&&new t(0,Number.POSITIVE_INFINITY,"")}catch(t){return Number.MAX_VALUE}return Number.POSITIVE_INFINITY}();function Je(t,e){return t.getTime()/1e3-e}var $e=function(){function t(t){this.hls=void 0,this.id3Track=null,this.media=null,this.dateRangeCuesAppended={},this.hls=t,this._registerListeners()}var e=t.prototype;return e.destroy=function(){this._unregisterListeners(),this.id3Track=null,this.media=null,this.dateRangeCuesAppended={},this.hls=null},e._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.FRAG_PARSING_METADATA,this.onFragParsingMetadata,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.LEVEL_UPDATED,this.onLevelUpdated,this)},e._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.FRAG_PARSING_METADATA,this.onFragParsingMetadata,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.LEVEL_UPDATED,this.onLevelUpdated,this)},e.onMediaAttached=function(t,e){this.media=e.media},e.onMediaDetaching=function(){this.id3Track&&(He(this.id3Track),this.id3Track=null,this.media=null,this.dateRangeCuesAppended={})},e.onManifestLoading=function(){this.dateRangeCuesAppended={}},e.createTrack=function(t){var e=this.getID3Track(t.textTracks);return e.mode="hidden",e},e.getID3Track=function(t){if(this.media){for(var e=0;e<t.length;e++){var r=t[e];if("metadata"===r.kind&&"id3"===r.label)return Ge(r,this.media),r}return this.media.addTextTrack("metadata","id3")}},e.onFragParsingMetadata=function(t,e){if(this.media){var r=this.hls.config,i=r.enableEmsgMetadataCues,n=r.enableID3MetadataCues;if(i||n){var a=e.samples;this.id3Track||(this.id3Track=this.createTrack(this.media));var s=Xe();if(s)for(var o=0;o<a.length;o++){var l=a[o].type;if((l!==qe||i)&&n){var u=yt(a[o].data);if(u){var h=a[o].pts,d=h+a[o].duration;d>Qe&&(d=Qe),d-h<=0&&(d=h+.25);for(var c=0;c<u.length;c++){var f=u[c];if(!mt(f)){this.updateId3CueEnds(h,l);var g=ze(s,h,d,f,l);g&&this.id3Track.addCue(g)}}}}}}}},e.updateId3CueEnds=function(t,e){var r,i=null==(r=this.id3Track)?void 0:r.cues;if(i)for(var n=i.length;n--;){var a=i[n];a.type===e&&a.startTime<t&&a.endTime===Qe&&(a.endTime=t)}},e.onBufferFlushing=function(t,e){var r=e.startOffset,i=e.endOffset,n=e.type,a=this.id3Track,s=this.hls;if(s){var o=s.config,l=o.enableEmsgMetadataCues,u=o.enableID3MetadataCues;a&&(l||u)&&Ve(a,r,i,"audio"===n?function(t){return t.type===We&&u}:"video"===n?function(t){return t.type===qe&&l}:function(t){return t.type===We&&u||t.type===qe&&l})}},e.onLevelUpdated=function(t,e){var r=this,i=e.details;if(this.media&&i.hasProgramDateTime&&this.hls.config.enableDateRangeMetadataCues){var n=this.dateRangeCuesAppended,a=this.id3Track,s=i.dateRanges,o=Object.keys(s);if(a)for(var l=Object.keys(n).filter((function(t){return!o.includes(t)})),u=function(){var t=l[h];Object.keys(n[t].cues).forEach((function(e){a.removeCue(n[t].cues[e])})),delete n[t]},h=l.length;h--;)u();var d=i.fragments[i.fragments.length-1];if(0!==o.length&&y(null==d?void 0:d.programDateTime)){this.id3Track||(this.id3Track=this.createTrack(this.media));for(var c=d.programDateTime/1e3-d.start,f=Xe(),g=function(){var t=o[v],e=s[t],i=Je(e.startDate,c),a=n[t],l=(null==a?void 0:a.cues)||{},u=(null==a?void 0:a.durationKnown)||!1,h=Qe,d=e.endDate;if(d)h=Je(d,c),u=!0;else if(e.endOnNext&&!u){var g=o.reduce((function(t,r){if(r!==e.id){var i=s[r];if(i.class===e.class&&i.startDate>e.startDate&&(!t||e.startDate<t.startDate))return i}return t}),null);g&&(h=Je(g.startDate,c),u=!0)}for(var m,p,y=Object.keys(e.attr),E=0;E<y.length;E++){var T=y[E];if("ID"!==(p=T)&&"CLASS"!==p&&"START-DATE"!==p&&"DURATION"!==p&&"END-DATE"!==p&&"END-ON-NEXT"!==p){var S=l[T];if(S)u&&!a.durationKnown&&(S.endTime=h);else if(f){var L=e.attr[T];P(T)&&(m=L,L=Uint8Array.from(m.replace(/^0x/,"").replace(/([\da-fA-F]{2}) ?/g,"0x$1 ").replace(/ +$/,"").split(" ")).buffer);var A=ze(f,i,h,{key:T,data:L},je);A&&(A.id=t,r.id3Track.addCue(A),l[T]=A)}}}n[t]={cues:l,dateRange:e,durationKnown:u}},v=0;v<o.length;v++)g()}}},t}(),Ze=function(){function t(t){var e=this;this.hls=void 0,this.config=void 0,this.media=null,this.levelDetails=null,this.currentTime=0,this.stallCount=0,this._latency=null,this.timeupdateHandler=function(){return e.timeupdate()},this.hls=t,this.config=t.config,this.registerListeners()}var e=t.prototype;return e.destroy=function(){this.unregisterListeners(),this.onMediaDetaching(),this.levelDetails=null,this.hls=this.timeupdateHandler=null},e.registerListeners=function(){this.hls.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),this.hls.on(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.on(S.LEVEL_UPDATED,this.onLevelUpdated,this),this.hls.on(S.ERROR,this.onError,this)},e.unregisterListeners=function(){this.hls.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),this.hls.off(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.off(S.LEVEL_UPDATED,this.onLevelUpdated,this),this.hls.off(S.ERROR,this.onError,this)},e.onMediaAttached=function(t,e){this.media=e.media,this.media.addEventListener("timeupdate",this.timeupdateHandler)},e.onMediaDetaching=function(){this.media&&(this.media.removeEventListener("timeupdate",this.timeupdateHandler),this.media=null)},e.onManifestLoading=function(){this.levelDetails=null,this._latency=null,this.stallCount=0},e.onLevelUpdated=function(t,e){var r=e.details;this.levelDetails=r,r.advanced&&this.timeupdate(),!r.live&&this.media&&this.media.removeEventListener("timeupdate",this.timeupdateHandler)},e.onError=function(t,e){var r;e.details===A.BUFFER_STALLED_ERROR&&(this.stallCount++,null!=(r=this.levelDetails)&&r.live&&w.warn("[playback-rate-controller]: Stall detected, adjusting target latency"))},e.timeupdate=function(){var t=this.media,e=this.levelDetails;if(t&&e){this.currentTime=t.currentTime;var r=this.computeLatency();if(null!==r){this._latency=r;var i=this.config,n=i.lowLatencyMode,a=i.maxLiveSyncPlaybackRate;if(n&&1!==a&&e.live){var s=this.targetLatency;if(null!==s){var o=r-s;if(o<Math.min(this.maxLatency,s+e.targetduration)&&o>.05&&this.forwardBufferLength>1){var l=Math.min(2,Math.max(1,a)),u=Math.round(2/(1+Math.exp(-.75*o-this.edgeStalled))*20)/20;t.playbackRate=Math.min(l,Math.max(1,u))}else 1!==t.playbackRate&&0!==t.playbackRate&&(t.playbackRate=1)}}}}},e.estimateLiveEdge=function(){var t=this.levelDetails;return null===t?null:t.edge+t.age},e.computeLatency=function(){var t=this.estimateLiveEdge();return null===t?null:t-this.currentTime},s(t,[{key:"latency",get:function(){return this._latency||0}},{key:"maxLatency",get:function(){var t=this.config,e=this.levelDetails;return void 0!==t.liveMaxLatencyDuration?t.liveMaxLatencyDuration:e?t.liveMaxLatencyDurationCount*e.targetduration:0}},{key:"targetLatency",get:function(){var t=this.levelDetails;if(null===t)return null;var e=t.holdBack,r=t.partHoldBack,i=t.targetduration,n=this.config,a=n.liveSyncDuration,s=n.liveSyncDurationCount,o=n.lowLatencyMode,l=this.hls.userConfig,u=o&&r||e;(l.liveSyncDuration||l.liveSyncDurationCount||0===u)&&(u=void 0!==a?a:s*i);var h=i;return u+Math.min(1*this.stallCount,h)}},{key:"liveSyncPosition",get:function(){var t=this.estimateLiveEdge(),e=this.targetLatency,r=this.levelDetails;if(null===t||null===e||null===r)return null;var i=r.edge,n=t-e-this.edgeStalled,a=i-r.totalduration,s=i-(this.config.lowLatencyMode&&r.partTarget||r.targetduration);return Math.min(Math.max(a,n),s)}},{key:"drift",get:function(){var t=this.levelDetails;return null===t?1:t.drift}},{key:"edgeStalled",get:function(){var t=this.levelDetails;if(null===t)return 0;var e=3*(this.config.lowLatencyMode&&t.partTarget||t.targetduration);return Math.max(t.age-e,0)}},{key:"forwardBufferLength",get:function(){var t=this.media,e=this.levelDetails;if(!t||!e)return 0;var r=t.buffered.length;return(r?t.buffered.end(r-1):e.edge)-this.currentTime}}]),t}(),tr=["NONE","TYPE-0","TYPE-1",null],er=["SDR","PQ","HLG"],rr="",ir="YES",nr="v2";function ar(t){var e=t.canSkipUntil,r=t.canSkipDateRanges,i=t.age;return e&&i<e/2?r?nr:ir:rr}var sr=function(){function t(t,e,r){this.msn=void 0,this.part=void 0,this.skip=void 0,this.msn=t,this.part=e,this.skip=r}return t.prototype.addDirectives=function(t){var e=new self.URL(t);return void 0!==this.msn&&e.searchParams.set("_HLS_msn",this.msn.toString()),void 0!==this.part&&e.searchParams.set("_HLS_part",this.part.toString()),this.skip&&e.searchParams.set("_HLS_skip",this.skip),e.href},t}(),or=function(){function t(t){this._attrs=void 0,this.audioCodec=void 0,this.bitrate=void 0,this.codecSet=void 0,this.url=void 0,this.frameRate=void 0,this.height=void 0,this.id=void 0,this.name=void 0,this.videoCodec=void 0,this.width=void 0,this.details=void 0,this.fragmentError=0,this.loadError=0,this.loaded=void 0,this.realBitrate=0,this.supportedPromise=void 0,this.supportedResult=void 0,this._avgBitrate=0,this._audioGroups=void 0,this._subtitleGroups=void 0,this._urlId=0,this.url=[t.url],this._attrs=[t.attrs],this.bitrate=t.bitrate,t.details&&(this.details=t.details),this.id=t.id||0,this.name=t.name,this.width=t.width||0,this.height=t.height||0,this.frameRate=t.attrs.optionalFloat("FRAME-RATE",0),this._avgBitrate=t.attrs.decimalInteger("AVERAGE-BANDWIDTH"),this.audioCodec=t.audioCodec,this.videoCodec=t.videoCodec,this.codecSet=[t.videoCodec,t.audioCodec].filter((function(t){return!!t})).map((function(t){return t.substring(0,4)})).join(","),this.addGroupId("audio",t.attrs.AUDIO),this.addGroupId("text",t.attrs.SUBTITLES)}var e=t.prototype;return e.hasAudioGroup=function(t){return lr(this._audioGroups,t)},e.hasSubtitleGroup=function(t){return lr(this._subtitleGroups,t)},e.addGroupId=function(t,e){if(e)if("audio"===t){var r=this._audioGroups;r||(r=this._audioGroups=[]),-1===r.indexOf(e)&&r.push(e)}else if("text"===t){var i=this._subtitleGroups;i||(i=this._subtitleGroups=[]),-1===i.indexOf(e)&&i.push(e)}},e.addFallback=function(){},s(t,[{key:"maxBitrate",get:function(){return Math.max(this.realBitrate,this.bitrate)}},{key:"averageBitrate",get:function(){return this._avgBitrate||this.realBitrate||this.bitrate}},{key:"attrs",get:function(){return this._attrs[0]}},{key:"codecs",get:function(){return this.attrs.CODECS||""}},{key:"pathwayId",get:function(){return this.attrs["PATHWAY-ID"]||"."}},{key:"videoRange",get:function(){return this.attrs["VIDEO-RANGE"]||"SDR"}},{key:"score",get:function(){return this.attrs.optionalFloat("SCORE",0)}},{key:"uri",get:function(){return this.url[0]||""}},{key:"audioGroups",get:function(){return this._audioGroups}},{key:"subtitleGroups",get:function(){return this._subtitleGroups}},{key:"urlId",get:function(){return 0},set:function(t){}},{key:"audioGroupIds",get:function(){return this.audioGroups?[this.audioGroupId]:void 0}},{key:"textGroupIds",get:function(){return this.subtitleGroups?[this.textGroupId]:void 0}},{key:"audioGroupId",get:function(){var t;return null==(t=this.audioGroups)?void 0:t[0]}},{key:"textGroupId",get:function(){var t;return null==(t=this.subtitleGroups)?void 0:t[0]}}]),t}();function lr(t,e){return!(!e||!t)&&-1!==t.indexOf(e)}function ur(t,e){var r=e.startPTS;if(y(r)){var i,n=0;e.sn>t.sn?(n=r-t.start,i=t):(n=t.start-r,i=e),i.duration!==n&&(i.duration=n)}else e.sn>t.sn?t.cc===e.cc&&t.minEndPTS?e.start=t.start+(t.minEndPTS-t.start):e.start=t.start+t.duration:e.start=Math.max(t.start-e.duration,0)}function hr(t,e,r,i,n,a){i-r<=0&&(w.warn("Fragment should have a positive duration",e),i=r+e.duration,a=n+e.duration);var s=r,o=i,l=e.startPTS,u=e.endPTS;if(y(l)){var h=Math.abs(l-r);y(e.deltaPTS)?e.deltaPTS=Math.max(h,e.deltaPTS):e.deltaPTS=h,s=Math.max(r,l),r=Math.min(r,l),n=Math.min(n,e.startDTS),o=Math.min(i,u),i=Math.max(i,u),a=Math.max(a,e.endDTS)}var d=r-e.start;0!==e.start&&(e.start=r),e.duration=i-e.start,e.startPTS=r,e.maxStartPTS=s,e.startDTS=n,e.endPTS=i,e.minEndPTS=o,e.endDTS=a;var c,f=e.sn;if(!t||f<t.startSN||f>t.endSN)return 0;var g=f-t.startSN,v=t.fragments;for(v[g]=e,c=g;c>0;c--)ur(v[c],v[c-1]);for(c=g;c<v.length-1;c++)ur(v[c],v[c+1]);return t.fragmentHint&&ur(v[v.length-1],t.fragmentHint),t.PTSKnown=t.alignedSliding=!0,d}function dr(t,e){for(var r=null,i=t.fragments,n=i.length-1;n>=0;n--){var a=i[n].initSegment;if(a){r=a;break}}t.fragmentHint&&delete t.fragmentHint.endPTS;var s,l,u,h,d,c=0;if(function(t,e,r){for(var i=e.skippedSegments,n=Math.max(t.startSN,e.startSN)-e.startSN,a=(t.fragmentHint?1:0)+(i?e.endSN:Math.min(t.endSN,e.endSN))-e.startSN,s=e.startSN-t.startSN,o=e.fragmentHint?e.fragments.concat(e.fragmentHint):e.fragments,l=t.fragmentHint?t.fragments.concat(t.fragmentHint):t.fragments,u=n;u<=a;u++){var h=l[s+u],d=o[u];i&&!d&&u<i&&(d=e.fragments[u]=h),h&&d&&r(h,d)}}(t,e,(function(t,i){t.relurl&&(c=t.cc-i.cc),y(t.startPTS)&&y(t.endPTS)&&(i.start=i.startPTS=t.startPTS,i.startDTS=t.startDTS,i.maxStartPTS=t.maxStartPTS,i.endPTS=t.endPTS,i.endDTS=t.endDTS,i.minEndPTS=t.minEndPTS,i.duration=t.endPTS-t.startPTS,i.duration&&(s=i),e.PTSKnown=e.alignedSliding=!0),i.elementaryStreams=t.elementaryStreams,i.loader=t.loader,i.stats=t.stats,t.initSegment&&(i.initSegment=t.initSegment,r=t.initSegment)})),r&&(e.fragmentHint?e.fragments.concat(e.fragmentHint):e.fragments).forEach((function(t){var e;!t||t.initSegment&&t.initSegment.relurl!==(null==(e=r)?void 0:e.relurl)||(t.initSegment=r)})),e.skippedSegments)if(e.deltaUpdateFailed=e.fragments.some((function(t){return!t})),e.deltaUpdateFailed){w.warn("[level-helper] Previous playlist missing segments skipped in delta playlist");for(var f=e.skippedSegments;f--;)e.fragments.shift();e.startSN=e.fragments[0].sn,e.startCC=e.fragments[0].cc}else e.canSkipDateRanges&&(e.dateRanges=(l=t.dateRanges,u=e.dateRanges,h=e.recentlyRemovedDateranges,d=o({},l),h&&h.forEach((function(t){delete d[t]})),Object.keys(u).forEach((function(t){var e=new F(u[t].attr,d[t]);e.isValid?d[t]=e:w.warn('Ignoring invalid Playlist Delta Update DATERANGE tag: "'+JSON.stringify(u[t].attr)+'"')})),d));var g=e.fragments;if(c){w.warn("discontinuity sliding from playlist, take drift into account");for(var v=0;v<g.length;v++)g[v].cc+=c}e.skippedSegments&&(e.startCC=e.fragments[0].cc),function(t,e,r){if(t&&e)for(var i=0,n=0,a=t.length;n<=a;n++){var s=t[n],o=e[n+i];s&&o&&s.index===o.index&&s.fragment.sn===o.fragment.sn?r(s,o):i--}}(t.partList,e.partList,(function(t,e){e.elementaryStreams=t.elementaryStreams,e.stats=t.stats})),s?hr(e,s,s.startPTS,s.endPTS,s.startDTS,s.endDTS):cr(t,e),g.length&&(e.totalduration=e.edge-g[0].start),e.driftStartTime=t.driftStartTime,e.driftStart=t.driftStart;var m=e.advancedDateTime;if(e.advanced&&m){var p=e.edge;e.driftStart||(e.driftStartTime=m,e.driftStart=p),e.driftEndTime=m,e.driftEnd=p}else e.driftEndTime=t.driftEndTime,e.driftEnd=t.driftEnd,e.advancedDateTime=t.advancedDateTime}function cr(t,e){var r=e.startSN+e.skippedSegments-t.startSN,i=t.fragments;r<0||r>=i.length||fr(e,i[r].start)}function fr(t,e){if(e){for(var r=t.fragments,i=t.skippedSegments;i<r.length;i++)r[i].start+=e;t.fragmentHint&&(t.fragmentHint.start+=e)}}function gr(t,e,r){var i;return null!=t&&t.details?vr(null==(i=t.details)?void 0:i.partList,e,r):null}function vr(t,e,r){if(t)for(var i=t.length;i--;){var n=t[i];if(n.index===r&&n.fragment.sn===e)return n}return null}function mr(t){t.forEach((function(t,e){var r=t.details;null!=r&&r.fragments&&r.fragments.forEach((function(t){t.level=e}))}))}function pr(t){switch(t.details){case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_TIMEOUT:case A.LEVEL_LOAD_TIMEOUT:case A.MANIFEST_LOAD_TIMEOUT:return!0}return!1}function yr(t,e){var r=pr(e);return t.default[(r?"timeout":"error")+"Retry"]}function Er(t,e){var r="linear"===t.backoff?1:Math.pow(2,e);return Math.min(r*t.retryDelayMs,t.maxRetryDelayMs)}function Tr(t){return i(i({},t),{errorRetry:null,timeoutRetry:null})}function Sr(t,e,r,i){if(!t)return!1;var n=null==i?void 0:i.code,a=e<t.maxNumRetry&&(function(t){return 0===t&&!1===navigator.onLine||!!t&&(t<400||t>499)}(n)||!!r);return t.shouldRetry?t.shouldRetry(t,e,r,i,a):a}var Lr=function(t,e){for(var r=0,i=t.length-1,n=null,a=null;r<=i;){var s=e(a=t[n=(r+i)/2|0]);if(s>0)r=n+1;else{if(!(s<0))return a;i=n-1}}return null};function Ar(t,e,r,i,n){void 0===r&&(r=0),void 0===i&&(i=0),void 0===n&&(n=.005);var a=null;if(t){a=e[t.sn-e[0].sn+1]||null;var s=t.endDTS-r;s>0&&s<15e-7&&(r+=15e-7)}else 0===r&&0===e[0].start&&(a=e[0]);if(a&&((!t||t.level===a.level)&&0===Rr(r,i,a)||function(t,e,r){if(e&&0===e.start&&e.level<t.level&&(e.endPTS||0)>0){var i=e.tagList.reduce((function(t,e){return"INF"===e[0]&&(t+=parseFloat(e[1])),t}),r);return t.start<=i}return!1}(a,t,Math.min(n,i))))return a;var o=Lr(e,Rr.bind(null,r,i));return!o||o===t&&a?a:o}function Rr(t,e,r){if(void 0===t&&(t=0),void 0===e&&(e=0),r.start<=t&&r.start+r.duration>t)return 0;var i=Math.min(e,r.duration+(r.deltaPTS?r.deltaPTS:0));return r.start+r.duration-i<=t?1:r.start-i>t&&r.start?-1:0}function br(t,e,r){var i=1e3*Math.min(e,r.duration+(r.deltaPTS?r.deltaPTS:0));return(r.endProgramDateTime||0)-i>t}var kr=0,Dr=2,Ir=3,wr=5,Cr=0,_r=1,xr=2,Pr=function(){function t(t){this.hls=void 0,this.playlistError=0,this.penalizedRenditions={},this.log=void 0,this.warn=void 0,this.error=void 0,this.hls=t,this.log=w.log.bind(w,"[info]:"),this.warn=w.warn.bind(w,"[warning]:"),this.error=w.error.bind(w,"[error]:"),this.registerListeners()}var e=t.prototype;return e.registerListeners=function(){var t=this.hls;t.on(S.ERROR,this.onError,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_UPDATED,this.onLevelUpdated,this)},e.unregisterListeners=function(){var t=this.hls;t&&(t.off(S.ERROR,this.onError,this),t.off(S.ERROR,this.onErrorOut,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_UPDATED,this.onLevelUpdated,this))},e.destroy=function(){this.unregisterListeners(),this.hls=null,this.penalizedRenditions={}},e.startLoad=function(t){},e.stopLoad=function(){this.playlistError=0},e.getVariantLevelIndex=function(t){return(null==t?void 0:t.type)===Fe?t.level:this.hls.loadLevel},e.onManifestLoading=function(){this.playlistError=0,this.penalizedRenditions={}},e.onLevelUpdated=function(){this.playlistError=0},e.onError=function(t,e){var r,i;if(!e.fatal){var n=this.hls,a=e.context;switch(e.details){case A.FRAG_LOAD_ERROR:case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_ERROR:case A.KEY_LOAD_TIMEOUT:return void(e.errorAction=this.getFragRetryOrSwitchAction(e));case A.FRAG_PARSING_ERROR:if(null!=(r=e.frag)&&r.gap)return void(e.errorAction={action:kr,flags:Cr});case A.FRAG_GAP:case A.FRAG_DECRYPT_ERROR:return e.errorAction=this.getFragRetryOrSwitchAction(e),void(e.errorAction.action=Dr);case A.LEVEL_EMPTY_ERROR:case A.LEVEL_PARSING_ERROR:var s,o,l=e.parent===Fe?e.level:n.loadLevel;return void(e.details===A.LEVEL_EMPTY_ERROR&&null!=(s=e.context)&&null!=(o=s.levelDetails)&&o.live?e.errorAction=this.getPlaylistRetryOrSwitchAction(e,l):(e.levelRetry=!1,e.errorAction=this.getLevelSwitchAction(e,l)));case A.LEVEL_LOAD_ERROR:case A.LEVEL_LOAD_TIMEOUT:return void("number"==typeof(null==a?void 0:a.level)&&(e.errorAction=this.getPlaylistRetryOrSwitchAction(e,a.level)));case A.AUDIO_TRACK_LOAD_ERROR:case A.AUDIO_TRACK_LOAD_TIMEOUT:case A.SUBTITLE_LOAD_ERROR:case A.SUBTITLE_TRACK_LOAD_TIMEOUT:if(a){var u=n.levels[n.loadLevel];if(u&&(a.type===xe&&u.hasAudioGroup(a.groupId)||a.type===Pe&&u.hasSubtitleGroup(a.groupId)))return e.errorAction=this.getPlaylistRetryOrSwitchAction(e,n.loadLevel),e.errorAction.action=Dr,void(e.errorAction.flags=_r)}return;case A.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED:var h=n.levels[n.loadLevel],d=null==h?void 0:h.attrs["HDCP-LEVEL"];return void(d?e.errorAction={action:Dr,flags:xr,hdcpLevel:d}:this.keySystemError(e));case A.BUFFER_ADD_CODEC_ERROR:case A.REMUX_ALLOC_ERROR:case A.BUFFER_APPEND_ERROR:return void(e.errorAction=this.getLevelSwitchAction(e,null!=(i=e.level)?i:n.loadLevel));case A.INTERNAL_EXCEPTION:case A.BUFFER_APPENDING_ERROR:case A.BUFFER_FULL_ERROR:case A.LEVEL_SWITCH_ERROR:case A.BUFFER_STALLED_ERROR:case A.BUFFER_SEEK_OVER_HOLE:case A.BUFFER_NUDGE_ON_STALL:return void(e.errorAction={action:kr,flags:Cr})}e.type===L.KEY_SYSTEM_ERROR&&this.keySystemError(e)}},e.keySystemError=function(t){var e=this.getVariantLevelIndex(t.frag);t.levelRetry=!1,t.errorAction=this.getLevelSwitchAction(t,e)},e.getPlaylistRetryOrSwitchAction=function(t,e){var r=yr(this.hls.config.playlistLoadPolicy,t),i=this.playlistError++;if(Sr(r,i,pr(t),t.response))return{action:wr,flags:Cr,retryConfig:r,retryCount:i};var n=this.getLevelSwitchAction(t,e);return r&&(n.retryConfig=r,n.retryCount=i),n},e.getFragRetryOrSwitchAction=function(t){var e=this.hls,r=this.getVariantLevelIndex(t.frag),i=e.levels[r],n=e.config,a=n.fragLoadPolicy,s=n.keyLoadPolicy,o=yr(t.details.startsWith("key")?s:a,t),l=e.levels.reduce((function(t,e){return t+e.fragmentError}),0);if(i&&(t.details!==A.FRAG_GAP&&i.fragmentError++,Sr(o,l,pr(t),t.response)))return{action:wr,flags:Cr,retryConfig:o,retryCount:l};var u=this.getLevelSwitchAction(t,r);return o&&(u.retryConfig=o,u.retryCount=l),u},e.getLevelSwitchAction=function(t,e){var r=this.hls;null==e&&(e=r.loadLevel);var i=this.hls.levels[e];if(i){var n,a,s=t.details;i.loadError++,s===A.BUFFER_APPEND_ERROR&&i.fragmentError++;var o=-1,l=r.levels,u=r.loadLevel,h=r.minAutoLevel,d=r.maxAutoLevel;r.autoLevelEnabled||(r.loadLevel=-1);for(var c,f=null==(n=t.frag)?void 0:n.type,g=(f===Me&&s===A.FRAG_PARSING_ERROR||"audio"===t.sourceBufferName&&(s===A.BUFFER_ADD_CODEC_ERROR||s===A.BUFFER_APPEND_ERROR))&&l.some((function(t){var e=t.audioCodec;return i.audioCodec!==e})),v="video"===t.sourceBufferName&&(s===A.BUFFER_ADD_CODEC_ERROR||s===A.BUFFER_APPEND_ERROR)&&l.some((function(t){var e=t.codecSet,r=t.audioCodec;return i.codecSet!==e&&i.audioCodec===r})),m=null!=(a=t.context)?a:{},p=m.type,y=m.groupId,E=function(){var e=(T+u)%l.length;if(e!==u&&e>=h&&e<=d&&0===l[e].loadError){var r,n,a=l[e];if(s===A.FRAG_GAP&&f===Fe&&t.frag){var c=l[e].details;if(c){var m=Ar(t.frag,c.fragments,t.frag.start);if(null!=m&&m.gap)return 0}}else{if(p===xe&&a.hasAudioGroup(y)||p===Pe&&a.hasSubtitleGroup(y))return 0;if(f===Me&&null!=(r=i.audioGroups)&&r.some((function(t){return a.hasAudioGroup(t)}))||f===Oe&&null!=(n=i.subtitleGroups)&&n.some((function(t){return a.hasSubtitleGroup(t)}))||g&&i.audioCodec===a.audioCodec||!g&&i.audioCodec!==a.audioCodec||v&&i.codecSet===a.codecSet)return 0}return o=e,1}},T=l.length;T--&&(0===(c=E())||1!==c););if(o>-1&&r.loadLevel!==o)return t.levelRetry=!0,this.playlistError=0,{action:Dr,flags:Cr,nextAutoLevel:o}}return{action:Dr,flags:_r}},e.onErrorOut=function(t,e){var r;switch(null==(r=e.errorAction)?void 0:r.action){case kr:break;case Dr:this.sendAlternateToPenaltyBox(e),e.errorAction.resolved||e.details===A.FRAG_GAP?/MediaSource readyState: ended/.test(e.error.message)&&(this.warn('MediaSource ended after "'+e.sourceBufferName+'" sourceBuffer append error. Attempting to recover from media error.'),this.hls.recoverMediaError()):e.fatal=!0}e.fatal&&this.hls.stopLoad()},e.sendAlternateToPenaltyBox=function(t){var e=this.hls,r=t.errorAction;if(r){var i=r.flags,n=r.hdcpLevel,a=r.nextAutoLevel;switch(i){case Cr:this.switchLevel(t,a);break;case xr:n&&(e.maxHdcpLevel=tr[tr.indexOf(n)-1],r.resolved=!0),this.warn('Restricting playback to HDCP-LEVEL of "'+e.maxHdcpLevel+'" or lower')}r.resolved||this.switchLevel(t,a)}},e.switchLevel=function(t,e){void 0!==e&&t.errorAction&&(this.warn("switching to level "+e+" after "+t.details),this.hls.nextAutoLevel=e,t.errorAction.resolved=!0,this.hls.nextLoadLevel=this.hls.nextAutoLevel)},t}(),Fr=function(){function t(t,e){this.hls=void 0,this.timer=-1,this.requestScheduled=-1,this.canLoad=!1,this.log=void 0,this.warn=void 0,this.log=w.log.bind(w,e+":"),this.warn=w.warn.bind(w,e+":"),this.hls=t}var e=t.prototype;return e.destroy=function(){this.clearTimer(),this.hls=this.log=this.warn=null},e.clearTimer=function(){-1!==this.timer&&(self.clearTimeout(this.timer),this.timer=-1)},e.startLoad=function(){this.canLoad=!0,this.requestScheduled=-1,this.loadPlaylist()},e.stopLoad=function(){this.canLoad=!1,this.clearTimer()},e.switchParams=function(t,e,r){var i=null==e?void 0:e.renditionReports;if(i){for(var n=-1,a=0;a<i.length;a++){var s=i[a],o=void 0;try{o=new self.URL(s.URI,e.url).href}catch(t){w.warn("Could not construct new URL for Rendition Report: "+t),o=s.URI||""}if(o===t){n=a;break}o===t.substring(0,o.length)&&(n=a)}if(-1!==n){var l=i[n],u=parseInt(l["LAST-MSN"])||(null==e?void 0:e.lastPartSn),h=parseInt(l["LAST-PART"])||(null==e?void 0:e.lastPartIndex);if(this.hls.config.lowLatencyMode){var d=Math.min(e.age-e.partTarget,e.targetduration);h>=0&&d>e.partTarget&&(h+=1)}var c=r&&ar(r);return new sr(u,h>=0?h:void 0,c)}}},e.loadPlaylist=function(t){-1===this.requestScheduled&&(this.requestScheduled=self.performance.now())},e.shouldLoadPlaylist=function(t){return this.canLoad&&!!t&&!!t.url&&(!t.details||t.details.live)},e.shouldReloadPlaylist=function(t){return-1===this.timer&&-1===this.requestScheduled&&this.shouldLoadPlaylist(t)},e.playlistLoaded=function(t,e,r){var i=this,n=e.details,a=e.stats,s=self.performance.now(),o=a.loading.first?Math.max(0,s-a.loading.first):0;if(n.advancedDateTime=Date.now()-o,n.live||null!=r&&r.live){if(n.reloaded(r),r&&this.log("live playlist "+t+" "+(n.advanced?"REFRESHED "+n.lastPartSn+"-"+n.lastPartIndex:n.updated?"UPDATED":"MISSED")),r&&n.fragments.length>0&&dr(r,n),!this.canLoad||!n.live)return;var l,u=void 0,h=void 0;if(n.canBlockReload&&n.endSN&&n.advanced){var d=this.hls.config.lowLatencyMode,c=n.lastPartSn,f=n.endSN,g=n.lastPartIndex,v=c===f;-1!==g?(u=v?f+1:c,h=v?d?0:g:g+1):u=f+1;var m=n.age,p=m+n.ageHeader,y=Math.min(p-n.partTarget,1.5*n.targetduration);if(y>0){if(r&&y>r.tuneInGoal)this.warn("CDN Tune-in goal increased from: "+r.tuneInGoal+" to: "+y+" with playlist age: "+n.age),y=0;else{var E=Math.floor(y/n.targetduration);u+=E,void 0!==h&&(h+=Math.round(y%n.targetduration/n.partTarget)),this.log("CDN Tune-in age: "+n.ageHeader+"s last advanced "+m.toFixed(2)+"s goal: "+y+" skip sn "+E+" to part "+h)}n.tuneInGoal=y}if(l=this.getDeliveryDirectives(n,e.deliveryDirectives,u,h),d||!v)return void this.loadPlaylist(l)}else(n.canBlockReload||n.canSkipUntil)&&(l=this.getDeliveryDirectives(n,e.deliveryDirectives,u,h));var T=this.hls.mainForwardBufferInfo,S=T?T.end-T.len:0,L=function(t,e){void 0===e&&(e=1/0);var r=1e3*t.targetduration;if(t.updated){var i=t.fragments;if(i.length&&4*r>e){var n=1e3*i[i.length-1].duration;n<r&&(r=n)}}else r/=2;return Math.round(r)}(n,1e3*(n.edge-S));n.updated&&s>this.requestScheduled+L&&(this.requestScheduled=a.loading.start),void 0!==u&&n.canBlockReload?this.requestScheduled=a.loading.first+L-(1e3*n.partTarget||1e3):-1===this.requestScheduled||this.requestScheduled+L<s?this.requestScheduled=s:this.requestScheduled-s<=0&&(this.requestScheduled+=L);var A=this.requestScheduled-s;A=Math.max(0,A),this.log("reload live playlist "+t+" in "+Math.round(A)+" ms"),this.timer=self.setTimeout((function(){return i.loadPlaylist(l)}),A)}else this.clearTimer()},e.getDeliveryDirectives=function(t,e,r,i){var n=ar(t);return null!=e&&e.skip&&t.deltaUpdateFailed&&(r=e.msn,i=e.part,n=rr),new sr(r,i,n)},e.checkRetry=function(t){var e=this,r=t.details,i=pr(t),n=t.errorAction,a=n||{},s=a.action,o=a.retryCount,l=void 0===o?0:o,u=a.retryConfig,h=!!n&&!!u&&(s===wr||!n.resolved&&s===Dr);if(h){var d;if(this.requestScheduled=-1,l>=u.maxNumRetry)return!1;if(i&&null!=(d=t.context)&&d.deliveryDirectives)this.warn("Retrying playlist loading "+(l+1)+"/"+u.maxNumRetry+' after "'+r+'" without delivery-directives'),this.loadPlaylist();else{var c=Er(u,l);this.timer=self.setTimeout((function(){return e.loadPlaylist()}),c),this.warn("Retrying playlist loading "+(l+1)+"/"+u.maxNumRetry+' after "'+r+'" in '+c+"ms")}t.levelRetry=!0,n.resolved=!0}return h},t}(),Mr=function(){function t(t,e,r){void 0===e&&(e=0),void 0===r&&(r=0),this.halfLife=void 0,this.alpha_=void 0,this.estimate_=void 0,this.totalWeight_=void 0,this.halfLife=t,this.alpha_=t?Math.exp(Math.log(.5)/t):0,this.estimate_=e,this.totalWeight_=r}var e=t.prototype;return e.sample=function(t,e){var r=Math.pow(this.alpha_,t);this.estimate_=e*(1-r)+r*this.estimate_,this.totalWeight_+=t},e.getTotalWeight=function(){return this.totalWeight_},e.getEstimate=function(){if(this.alpha_){var t=1-Math.pow(this.alpha_,this.totalWeight_);if(t)return this.estimate_/t}return this.estimate_},t}(),Or=function(){function t(t,e,r,i){void 0===i&&(i=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_=r,this.minWeight_=.001,this.minDelayMs_=50,this.slow_=new Mr(t),this.fast_=new Mr(e),this.defaultTTFB_=i,this.ttfb_=new Mr(t)}var e=t.prototype;return e.update=function(t,e){var r=this.slow_,i=this.fast_,n=this.ttfb_;r.halfLife!==t&&(this.slow_=new Mr(t,r.getEstimate(),r.getTotalWeight())),i.halfLife!==e&&(this.fast_=new Mr(e,i.getEstimate(),i.getTotalWeight())),n.halfLife!==t&&(this.ttfb_=new Mr(t,n.getEstimate(),n.getTotalWeight()))},e.sample=function(t,e){var r=(t=Math.max(t,this.minDelayMs_))/1e3,i=8*e/r;this.fast_.sample(r,i),this.slow_.sample(r,i)},e.sampleTTFB=function(t){var e=t/1e3,r=Math.sqrt(2)*Math.exp(-Math.pow(e,2)/2);this.ttfb_.sample(r,Math.max(t,5))},e.canEstimate=function(){return this.fast_.getTotalWeight()>=this.minWeight_},e.getEstimate=function(){return this.canEstimate()?Math.min(this.fast_.getEstimate(),this.slow_.getEstimate()):this.defaultEstimate_},e.getEstimateTTFB=function(){return this.ttfb_.getTotalWeight()>=this.minWeight_?this.ttfb_.getEstimate():this.defaultTTFB_},e.destroy=function(){},t}(),Nr={supported:!0,configurations:[],decodingInfoResults:[{supported:!0,powerEfficient:!0,smooth:!0}]},Ur={};function Br(t,e,r){var n=t.videoCodec,a=t.audioCodec;if(!n||!a||!r)return Promise.resolve(Nr);var s={width:t.width,height:t.height,bitrate:Math.ceil(Math.max(.9*t.bitrate,t.averageBitrate)),framerate:t.frameRate||30},o=t.videoRange;"SDR"!==o&&(s.transferFunction=o.toLowerCase());var l=n.split(",").map((function(t){return{type:"media-source",video:i(i({},s),{},{contentType:he(t,"video")})}}));return a&&t.audioGroups&&t.audioGroups.forEach((function(t){var r;t&&(null==(r=e.groups[t])||r.tracks.forEach((function(e){if(e.groupId===t){var r=e.channels||"",i=parseFloat(r);y(i)&&i>2&&l.push.apply(l,a.split(",").map((function(t){return{type:"media-source",audio:{contentType:he(t,"audio"),channels:""+i}}})))}})))})),Promise.all(l.map((function(t){var e=function(t){var e=t.audio,r=t.video,i=r||e;if(i){var n=i.contentType.split('"')[1];if(r)return"r"+r.height+"x"+r.width+"f"+Math.ceil(r.framerate)+(r.transferFunction||"sd")+"_"+n+"_"+Math.ceil(r.bitrate/1e5);if(e)return"c"+e.channels+(e.spatialRendering?"s":"n")+"_"+n}return""}(t);return Ur[e]||(Ur[e]=r.decodingInfo(t))}))).then((function(t){return{supported:!t.some((function(t){return!t.supported})),configurations:l,decodingInfoResults:t}})).catch((function(t){return{supported:!1,configurations:l,decodingInfoResults:[],error:t}}))}function Gr(t,e){var r=!1,i=[];return t&&(r="SDR"!==t,i=[t]),e&&(i=e.allowedVideoRanges||er.slice(0),i=(r=void 0!==e.preferHDR?e.preferHDR:function(){if("function"==typeof matchMedia){var t=matchMedia("(dynamic-range: high)"),e=matchMedia("bad query");if(t.media!==e.media)return!0===t.matches}return!1}())?i.filter((function(t){return"SDR"!==t})):["SDR"]),{preferHDR:r,allowedVideoRanges:i}}function Kr(t,e){w.log('[abr] start candidates with "'+t+'" ignored because '+e)}function Hr(t,e,r){if("attrs"in t){var i=e.indexOf(t);if(-1!==i)return i}for(var n=0;n<e.length;n++)if(Vr(t,e[n],r))return n;return-1}function Vr(t,e,r){var i=t.groupId,n=t.name,a=t.lang,s=t.assocLang,o=t.characteristics,l=t.default,u=t.forced;return(void 0===i||e.groupId===i)&&(void 0===n||e.name===n)&&(void 0===a||e.lang===a)&&(void 0===a||e.assocLang===s)&&(void 0===l||e.default===l)&&(void 0===u||e.forced===u)&&(void 0===o||function(t,e){void 0===e&&(e="");var r=t.split(","),i=e.split(",");return r.length===i.length&&!r.some((function(t){return-1===i.indexOf(t)}))}(o,e.characteristics))&&(void 0===r||r(t,e))}function Yr(t,e){var r=t.audioCodec,i=t.channels;return!(void 0!==r&&(e.audioCodec||"").substring(0,4)!==r.substring(0,4)||void 0!==i&&i!==(e.channels||"2"))}function Wr(t,e,r){for(var i=e;i;i--)if(r(t[i]))return i;for(var n=e+1;n<t.length;n++)if(r(t[n]))return n;return-1}var jr=function(){function t(t){var e=this;this.hls=void 0,this.lastLevelLoadSec=0,this.lastLoadedFragLevel=-1,this.firstSelection=-1,this._nextAutoLevel=-1,this.nextAutoLevelKey="",this.audioTracksByGroup=null,this.codecTiers=null,this.timer=-1,this.fragCurrent=null,this.partCurrent=null,this.bitrateTestDelay=0,this.bwEstimator=void 0,this._abandonRulesCheck=function(){var t=e.fragCurrent,r=e.partCurrent,i=e.hls,n=i.autoLevelEnabled,a=i.media;if(t&&a){var s=performance.now(),o=r?r.stats:t.stats,l=r?r.duration:t.duration,u=s-o.loading.start,h=i.minAutoLevel;if(o.aborted||o.loaded&&o.loaded===o.total||t.level<=h)return e.clearTimer(),void(e._nextAutoLevel=-1);if(n&&!a.paused&&a.playbackRate&&a.readyState){var d=i.mainForwardBufferInfo;if(null!==d){var c=e.bwEstimator.getEstimateTTFB(),f=Math.abs(a.playbackRate);if(!(u<=Math.max(c,l/(2*f)*1e3))){var g=d.len/f,v=o.loading.first?o.loading.first-o.loading.start:-1,m=o.loaded&&v>-1,p=e.getBwEstimate(),E=i.levels,T=E[t.level],L=o.total||Math.max(o.loaded,Math.round(l*T.averageBitrate/8)),A=m?u-v:u;A<1&&m&&(A=Math.min(u,8*o.loaded/p));var R=m?1e3*o.loaded/A:0,b=R?(L-o.loaded)/R:8*L/p+c/1e3;if(!(b<=g)){var k,D=R?8*R:p,I=Number.POSITIVE_INFINITY;for(k=t.level-1;k>h;k--){var C=E[k].maxBitrate;if((I=e.getTimeToLoadFrag(c/1e3,D,l*C,!E[k].details))<g)break}if(!(I>=b||I>10*l)){i.nextLoadLevel=i.nextAutoLevel=k,m?e.bwEstimator.sample(u-Math.min(c,v),o.loaded):e.bwEstimator.sampleTTFB(u);var _=E[k].maxBitrate;e.getBwEstimate()*e.hls.config.abrBandWidthUpFactor>_&&e.resetEstimator(_),e.clearTimer(),w.warn("[abr] Fragment "+t.sn+(r?" part "+r.index:"")+" of level "+t.level+" is loading too slowly;\n Time to underbuffer: "+g.toFixed(3)+" s\n Estimated load time for current fragment: "+b.toFixed(3)+" s\n Estimated load time for down switch fragment: "+I.toFixed(3)+" s\n TTFB estimate: "+(0|v)+" ms\n Current BW estimate: "+(y(p)?0|p:"Unknown")+" bps\n New BW estimate: "+(0|e.getBwEstimate())+" bps\n Switching to level "+k+" @ "+(0|_)+" bps"),i.trigger(S.FRAG_LOAD_EMERGENCY_ABORTED,{frag:t,part:r,stats:o})}}}}}}},this.hls=t,this.bwEstimator=this.initEstimator(),this.registerListeners()}var e=t.prototype;return e.resetEstimator=function(t){t&&(w.log("setting initial bwe to "+t),this.hls.config.abrEwmaDefaultEstimate=t),this.firstSelection=-1,this.bwEstimator=this.initEstimator()},e.initEstimator=function(){var t=this.hls.config;return new Or(t.abrEwmaSlowVoD,t.abrEwmaFastVoD,t.abrEwmaDefaultEstimate)},e.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.FRAG_LOADING,this.onFragLoading,this),t.on(S.FRAG_LOADED,this.onFragLoaded,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this),t.on(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.MAX_AUTO_LEVEL_UPDATED,this.onMaxAutoLevelUpdated,this),t.on(S.ERROR,this.onError,this)},e.unregisterListeners=function(){var t=this.hls;t&&(t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.FRAG_LOADING,this.onFragLoading,this),t.off(S.FRAG_LOADED,this.onFragLoaded,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this),t.off(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.MAX_AUTO_LEVEL_UPDATED,this.onMaxAutoLevelUpdated,this),t.off(S.ERROR,this.onError,this))},e.destroy=function(){this.unregisterListeners(),this.clearTimer(),this.hls=this._abandonRulesCheck=null,this.fragCurrent=this.partCurrent=null},e.onManifestLoading=function(t,e){this.lastLoadedFragLevel=-1,this.firstSelection=-1,this.lastLevelLoadSec=0,this.fragCurrent=this.partCurrent=null,this.onLevelsUpdated(),this.clearTimer()},e.onLevelsUpdated=function(){this.lastLoadedFragLevel>-1&&this.fragCurrent&&(this.lastLoadedFragLevel=this.fragCurrent.level),this._nextAutoLevel=-1,this.onMaxAutoLevelUpdated(),this.codecTiers=null,this.audioTracksByGroup=null},e.onMaxAutoLevelUpdated=function(){this.firstSelection=-1,this.nextAutoLevelKey=""},e.onFragLoading=function(t,e){var r,i=e.frag;this.ignoreFragment(i)||(i.bitrateTest||(this.fragCurrent=i,this.partCurrent=null!=(r=e.part)?r:null),this.clearTimer(),this.timer=self.setInterval(this._abandonRulesCheck,100))},e.onLevelSwitching=function(t,e){this.clearTimer()},e.onError=function(t,e){if(!e.fatal)switch(e.details){case A.BUFFER_ADD_CODEC_ERROR:case A.BUFFER_APPEND_ERROR:this.lastLoadedFragLevel=-1,this.firstSelection=-1;break;case A.FRAG_LOAD_TIMEOUT:var r=e.frag,i=this.fragCurrent,n=this.partCurrent;if(r&&i&&r.sn===i.sn&&r.level===i.level){var a=performance.now(),s=n?n.stats:r.stats,o=a-s.loading.start,l=s.loading.first?s.loading.first-s.loading.start:-1;if(s.loaded&&l>-1){var u=this.bwEstimator.getEstimateTTFB();this.bwEstimator.sample(o-Math.min(u,l),s.loaded)}else this.bwEstimator.sampleTTFB(o)}}},e.getTimeToLoadFrag=function(t,e,r,i){return t+r/e+(i?this.lastLevelLoadSec:0)},e.onLevelLoaded=function(t,e){var r=this.hls.config,i=e.stats.loading,n=i.end-i.start;y(n)&&(this.lastLevelLoadSec=n/1e3),e.details.live?this.bwEstimator.update(r.abrEwmaSlowLive,r.abrEwmaFastLive):this.bwEstimator.update(r.abrEwmaSlowVoD,r.abrEwmaFastVoD)},e.onFragLoaded=function(t,e){var r=e.frag,i=e.part,n=i?i.stats:r.stats;if(r.type===Fe&&this.bwEstimator.sampleTTFB(n.loading.first-n.loading.start),!this.ignoreFragment(r)){if(this.clearTimer(),r.level===this._nextAutoLevel&&(this._nextAutoLevel=-1),this.firstSelection=-1,this.hls.config.abrMaxWithRealBitrate){var a=i?i.duration:r.duration,s=this.hls.levels[r.level],o=(s.loaded?s.loaded.bytes:0)+n.loaded,l=(s.loaded?s.loaded.duration:0)+a;s.loaded={bytes:o,duration:l},s.realBitrate=Math.round(8*o/l)}if(r.bitrateTest){var u={stats:n,frag:r,part:i,id:r.type};this.onFragBuffered(S.FRAG_BUFFERED,u),r.bitrateTest=!1}else this.lastLoadedFragLevel=r.level}},e.onFragBuffered=function(t,e){var r=e.frag,i=e.part,n=null!=i&&i.stats.loaded?i.stats:r.stats;if(!n.aborted&&!this.ignoreFragment(r)){var a=n.parsing.end-n.loading.start-Math.min(n.loading.first-n.loading.start,this.bwEstimator.getEstimateTTFB());this.bwEstimator.sample(a,n.loaded),n.bwEstimate=this.getBwEstimate(),r.bitrateTest?this.bitrateTestDelay=a/1e3:this.bitrateTestDelay=0}},e.ignoreFragment=function(t){return t.type!==Fe||"initSegment"===t.sn},e.clearTimer=function(){this.timer>-1&&(self.clearInterval(this.timer),this.timer=-1)},e.getAutoLevelKey=function(){return this.getBwEstimate()+"_"+this.getStarvationDelay().toFixed(2)},e.getNextABRAutoLevel=function(){var t=this.fragCurrent,e=this.partCurrent,r=this.hls,i=r.maxAutoLevel,n=r.config,a=r.minAutoLevel,s=e?e.duration:t?t.duration:0,o=this.getBwEstimate(),l=this.getStarvationDelay(),u=n.abrBandWidthFactor,h=n.abrBandWidthUpFactor;if(l){var d=this.findBestLevel(o,a,i,l,0,u,h);if(d>=0)return d}var c=s?Math.min(s,n.maxStarvationDelay):n.maxStarvationDelay;if(!l){var f=this.bitrateTestDelay;f&&(c=(s?Math.min(s,n.maxLoadingDelay):n.maxLoadingDelay)-f,w.info("[abr] bitrate test took "+Math.round(1e3*f)+"ms, set first fragment max fetchDuration to "+Math.round(1e3*c)+" ms"),u=h=1)}var g=this.findBestLevel(o,a,i,l,c,u,h);if(w.info("[abr] "+(l?"rebuffering expected":"buffer is empty")+", optimal quality level "+g),g>-1)return g;var v=r.levels[a],m=r.levels[r.loadLevel];return(null==v?void 0:v.bitrate)<(null==m?void 0:m.bitrate)?a:r.loadLevel},e.getStarvationDelay=function(){var t=this.hls,e=t.media;if(!e)return 1/0;var r=e&&0!==e.playbackRate?Math.abs(e.playbackRate):1,i=t.mainForwardBufferInfo;return(i?i.len:0)/r},e.getBwEstimate=function(){return this.bwEstimator.canEstimate()?this.bwEstimator.getEstimate():this.hls.config.abrEwmaDefaultEstimate},e.findBestLevel=function(t,e,r,i,n,a,s){var o,l=this,u=i+n,h=this.lastLoadedFragLevel,d=-1===h?this.hls.firstLevel:h,c=this.fragCurrent,f=this.partCurrent,g=this.hls,v=g.levels,m=g.allAudioTracks,p=g.loadLevel,E=g.config;if(1===v.length)return 0;var T,S=v[d],L=!(null==S||null==(o=S.details)||!o.live),A=-1===p||-1===h,R="SDR",b=(null==S?void 0:S.frameRate)||0,k=E.audioPreference,D=E.videoPreference,I=this.audioTracksByGroup||(this.audioTracksByGroup=function(t){return t.reduce((function(t,e){var r=t.groups[e.groupId];r||(r=t.groups[e.groupId]={tracks:[],channels:{2:0},hasDefault:!1,hasAutoSelect:!1}),r.tracks.push(e);var i=e.channels||"2";return r.channels[i]=(r.channels[i]||0)+1,r.hasDefault=r.hasDefault||e.default,r.hasAutoSelect=r.hasAutoSelect||e.autoselect,r.hasDefault&&(t.hasDefaultAudio=!0),r.hasAutoSelect&&(t.hasAutoSelectAudio=!0),t}),{hasDefaultAudio:!1,hasAutoSelectAudio:!1,groups:{}})}(m));if(A){if(-1!==this.firstSelection)return this.firstSelection;var C=this.codecTiers||(this.codecTiers=function(t,e,r,i){return t.slice(r,i+1).reduce((function(t,r){if(!r.codecSet)return t;var i=r.audioGroups,n=t[r.codecSet];n||(t[r.codecSet]=n={minBitrate:1/0,minHeight:1/0,minFramerate:1/0,maxScore:0,videoRanges:{SDR:0},channels:{2:0},hasDefaultAudio:!i,fragmentError:0}),n.minBitrate=Math.min(n.minBitrate,r.bitrate);var a=Math.min(r.height,r.width);return n.minHeight=Math.min(n.minHeight,a),n.minFramerate=Math.min(n.minFramerate,r.frameRate),n.maxScore=Math.max(n.maxScore,r.score),n.fragmentError+=r.fragmentError,n.videoRanges[r.videoRange]=(n.videoRanges[r.videoRange]||0)+1,i&&i.forEach((function(t){if(t){var r=e.groups[t];r&&(n.hasDefaultAudio=n.hasDefaultAudio||e.hasDefaultAudio?r.hasDefault:r.hasAutoSelect||!e.hasDefaultAudio&&!e.hasAutoSelectAudio,Object.keys(r.channels).forEach((function(t){n.channels[t]=(n.channels[t]||0)+r.channels[t]})))}})),t}),{})}(v,I,e,r)),_=function(t,e,r,i,n){for(var a=Object.keys(t),s=null==i?void 0:i.channels,o=null==i?void 0:i.audioCodec,l=s&&2===parseInt(s),u=!0,h=!1,d=1/0,c=1/0,f=1/0,g=0,v=[],m=Gr(e,n),p=m.preferHDR,E=m.allowedVideoRanges,T=function(){var e=t[a[S]];u=e.channels[2]>0,d=Math.min(d,e.minHeight),c=Math.min(c,e.minFramerate),f=Math.min(f,e.minBitrate);var r=E.filter((function(t){return e.videoRanges[t]>0}));r.length>0&&(h=!0,v=r)},S=a.length;S--;)T();d=y(d)?d:0,c=y(c)?c:0;var L=Math.max(1080,d),A=Math.max(30,c);return f=y(f)?f:r,r=Math.max(f,r),h||(e=void 0,v=[]),{codecSet:a.reduce((function(e,i){var n=t[i];if(i===e)return e;if(n.minBitrate>r)return Kr(i,"min bitrate of "+n.minBitrate+" > current estimate of "+r),e;if(!n.hasDefaultAudio)return Kr(i,"no renditions with default or auto-select sound found"),e;if(o&&i.indexOf(o.substring(0,4))%5!=0)return Kr(i,'audio codec preference "'+o+'" not found'),e;if(s&&!l){if(!n.channels[s])return Kr(i,"no renditions with "+s+" channel sound found (channels options: "+Object.keys(n.channels)+")"),e}else if((!o||l)&&u&&0===n.channels[2])return Kr(i,"no renditions with stereo sound found"),e;return n.minHeight>L?(Kr(i,"min resolution of "+n.minHeight+" > maximum of "+L),e):n.minFramerate>A?(Kr(i,"min framerate of "+n.minFramerate+" > maximum of "+A),e):v.some((function(t){return n.videoRanges[t]>0}))?n.maxScore<g?(Kr(i,"max score of "+n.maxScore+" < selected max of "+g),e):e&&(ce(i)>=ce(e)||n.fragmentError>t[e].fragmentError)?e:(g=n.maxScore,i):(Kr(i,"no variants with VIDEO-RANGE of "+JSON.stringify(v)+" found"),e)}),void 0),videoRanges:v,preferHDR:p,minFramerate:c,minBitrate:f}}(C,R,t,k,D),x=_.codecSet,P=_.videoRanges,F=_.minFramerate,M=_.minBitrate,O=_.preferHDR;T=x,R=O?P[P.length-1]:P[0],b=F,t=Math.max(t,M),w.log("[abr] picked start tier "+JSON.stringify(_))}else T=null==S?void 0:S.codecSet,R=null==S?void 0:S.videoRange;for(var N,U=f?f.duration:c?c.duration:0,B=this.bwEstimator.getEstimateTTFB()/1e3,G=[],K=function(){var e,o=v[H],c=H>d;if(!o)return 0;if(E.useMediaCapabilities&&!o.supportedResult&&!o.supportedPromise){var g=navigator.mediaCapabilities;"function"==typeof(null==g?void 0:g.decodingInfo)&&function(t,e,r,i,n,a){var s=t.audioCodec?t.audioGroups:null,o=null==a?void 0:a.audioCodec,l=null==a?void 0:a.channels,u=l?parseInt(l):o?1/0:2,h=null;if(null!=s&&s.length)try{h=1===s.length&&s[0]?e.groups[s[0]].channels:s.reduce((function(t,r){if(r){var i=e.groups[r];if(!i)throw new Error("Audio track group "+r+" not found");Object.keys(i.channels).forEach((function(e){t[e]=(t[e]||0)+i.channels[e]}))}return t}),{2:0})}catch(t){return!0}return void 0!==t.videoCodec&&(t.width>1920&&t.height>1088||t.height>1920&&t.width>1088||t.frameRate>Math.max(i,30)||"SDR"!==t.videoRange&&t.videoRange!==r||t.bitrate>Math.max(n,8e6))||!!h&&y(u)&&Object.keys(h).some((function(t){return parseInt(t)>u}))}(o,I,R,b,t,k)?(o.supportedPromise=Br(o,I,g),o.supportedPromise.then((function(t){if(l.hls){o.supportedResult=t;var e=l.hls.levels,r=e.indexOf(o);t.error?w.warn('[abr] MediaCapabilities decodingInfo error: "'+t.error+'" for level '+r+" "+JSON.stringify(t)):t.supported||(w.warn("[abr] Unsupported MediaCapabilities decodingInfo result for level "+r+" "+JSON.stringify(t)),r>-1&&e.length>1&&(w.log("[abr] Removing unsupported level "+r),l.hls.removeLevel(r)))}}))):o.supportedResult=Nr}if(T&&o.codecSet!==T||R&&o.videoRange!==R||c&&b>o.frameRate||!c&&b>0&&b<o.frameRate||o.supportedResult&&(null==(e=o.supportedResult.decodingInfoResults)||!e[0].smooth))return G.push(H),0;var m,D=o.details,C=(f?null==D?void 0:D.partTarget:null==D?void 0:D.averagetargetduration)||U;m=c?s*t:a*t;var _=U&&i>=2*U&&0===n?v[H].averageBitrate:v[H].maxBitrate,x=l.getTimeToLoadFrag(B,m,_*C,void 0===D);if(m>=_&&(H===h||0===o.loadError&&0===o.fragmentError)&&(x<=B||!y(x)||L&&!l.bitrateTestDelay||x<u)){var P=l.forcedAutoLevel;return H===p||-1!==P&&P===p||(G.length&&w.trace("[abr] Skipped level(s) "+G.join(",")+" of "+r+' max with CODECS and VIDEO-RANGE:"'+v[G[0]].codecs+'" '+v[G[0]].videoRange+'; not compatible with "'+S.codecs+'" '+R),w.info("[abr] switch candidate:"+d+"->"+H+" adjustedbw("+Math.round(m)+")-bitrate="+Math.round(m-_)+" ttfb:"+B.toFixed(1)+" avgDuration:"+C.toFixed(1)+" maxFetchDuration:"+u.toFixed(1)+" fetchDuration:"+x.toFixed(1)+" firstSelection:"+A+" codecSet:"+T+" videoRange:"+R+" hls.loadLevel:"+p)),A&&(l.firstSelection=H),{v:H}}},H=r;H>=e;H--)if(0!==(N=K())&&N)return N.v;return-1},s(t,[{key:"firstAutoLevel",get:function(){var t=this.hls,e=t.maxAutoLevel,r=t.minAutoLevel,i=this.getBwEstimate(),n=this.hls.config.maxStarvationDelay,a=this.findBestLevel(i,r,e,0,n,1,1);if(a>-1)return a;var s=this.hls.firstLevel,o=Math.min(Math.max(s,r),e);return w.warn("[abr] Could not find best starting auto level. Defaulting to first in playlist "+s+" clamped to "+o),o}},{key:"forcedAutoLevel",get:function(){return this.nextAutoLevelKey?-1:this._nextAutoLevel}},{key:"nextAutoLevel",get:function(){var t=this.forcedAutoLevel,e=this.bwEstimator.canEstimate(),r=this.lastLoadedFragLevel>-1;if(!(-1===t||e&&r&&this.nextAutoLevelKey!==this.getAutoLevelKey()))return t;var i=e&&r?this.getNextABRAutoLevel():this.firstAutoLevel;if(-1!==t){var n=this.hls.levels;if(n.length>Math.max(t,i)&&n[t].loadError<=n[i].loadError)return t}return this._nextAutoLevel=i,this.nextAutoLevelKey=this.getAutoLevelKey(),i},set:function(t){var e=this.hls,r=e.maxAutoLevel,i=e.minAutoLevel,n=Math.min(Math.max(t,i),r);this._nextAutoLevel!==n&&(this.nextAutoLevelKey="",this._nextAutoLevel=n)}}]),t}(),qr=function(){function t(){this._boundTick=void 0,this._tickTimer=null,this._tickInterval=null,this._tickCallCount=0,this._boundTick=this.tick.bind(this)}var e=t.prototype;return e.destroy=function(){this.onHandlerDestroying(),this.onHandlerDestroyed()},e.onHandlerDestroying=function(){this.clearNextTick(),this.clearInterval()},e.onHandlerDestroyed=function(){},e.hasInterval=function(){return!!this._tickInterval},e.hasNextTick=function(){return!!this._tickTimer},e.setInterval=function(t){return!this._tickInterval&&(this._tickCallCount=0,this._tickInterval=self.setInterval(this._boundTick,t),!0)},e.clearInterval=function(){return!!this._tickInterval&&(self.clearInterval(this._tickInterval),this._tickInterval=null,!0)},e.clearNextTick=function(){return!!this._tickTimer&&(self.clearTimeout(this._tickTimer),this._tickTimer=null,!0)},e.tick=function(){this._tickCallCount++,1===this._tickCallCount&&(this.doTick(),this._tickCallCount>1&&this.tickImmediate(),this._tickCallCount=0)},e.tickImmediate=function(){this.clearNextTick(),this._tickTimer=self.setTimeout(this._boundTick,0)},e.doTick=function(){},t}(),Xr="NOT_LOADED",zr="APPENDING",Qr="PARTIAL",Jr="OK",$r=function(){function t(t){this.activePartLists=Object.create(null),this.endListFragments=Object.create(null),this.fragments=Object.create(null),this.timeRanges=Object.create(null),this.bufferPadding=.2,this.hls=void 0,this.hasGaps=!1,this.hls=t,this._registerListeners()}var e=t.prototype;return e._registerListeners=function(){var t=this.hls;t.on(S.BUFFER_APPENDED,this.onBufferAppended,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this),t.on(S.FRAG_LOADED,this.onFragLoaded,this)},e._unregisterListeners=function(){var t=this.hls;t.off(S.BUFFER_APPENDED,this.onBufferAppended,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this),t.off(S.FRAG_LOADED,this.onFragLoaded,this)},e.destroy=function(){this._unregisterListeners(),this.fragments=this.activePartLists=this.endListFragments=this.timeRanges=null},e.getAppendedFrag=function(t,e){var r=this.activePartLists[e];if(r)for(var i=r.length;i--;){var n=r[i];if(!n)break;var a=n.end;if(n.start<=t&&null!==a&&t<=a)return n}return this.getBufferedFrag(t,e)},e.getBufferedFrag=function(t,e){for(var r=this.fragments,i=Object.keys(r),n=i.length;n--;){var a=r[i[n]];if((null==a?void 0:a.body.type)===e&&a.buffered){var s=a.body;if(s.start<=t&&t<=s.end)return s}}return null},e.detectEvictedFragments=function(t,e,r,i){var n=this;this.timeRanges&&(this.timeRanges[t]=e);var a=(null==i?void 0:i.fragment.sn)||-1;Object.keys(this.fragments).forEach((function(i){var s=n.fragments[i];if(s&&!(a>=s.body.sn))if(s.buffered||s.loaded){var o=s.range[t];o&&o.time.some((function(t){var r=!n.isTimeBuffered(t.startPTS,t.endPTS,e);return r&&n.removeFragment(s.body),r}))}else s.body.type===r&&n.removeFragment(s.body)}))},e.detectPartialFragments=function(t){var e=this,r=this.timeRanges,i=t.frag,n=t.part;if(r&&"initSegment"!==i.sn){var a=ti(i),s=this.fragments[a];if(!(!s||s.buffered&&i.gap)){var o=!i.relurl;Object.keys(r).forEach((function(t){var a=i.elementaryStreams[t];if(a){var l=r[t],u=o||!0===a.partial;s.range[t]=e.getBufferedTimes(i,n,u,l)}})),s.loaded=null,Object.keys(s.range).length?(s.buffered=!0,(s.body.endList=i.endList||s.body.endList)&&(this.endListFragments[s.body.type]=s),Zr(s)||this.removeParts(i.sn-1,i.type)):this.removeFragment(s.body)}}},e.removeParts=function(t,e){var r=this.activePartLists[e];r&&(this.activePartLists[e]=r.filter((function(e){return e.fragment.sn>=t})))},e.fragBuffered=function(t,e){var r=ti(t),i=this.fragments[r];!i&&e&&(i=this.fragments[r]={body:t,appendedPTS:null,loaded:null,buffered:!1,range:Object.create(null)},t.gap&&(this.hasGaps=!0)),i&&(i.loaded=null,i.buffered=!0)},e.getBufferedTimes=function(t,e,r,i){for(var n={time:[],partial:r},a=t.start,s=t.end,o=t.minEndPTS||s,l=t.maxStartPTS||a,u=0;u<i.length;u++){var h=i.start(u)-this.bufferPadding,d=i.end(u)+this.bufferPadding;if(l>=h&&o<=d){n.time.push({startPTS:Math.max(a,i.start(u)),endPTS:Math.min(s,i.end(u))});break}if(a<d&&s>h){var c=Math.max(a,i.start(u)),f=Math.min(s,i.end(u));f>c&&(n.partial=!0,n.time.push({startPTS:c,endPTS:f}))}else if(s<=h)break}return n},e.getPartialFragment=function(t){var e,r,i,n=null,a=0,s=this.bufferPadding,o=this.fragments;return Object.keys(o).forEach((function(l){var u=o[l];u&&Zr(u)&&(r=u.body.start-s,i=u.body.end+s,t>=r&&t<=i&&(e=Math.min(t-r,i-t),a<=e&&(n=u.body,a=e)))})),n},e.isEndListAppended=function(t){var e=this.endListFragments[t];return void 0!==e&&(e.buffered||Zr(e))},e.getState=function(t){var e=ti(t),r=this.fragments[e];return r?r.buffered?Zr(r)?Qr:Jr:zr:Xr},e.isTimeBuffered=function(t,e,r){for(var i,n,a=0;a<r.length;a++){if(i=r.start(a)-this.bufferPadding,n=r.end(a)+this.bufferPadding,t>=i&&e<=n)return!0;if(e<=i)return!1}return!1},e.onFragLoaded=function(t,e){var r=e.frag,i=e.part;if("initSegment"!==r.sn&&!r.bitrateTest){var n=i?null:e,a=ti(r);this.fragments[a]={body:r,appendedPTS:null,loaded:n,buffered:!1,range:Object.create(null)}}},e.onBufferAppended=function(t,e){var r=this,i=e.frag,n=e.part,a=e.timeRanges;if("initSegment"!==i.sn){var s=i.type;if(n){var o=this.activePartLists[s];o||(this.activePartLists[s]=o=[]),o.push(n)}this.timeRanges=a,Object.keys(a).forEach((function(t){var e=a[t];r.detectEvictedFragments(t,e,s,n)}))}},e.onFragBuffered=function(t,e){this.detectPartialFragments(e)},e.hasFragment=function(t){var e=ti(t);return!!this.fragments[e]},e.hasParts=function(t){var e;return!(null==(e=this.activePartLists[t])||!e.length)},e.removeFragmentsInRange=function(t,e,r,i,n){var a=this;i&&!this.hasGaps||Object.keys(this.fragments).forEach((function(s){var o=a.fragments[s];if(o){var l=o.body;l.type!==r||i&&!l.gap||l.start<e&&l.end>t&&(o.buffered||n)&&a.removeFragment(l)}}))},e.removeFragment=function(t){var e=ti(t);t.stats.loaded=0,t.clearElementaryStreamInfo();var r=this.activePartLists[t.type];if(r){var i=t.sn;this.activePartLists[t.type]=r.filter((function(t){return t.fragment.sn!==i}))}delete this.fragments[e],t.endList&&delete this.endListFragments[t.type]},e.removeAllFragments=function(){this.fragments=Object.create(null),this.endListFragments=Object.create(null),this.activePartLists=Object.create(null),this.hasGaps=!1},t}();function Zr(t){var e,r,i;return t.buffered&&(t.body.gap||(null==(e=t.range.video)?void 0:e.partial)||(null==(r=t.range.audio)?void 0:r.partial)||(null==(i=t.range.audiovideo)?void 0:i.partial))}function ti(t){return t.type+"_"+t.level+"_"+t.sn}var ei={length:0,start:function(){return 0},end:function(){return 0}},ri=function(){function t(){}return t.isBuffered=function(e,r){try{if(e)for(var i=t.getBuffered(e),n=0;n<i.length;n++)if(r>=i.start(n)&&r<=i.end(n))return!0}catch(t){}return!1},t.bufferInfo=function(e,r,i){try{if(e){var n,a=t.getBuffered(e),s=[];for(n=0;n<a.length;n++)s.push({start:a.start(n),end:a.end(n)});return this.bufferedInfo(s,r,i)}}catch(t){}return{len:0,start:r,end:r,nextStart:void 0}},t.bufferedInfo=function(t,e,r){e=Math.max(0,e),t.sort((function(t,e){var r=t.start-e.start;return r||e.end-t.end}));var i=[];if(r)for(var n=0;n<t.length;n++){var a=i.length;if(a){var s=i[a-1].end;t[n].start-s<r?t[n].end>s&&(i[a-1].end=t[n].end):i.push(t[n])}else i.push(t[n])}else i=t;for(var o,l=0,u=e,h=e,d=0;d<i.length;d++){var c=i[d].start,f=i[d].end;if(e+r>=c&&e<f)u=c,l=(h=f)-e;else if(e+r<c){o=c;break}}return{len:l,start:u||0,end:h||0,nextStart:o}},t.getBuffered=function(t){try{return t.buffered}catch(t){return w.log("failed to get media.buffered",t),ei}},t}(),ii=function(t,e,r,i,n,a){void 0===i&&(i=0),void 0===n&&(n=-1),void 0===a&&(a=!1),this.level=void 0,this.sn=void 0,this.part=void 0,this.id=void 0,this.size=void 0,this.partial=void 0,this.transmuxing={start:0,executeStart:0,executeEnd:0,end:0},this.buffering={audio:{start:0,executeStart:0,executeEnd:0,end:0},video:{start:0,executeStart:0,executeEnd:0,end:0},audiovideo:{start:0,executeStart:0,executeEnd:0,end:0}},this.level=t,this.sn=e,this.id=r,this.size=i,this.part=n,this.partial=a};function ni(t,e){for(var r=0,i=t.length;r<i;r++){var n;if((null==(n=t[r])?void 0:n.cc)===e)return t[r]}return null}function ai(t,e){if(t){var r=t.start+e;t.start=t.startPTS=r,t.endPTS=r+t.duration}}function si(t,e){for(var r=e.fragments,i=0,n=r.length;i<n;i++)ai(r[i],t);e.fragmentHint&&ai(e.fragmentHint,t),e.alignedSliding=!0}function oi(t,e,r){e&&(function(t,e,r){if(function(t,e,r){return!(!e||!(r.endCC>r.startCC||t&&t.cc<r.startCC))}(t,r,e)){var i=function(t,e){var r=t.fragments,i=e.fragments;if(i.length&&r.length){var n=ni(r,i[0].cc);if(n&&(!n||n.startPTS))return n;w.log("No frag in previous level to align on")}else w.log("No fragments to align")}(r,e);i&&y(i.start)&&(w.log("Adjusting PTS using last level due to CC increase within current level "+e.url),si(i.start,e))}}(t,r,e),!r.alignedSliding&&e&&li(r,e),r.alignedSliding||!e||r.skippedSegments||cr(e,r))}function li(t,e){if(t.hasProgramDateTime&&e.hasProgramDateTime){var r=t.fragments,i=e.fragments;if(r.length&&i.length){var n,a,s=Math.min(e.endCC,t.endCC);e.startCC<s&&t.startCC<s&&(n=ni(i,s),a=ni(r,s)),n&&a||(a=ni(r,(n=i[Math.floor(i.length/2)]).cc)||r[Math.floor(r.length/2)]);var o=n.programDateTime,l=a.programDateTime;o&&l&&si((l-o)/1e3-(a.start-n.start),t)}}}var ui=Math.pow(2,17),hi=function(){function t(t){this.config=void 0,this.loader=null,this.partLoadTimeout=-1,this.config=t}var e=t.prototype;return e.destroy=function(){this.loader&&(this.loader.destroy(),this.loader=null)},e.abort=function(){this.loader&&this.loader.abort()},e.load=function(t,e){var r=this,n=t.url;if(!n)return Promise.reject(new fi({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_ERROR,fatal:!1,frag:t,error:new Error("Fragment does not have a "+(n?"part list":"url")),networkDetails:null}));this.abort();var a=this.config,s=a.fLoader,o=a.loader;return new Promise((function(l,u){if(r.loader&&r.loader.destroy(),t.gap){if(t.tagList.some((function(t){return"GAP"===t[0]})))return void u(ci(t));t.gap=!1}var h=r.loader=t.loader=s?new s(a):new o(a),d=di(t),c=Tr(a.fragLoadPolicy.default),f={loadPolicy:c,timeout:c.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0,highWaterMark:"initSegment"===t.sn?1/0:ui};t.stats=h.stats,h.load(d,f,{onSuccess:function(e,i,n,a){r.resetLoader(t,h);var s=e.data;n.resetIV&&t.decryptdata&&(t.decryptdata.iv=new Uint8Array(s.slice(0,16)),s=s.slice(16)),l({frag:t,part:null,payload:s,networkDetails:a})},onError:function(e,a,s,o){r.resetLoader(t,h),u(new fi({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_ERROR,fatal:!1,frag:t,response:i({url:n,data:void 0},e),error:new Error("HTTP Error "+e.code+" "+e.text),networkDetails:s,stats:o}))},onAbort:function(e,i,n){r.resetLoader(t,h),u(new fi({type:L.NETWORK_ERROR,details:A.INTERNAL_ABORTED,fatal:!1,frag:t,error:new Error("Aborted"),networkDetails:n,stats:e}))},onTimeout:function(e,i,n){r.resetLoader(t,h),u(new fi({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_TIMEOUT,fatal:!1,frag:t,error:new Error("Timeout after "+f.timeout+"ms"),networkDetails:n,stats:e}))},onProgress:function(r,i,n,a){e&&e({frag:t,part:null,payload:n,networkDetails:a})}})}))},e.loadPart=function(t,e,r){var n=this;this.abort();var a=this.config,s=a.fLoader,o=a.loader;return new Promise((function(l,u){if(n.loader&&n.loader.destroy(),t.gap||e.gap)u(ci(t,e));else{var h=n.loader=t.loader=s?new s(a):new o(a),d=di(t,e),c=Tr(a.fragLoadPolicy.default),f={loadPolicy:c,timeout:c.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0,highWaterMark:ui};e.stats=h.stats,h.load(d,f,{onSuccess:function(i,a,s,o){n.resetLoader(t,h),n.updateStatsFromPart(t,e);var u={frag:t,part:e,payload:i.data,networkDetails:o};r(u),l(u)},onError:function(r,a,s,o){n.resetLoader(t,h),u(new fi({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_ERROR,fatal:!1,frag:t,part:e,response:i({url:d.url,data:void 0},r),error:new Error("HTTP Error "+r.code+" "+r.text),networkDetails:s,stats:o}))},onAbort:function(r,i,a){t.stats.aborted=e.stats.aborted,n.resetLoader(t,h),u(new fi({type:L.NETWORK_ERROR,details:A.INTERNAL_ABORTED,fatal:!1,frag:t,part:e,error:new Error("Aborted"),networkDetails:a,stats:r}))},onTimeout:function(r,i,a){n.resetLoader(t,h),u(new fi({type:L.NETWORK_ERROR,details:A.FRAG_LOAD_TIMEOUT,fatal:!1,frag:t,part:e,error:new Error("Timeout after "+f.timeout+"ms"),networkDetails:a,stats:r}))}})}}))},e.updateStatsFromPart=function(t,e){var r=t.stats,i=e.stats,n=i.total;if(r.loaded+=i.loaded,n){var a=Math.round(t.duration/e.duration),s=Math.min(Math.round(r.loaded/n),a),o=(a-s)*Math.round(r.loaded/s);r.total=r.loaded+o}else r.total=Math.max(r.loaded,r.total);var l=r.loading,u=i.loading;l.start?l.first+=u.first-u.start:(l.start=u.start,l.first=u.first),l.end=u.end},e.resetLoader=function(t,e){t.loader=null,this.loader===e&&(self.clearTimeout(this.partLoadTimeout),this.loader=null),e.destroy()},t}();function di(t,e){void 0===e&&(e=null);var r=e||t,i={frag:t,part:e,responseType:"arraybuffer",url:r.url,headers:{},rangeStart:0,rangeEnd:0},n=r.byteRangeStartOffset,a=r.byteRangeEndOffset;if(y(n)&&y(a)){var s,o=n,l=a;if("initSegment"===t.sn&&"AES-128"===(null==(s=t.decryptdata)?void 0:s.method)){var u=a-n;u%16&&(l=a+(16-u%16)),0!==n&&(i.resetIV=!0,o=n-16)}i.rangeStart=o,i.rangeEnd=l}return i}function ci(t,e){var r=new Error("GAP "+(t.gap?"tag":"attribute")+" found"),i={type:L.MEDIA_ERROR,details:A.FRAG_GAP,fatal:!1,frag:t,error:r,networkDetails:null};return e&&(i.part=e),(e||t).stats.aborted=!0,new fi(i)}var fi=function(t){function e(e){var r;return(r=t.call(this,e.error.message)||this).data=void 0,r.data=e,r}return l(e,t),e}(c(Error)),gi=function(){function t(t,e){this.subtle=void 0,this.aesIV=void 0,this.subtle=t,this.aesIV=e}return t.prototype.decrypt=function(t,e){return this.subtle.decrypt({name:"AES-CBC",iv:this.aesIV},e,t)},t}(),vi=function(){function t(t,e){this.subtle=void 0,this.key=void 0,this.subtle=t,this.key=e}return t.prototype.expandKey=function(){return this.subtle.importKey("raw",this.key,{name:"AES-CBC"},!1,["encrypt","decrypt"])},t}(),mi=function(){function t(){this.rcon=[0,1,2,4,8,16,32,64,128,27,54],this.subMix=[new Uint32Array(256),new Uint32Array(256),new Uint32Array(256),new Uint32Array(256)],this.invSubMix=[new Uint32Array(256),new Uint32Array(256),new Uint32Array(256),new Uint32Array(256)],this.sBox=new Uint32Array(256),this.invSBox=new Uint32Array(256),this.key=new Uint32Array(0),this.ksRows=0,this.keySize=0,this.keySchedule=void 0,this.invKeySchedule=void 0,this.initTable()}var e=t.prototype;return e.uint8ArrayToUint32Array_=function(t){for(var e=new DataView(t),r=new Uint32Array(4),i=0;i<4;i++)r[i]=e.getUint32(4*i);return r},e.initTable=function(){var t=this.sBox,e=this.invSBox,r=this.subMix,i=r[0],n=r[1],a=r[2],s=r[3],o=this.invSubMix,l=o[0],u=o[1],h=o[2],d=o[3],c=new Uint32Array(256),f=0,g=0,v=0;for(v=0;v<256;v++)c[v]=v<128?v<<1:v<<1^283;for(v=0;v<256;v++){var m=g^g<<1^g<<2^g<<3^g<<4;m=m>>>8^255&m^99,t[f]=m,e[m]=f;var p=c[f],y=c[p],E=c[y],T=257*c[m]^16843008*m;i[f]=T<<24|T>>>8,n[f]=T<<16|T>>>16,a[f]=T<<8|T>>>24,s[f]=T,T=16843009*E^65537*y^257*p^16843008*f,l[m]=T<<24|T>>>8,u[m]=T<<16|T>>>16,h[m]=T<<8|T>>>24,d[m]=T,f?(f=p^c[c[c[E^p]]],g^=c[c[g]]):f=g=1}},e.expandKey=function(t){for(var e=this.uint8ArrayToUint32Array_(t),r=!0,i=0;i<e.length&&r;)r=e[i]===this.key[i],i++;if(!r){this.key=e;var n=this.keySize=e.length;if(4!==n&&6!==n&&8!==n)throw new Error("Invalid aes key size="+n);var a,s,o,l,u=this.ksRows=4*(n+6+1),h=this.keySchedule=new Uint32Array(u),d=this.invKeySchedule=new Uint32Array(u),c=this.sBox,f=this.rcon,g=this.invSubMix,v=g[0],m=g[1],p=g[2],y=g[3];for(a=0;a<u;a++)a<n?o=h[a]=e[a]:(l=o,a%n==0?(l=c[(l=l<<8|l>>>24)>>>24]<<24|c[l>>>16&255]<<16|c[l>>>8&255]<<8|c[255&l],l^=f[a/n|0]<<24):n>6&&a%n==4&&(l=c[l>>>24]<<24|c[l>>>16&255]<<16|c[l>>>8&255]<<8|c[255&l]),h[a]=o=(h[a-n]^l)>>>0);for(s=0;s<u;s++)a=u-s,l=3&s?h[a]:h[a-4],d[s]=s<4||a<=4?l:v[c[l>>>24]]^m[c[l>>>16&255]]^p[c[l>>>8&255]]^y[c[255&l]],d[s]=d[s]>>>0}},e.networkToHostOrderSwap=function(t){return t<<24|(65280&t)<<8|(16711680&t)>>8|t>>>24},e.decrypt=function(t,e,r){for(var i,n,a,s,o,l,u,h,d,c,f,g,v,m,p=this.keySize+6,y=this.invKeySchedule,E=this.invSBox,T=this.invSubMix,S=T[0],L=T[1],A=T[2],R=T[3],b=this.uint8ArrayToUint32Array_(r),k=b[0],D=b[1],I=b[2],w=b[3],C=new Int32Array(t),_=new Int32Array(C.length),x=this.networkToHostOrderSwap;e<C.length;){for(d=x(C[e]),c=x(C[e+1]),f=x(C[e+2]),g=x(C[e+3]),o=d^y[0],l=g^y[1],u=f^y[2],h=c^y[3],v=4,m=1;m<p;m++)i=S[o>>>24]^L[l>>16&255]^A[u>>8&255]^R[255&h]^y[v],n=S[l>>>24]^L[u>>16&255]^A[h>>8&255]^R[255&o]^y[v+1],a=S[u>>>24]^L[h>>16&255]^A[o>>8&255]^R[255&l]^y[v+2],s=S[h>>>24]^L[o>>16&255]^A[l>>8&255]^R[255&u]^y[v+3],o=i,l=n,u=a,h=s,v+=4;i=E[o>>>24]<<24^E[l>>16&255]<<16^E[u>>8&255]<<8^E[255&h]^y[v],n=E[l>>>24]<<24^E[u>>16&255]<<16^E[h>>8&255]<<8^E[255&o]^y[v+1],a=E[u>>>24]<<24^E[h>>16&255]<<16^E[o>>8&255]<<8^E[255&l]^y[v+2],s=E[h>>>24]<<24^E[o>>16&255]<<16^E[l>>8&255]<<8^E[255&u]^y[v+3],_[e]=x(i^k),_[e+1]=x(s^D),_[e+2]=x(a^I),_[e+3]=x(n^w),k=d,D=c,I=f,w=g,e+=4}return _.buffer},t}(),pi=function(){function t(t,e){var r=(void 0===e?{}:e).removePKCS7Padding,i=void 0===r||r;if(this.logEnabled=!0,this.removePKCS7Padding=void 0,this.subtle=null,this.softwareDecrypter=null,this.key=null,this.fastAesKey=null,this.remainderData=null,this.currentIV=null,this.currentResult=null,this.useSoftware=void 0,this.useSoftware=t.enableSoftwareAES,this.removePKCS7Padding=i,i)try{var n=self.crypto;n&&(this.subtle=n.subtle||n.webkitSubtle)}catch(t){}this.useSoftware=!this.subtle}var e=t.prototype;return e.destroy=function(){this.subtle=null,this.softwareDecrypter=null,this.key=null,this.fastAesKey=null,this.remainderData=null,this.currentIV=null,this.currentResult=null},e.isSync=function(){return this.useSoftware},e.flush=function(){var t=this.currentResult,e=this.remainderData;if(!t||e)return this.reset(),null;var r,i,n,a=new Uint8Array(t);return this.reset(),this.removePKCS7Padding?(i=(r=a).byteLength,(n=i&&new DataView(r.buffer).getUint8(i-1))?lt(r,0,i-n):r):a},e.reset=function(){this.currentResult=null,this.currentIV=null,this.remainderData=null,this.softwareDecrypter&&(this.softwareDecrypter=null)},e.decrypt=function(t,e,r){var i=this;return this.useSoftware?new Promise((function(n,a){i.softwareDecrypt(new Uint8Array(t),e,r);var s=i.flush();s?n(s.buffer):a(new Error("[softwareDecrypt] Failed to decrypt data"))})):this.webCryptoDecrypt(new Uint8Array(t),e,r)},e.softwareDecrypt=function(t,e,r){var i=this.currentIV,n=this.currentResult,a=this.remainderData;this.logOnce("JS AES decrypt"),a&&(t=Wt(a,t),this.remainderData=null);var s=this.getValidChunk(t);if(!s.length)return null;i&&(r=i);var o=this.softwareDecrypter;o||(o=this.softwareDecrypter=new mi),o.expandKey(e);var l=n;return this.currentResult=o.decrypt(s.buffer,0,r),this.currentIV=lt(s,-16).buffer,l||null},e.webCryptoDecrypt=function(t,e,r){var i=this;if(this.key!==e||!this.fastAesKey){if(!this.subtle)return Promise.resolve(this.onWebCryptoError(t,e,r));this.key=e,this.fastAesKey=new vi(this.subtle,e)}return this.fastAesKey.expandKey().then((function(e){return i.subtle?(i.logOnce("WebCrypto AES decrypt"),new gi(i.subtle,new Uint8Array(r)).decrypt(t.buffer,e)):Promise.reject(new Error("web crypto not initialized"))})).catch((function(n){return w.warn("[decrypter]: WebCrypto Error, disable WebCrypto API, "+n.name+": "+n.message),i.onWebCryptoError(t,e,r)}))},e.onWebCryptoError=function(t,e,r){this.useSoftware=!0,this.logEnabled=!0,this.softwareDecrypt(t,e,r);var i=this.flush();if(i)return i.buffer;throw new Error("WebCrypto and softwareDecrypt: failed to decrypt data")},e.getValidChunk=function(t){var e=t,r=t.length-t.length%16;return r!==t.length&&(e=lt(t,0,r),this.remainderData=lt(t,r)),e},e.logOnce=function(t){this.logEnabled&&(w.log("[decrypter]: "+t),this.logEnabled=!1)},t}(),yi=function(t){for(var e="",r=t.length,i=0;i<r;i++)e+="["+t.start(i).toFixed(3)+"-"+t.end(i).toFixed(3)+"]";return e},Ei="STOPPED",Ti="IDLE",Si="KEY_LOADING",Li="FRAG_LOADING",Ai="FRAG_LOADING_WAITING_RETRY",Ri="WAITING_TRACK",bi="PARSING",ki="PARSED",Di="ENDED",Ii="ERROR",wi="WAITING_INIT_PTS",Ci="WAITING_LEVEL",_i=function(t){function e(e,r,i,n,a){var s;return(s=t.call(this)||this).hls=void 0,s.fragPrevious=null,s.fragCurrent=null,s.fragmentTracker=void 0,s.transmuxer=null,s._state=Ei,s.playlistType=void 0,s.media=null,s.mediaBuffer=null,s.config=void 0,s.bitrateTest=!1,s.lastCurrentTime=0,s.nextLoadPosition=0,s.startPosition=0,s.startTimeOffset=null,s.loadedmetadata=!1,s.retryDate=0,s.levels=null,s.fragmentLoader=void 0,s.keyLoader=void 0,s.levelLastLoaded=null,s.startFragRequested=!1,s.decrypter=void 0,s.initPTS=[],s.onvseeking=null,s.onvended=null,s.logPrefix="",s.log=void 0,s.warn=void 0,s.playlistType=a,s.logPrefix=n,s.log=w.log.bind(w,n+":"),s.warn=w.warn.bind(w,n+":"),s.hls=e,s.fragmentLoader=new hi(e.config),s.keyLoader=i,s.fragmentTracker=r,s.config=e.config,s.decrypter=new pi(e.config),e.on(S.MANIFEST_LOADED,s.onManifestLoaded,function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(s)),s}l(e,t);var r=e.prototype;return r.doTick=function(){this.onTickEnd()},r.onTickEnd=function(){},r.startLoad=function(t){},r.stopLoad=function(){this.fragmentLoader.abort(),this.keyLoader.abort(this.playlistType);var t=this.fragCurrent;null!=t&&t.loader&&(t.abortRequests(),this.fragmentTracker.removeFragment(t)),this.resetTransmuxer(),this.fragCurrent=null,this.fragPrevious=null,this.clearInterval(),this.clearNextTick(),this.state=Ei},r._streamEnded=function(t,e){if(e.live||t.nextStart||!t.end||!this.media)return!1;var r=e.partList;if(null!=r&&r.length){var i=r[r.length-1];return ri.isBuffered(this.media,i.start+i.duration/2)}var n=e.fragments[e.fragments.length-1].type;return this.fragmentTracker.isEndListAppended(n)},r.getLevelDetails=function(){var t;if(this.levels&&null!==this.levelLastLoaded)return null==(t=this.levelLastLoaded)?void 0:t.details},r.onMediaAttached=function(t,e){var r=this.media=this.mediaBuffer=e.media;this.onvseeking=this.onMediaSeeking.bind(this),this.onvended=this.onMediaEnded.bind(this),r.addEventListener("seeking",this.onvseeking),r.addEventListener("ended",this.onvended);var i=this.config;this.levels&&i.autoStartLoad&&this.state===Ei&&this.startLoad(i.startPosition)},r.onMediaDetaching=function(){var t=this.media;null!=t&&t.ended&&(this.log("MSE detaching and video ended, reset startPosition"),this.startPosition=this.lastCurrentTime=0),t&&this.onvseeking&&this.onvended&&(t.removeEventListener("seeking",this.onvseeking),t.removeEventListener("ended",this.onvended),this.onvseeking=this.onvended=null),this.keyLoader&&this.keyLoader.detach(),this.media=this.mediaBuffer=null,this.loadedmetadata=!1,this.fragmentTracker.removeAllFragments(),this.stopLoad()},r.onMediaSeeking=function(){var t=this.config,e=this.fragCurrent,r=this.media,i=this.mediaBuffer,n=this.state,a=r?r.currentTime:0,s=ri.bufferInfo(i||r,a,t.maxBufferHole);if(this.log("media seeking to "+(y(a)?a.toFixed(3):a)+", state: "+n),this.state===Di)this.resetLoadingState();else if(e){var o=t.maxFragLookUpTolerance,l=e.start-o,u=e.start+e.duration+o;if(!s.len||u<s.start||l>s.end){var h=a>u;(a<l||h)&&(h&&e.loader&&(this.log("seeking outside of buffer while fragment load in progress, cancel fragment load"),e.abortRequests(),this.resetLoadingState()),this.fragPrevious=null)}}r&&(this.fragmentTracker.removeFragmentsInRange(a,1/0,this.playlistType,!0),this.lastCurrentTime=a),this.loadedmetadata||s.len||(this.nextLoadPosition=this.startPosition=a),this.tickImmediate()},r.onMediaEnded=function(){this.startPosition=this.lastCurrentTime=0},r.onManifestLoaded=function(t,e){this.startTimeOffset=e.startTimeOffset,this.initPTS=[]},r.onHandlerDestroying=function(){this.hls.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),this.stopLoad(),t.prototype.onHandlerDestroying.call(this),this.hls=null},r.onHandlerDestroyed=function(){this.state=Ei,this.fragmentLoader&&this.fragmentLoader.destroy(),this.keyLoader&&this.keyLoader.destroy(),this.decrypter&&this.decrypter.destroy(),this.hls=this.log=this.warn=this.decrypter=this.keyLoader=this.fragmentLoader=this.fragmentTracker=null,t.prototype.onHandlerDestroyed.call(this)},r.loadFragment=function(t,e,r){this._loadFragForPlayback(t,e,r)},r._loadFragForPlayback=function(t,e,r){var i=this;this._doFragLoad(t,e,r,(function(e){if(i.fragContextChanged(t))return i.warn("Fragment "+t.sn+(e.part?" p: "+e.part.index:"")+" of level "+t.level+" was dropped during download."),void i.fragmentTracker.removeFragment(t);t.stats.chunkCount++,i._handleFragmentLoadProgress(e)})).then((function(e){if(e){var r=i.state;i.fragContextChanged(t)?(r===Li||!i.fragCurrent&&r===bi)&&(i.fragmentTracker.removeFragment(t),i.state=Ti):("payload"in e&&(i.log("Loaded fragment "+t.sn+" of level "+t.level),i.hls.trigger(S.FRAG_LOADED,e)),i._handleFragmentLoadComplete(e))}})).catch((function(e){i.state!==Ei&&i.state!==Ii&&(i.warn("Frag error: "+((null==e?void 0:e.message)||e)),i.resetFragmentLoading(t))}))},r.clearTrackerIfNeeded=function(t){var e,r=this.fragmentTracker;if(r.getState(t)===zr){var i=t.type,n=this.getFwdBufferInfo(this.mediaBuffer,i),a=Math.max(t.duration,n?n.len:this.config.maxBufferLength),s=this.backtrackFragment;(1==(s?t.sn-s.sn:0)||this.reduceMaxBufferLength(a,t.duration))&&r.removeFragment(t)}else 0===(null==(e=this.mediaBuffer)?void 0:e.buffered.length)?r.removeAllFragments():r.hasParts(t.type)&&(r.detectPartialFragments({frag:t,part:null,stats:t.stats,id:t.type}),r.getState(t)===Qr&&r.removeFragment(t))},r.checkLiveUpdate=function(t){if(t.updated&&!t.live){var e=t.fragments[t.fragments.length-1];this.fragmentTracker.detectPartialFragments({frag:e,part:null,stats:e.stats,id:e.type})}t.fragments[0]||(t.deltaUpdateFailed=!0)},r.flushMainBuffer=function(t,e,r){if(void 0===r&&(r=null),t-e){var i={startOffset:t,endOffset:e,type:r};this.hls.trigger(S.BUFFER_FLUSHING,i)}},r._loadInitSegment=function(t,e){var r=this;this._doFragLoad(t,e).then((function(e){if(!e||r.fragContextChanged(t)||!r.levels)throw new Error("init load aborted");return e})).then((function(e){var i=r.hls,n=e.payload,a=t.decryptdata;if(n&&n.byteLength>0&&null!=a&&a.key&&a.iv&&"AES-128"===a.method){var s=self.performance.now();return r.decrypter.decrypt(new Uint8Array(n),a.key.buffer,a.iv.buffer).catch((function(e){throw i.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_DECRYPT_ERROR,fatal:!1,error:e,reason:e.message,frag:t}),e})).then((function(n){var a=self.performance.now();return i.trigger(S.FRAG_DECRYPTED,{frag:t,payload:n,stats:{tstart:s,tdecrypt:a}}),e.payload=n,r.completeInitSegmentLoad(e)}))}return r.completeInitSegmentLoad(e)})).catch((function(e){r.state!==Ei&&r.state!==Ii&&(r.warn(e),r.resetFragmentLoading(t))}))},r.completeInitSegmentLoad=function(t){if(!this.levels)throw new Error("init load aborted, missing levels");var e=t.frag.stats;this.state=Ti,t.frag.data=new Uint8Array(t.payload),e.parsing.start=e.buffering.start=self.performance.now(),e.parsing.end=e.buffering.end=self.performance.now(),this.tick()},r.fragContextChanged=function(t){var e=this.fragCurrent;return!t||!e||t.sn!==e.sn||t.level!==e.level},r.fragBufferedComplete=function(t,e){var r,i,n,a,s=this.mediaBuffer?this.mediaBuffer:this.media;if(this.log("Buffered "+t.type+" sn: "+t.sn+(e?" part: "+e.index:"")+" of "+(this.playlistType===Fe?"level":"track")+" "+t.level+" (frag:["+(null!=(r=t.startPTS)?r:NaN).toFixed(3)+"-"+(null!=(i=t.endPTS)?i:NaN).toFixed(3)+"] > buffer:"+(s?yi(ri.getBuffered(s)):"(detached)")+")"),"initSegment"!==t.sn){var o;if(t.type!==Oe){var l=t.elementaryStreams;if(!Object.keys(l).some((function(t){return!!l[t]})))return void(this.state=Ti)}var u=null==(o=this.levels)?void 0:o[t.level];null!=u&&u.fragmentError&&(this.log("Resetting level fragment error count of "+u.fragmentError+" on frag buffered"),u.fragmentError=0)}this.state=Ti,s&&(!this.loadedmetadata&&t.type==Fe&&s.buffered.length&&(null==(n=this.fragCurrent)?void 0:n.sn)===(null==(a=this.fragPrevious)?void 0:a.sn)&&(this.loadedmetadata=!0,this.seekToStartPos()),this.tick())},r.seekToStartPos=function(){},r._handleFragmentLoadComplete=function(t){var e=this.transmuxer;if(e){var r=t.frag,i=t.part,n=t.partsLoaded,a=!n||0===n.length||n.some((function(t){return!t})),s=new ii(r.level,r.sn,r.stats.chunkCount+1,0,i?i.index:-1,!a);e.flush(s)}},r._handleFragmentLoadProgress=function(t){},r._doFragLoad=function(t,e,r,i){var n,a=this;void 0===r&&(r=null);var s=null==e?void 0:e.details;if(!this.levels||!s)throw new Error("frag load aborted, missing level"+(s?"":" detail")+"s");var o=null;if(!t.encrypted||null!=(n=t.decryptdata)&&n.key?!t.encrypted&&s.encryptedFragments.length&&this.keyLoader.loadClear(t,s.encryptedFragments):(this.log("Loading key for "+t.sn+" of ["+s.startSN+"-"+s.endSN+"], "+("[stream-controller]"===this.logPrefix?"level":"track")+" "+t.level),this.state=Si,this.fragCurrent=t,o=this.keyLoader.load(t).then((function(t){if(!a.fragContextChanged(t.frag))return a.hls.trigger(S.KEY_LOADED,t),a.state===Si&&(a.state=Ti),t})),this.hls.trigger(S.KEY_LOADING,{frag:t}),null===this.fragCurrent&&(o=Promise.reject(new Error("frag load aborted, context changed in KEY_LOADING")))),r=Math.max(t.start,r||0),this.config.lowLatencyMode&&"initSegment"!==t.sn){var l=s.partList;if(l&&i){r>t.end&&s.fragmentHint&&(t=s.fragmentHint);var u=this.getNextPart(l,t,r);if(u>-1){var h,d=l[u];return this.log("Loading part sn: "+t.sn+" p: "+d.index+" cc: "+t.cc+" of playlist ["+s.startSN+"-"+s.endSN+"] parts [0-"+u+"-"+(l.length-1)+"] "+("[stream-controller]"===this.logPrefix?"level":"track")+": "+t.level+", target: "+parseFloat(r.toFixed(3))),this.nextLoadPosition=d.start+d.duration,this.state=Li,h=o?o.then((function(r){return!r||a.fragContextChanged(r.frag)?null:a.doFragPartsLoad(t,d,e,i)})).catch((function(t){return a.handleFragLoadError(t)})):this.doFragPartsLoad(t,d,e,i).catch((function(t){return a.handleFragLoadError(t)})),this.hls.trigger(S.FRAG_LOADING,{frag:t,part:d,targetBufferTime:r}),null===this.fragCurrent?Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING parts")):h}if(!t.url||this.loadedEndOfParts(l,r))return Promise.resolve(null)}}this.log("Loading fragment "+t.sn+" cc: "+t.cc+" "+(s?"of ["+s.startSN+"-"+s.endSN+"] ":"")+("[stream-controller]"===this.logPrefix?"level":"track")+": "+t.level+", target: "+parseFloat(r.toFixed(3))),y(t.sn)&&!this.bitrateTest&&(this.nextLoadPosition=t.start+t.duration),this.state=Li;var c,f=this.config.progressive;return c=f&&o?o.then((function(e){return!e||a.fragContextChanged(null==e?void 0:e.frag)?null:a.fragmentLoader.load(t,i)})).catch((function(t){return a.handleFragLoadError(t)})):Promise.all([this.fragmentLoader.load(t,f?i:void 0),o]).then((function(t){var e=t[0];return!f&&e&&i&&i(e),e})).catch((function(t){return a.handleFragLoadError(t)})),this.hls.trigger(S.FRAG_LOADING,{frag:t,targetBufferTime:r}),null===this.fragCurrent?Promise.reject(new Error("frag load aborted, context changed in FRAG_LOADING")):c},r.doFragPartsLoad=function(t,e,r,i){var n=this;return new Promise((function(a,s){var o,l=[],u=null==(o=r.details)?void 0:o.partList;!function e(o){n.fragmentLoader.loadPart(t,o,i).then((function(i){l[o.index]=i;var s=i.part;n.hls.trigger(S.FRAG_LOADED,i);var h=gr(r,t.sn,o.index+1)||vr(u,t.sn,o.index+1);if(!h)return a({frag:t,part:s,partsLoaded:l});e(h)})).catch(s)}(e)}))},r.handleFragLoadError=function(t){if("data"in t){var e=t.data;t.data&&e.details===A.INTERNAL_ABORTED?this.handleFragLoadAborted(e.frag,e.part):this.hls.trigger(S.ERROR,e)}else this.hls.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.INTERNAL_EXCEPTION,err:t,error:t,fatal:!0});return null},r._handleTransmuxerFlush=function(t){var e=this.getCurrentContext(t);if(e&&this.state===bi){var r=e.frag,i=e.part,n=e.level,a=self.performance.now();r.stats.parsing.end=a,i&&(i.stats.parsing.end=a),this.updateLevelTiming(r,i,n,t.partial)}else this.fragCurrent||this.state===Ei||this.state===Ii||(this.state=Ti)},r.getCurrentContext=function(t){var e=this.levels,r=this.fragCurrent,i=t.level,n=t.sn,a=t.part;if(null==e||!e[i])return this.warn("Levels object was unset while buffering fragment "+n+" of level "+i+". The current chunk will not be buffered."),null;var s=e[i],o=a>-1?gr(s,n,a):null,l=o?o.fragment:function(t,e,r){if(null==t||!t.details)return null;var i=t.details,n=i.fragments[e-i.startSN];return n||((n=i.fragmentHint)&&n.sn===e?n:e<i.startSN&&r&&r.sn===e?r:null)}(s,n,r);return l?(r&&r!==l&&(l.stats=r.stats),{frag:l,part:o,level:s}):null},r.bufferFragmentData=function(t,e,r,i,n){var a;if(t&&this.state===bi){var s=t.data1,o=t.data2,l=s;if(s&&o&&(l=Wt(s,o)),null!=(a=l)&&a.length){var u={type:t.type,frag:e,part:r,chunkMeta:i,parent:e.type,data:l};if(this.hls.trigger(S.BUFFER_APPENDING,u),t.dropped&&t.independent&&!r){if(n)return;this.flushBufferGap(e)}}}},r.flushBufferGap=function(t){var e=this.media;if(e)if(ri.isBuffered(e,e.currentTime)){var r=e.currentTime,i=ri.bufferInfo(e,r,0),n=t.duration,a=Math.min(2*this.config.maxFragLookUpTolerance,.25*n),s=Math.max(Math.min(t.start-a,i.end-a),r+a);t.start-s>a&&this.flushMainBuffer(s,t.start)}else this.flushMainBuffer(0,t.start)},r.getFwdBufferInfo=function(t,e){var r=this.getLoadPosition();return y(r)?this.getFwdBufferInfoAtPos(t,r,e):null},r.getFwdBufferInfoAtPos=function(t,e,r){var i=this.config.maxBufferHole,n=ri.bufferInfo(t,e,i);if(0===n.len&&void 0!==n.nextStart){var a=this.fragmentTracker.getBufferedFrag(e,r);if(a&&n.nextStart<a.end)return ri.bufferInfo(t,e,Math.max(n.nextStart,i))}return n},r.getMaxBufferLength=function(t){var e,r=this.config;return e=t?Math.max(8*r.maxBufferSize/t,r.maxBufferLength):r.maxBufferLength,Math.min(e,r.maxMaxBufferLength)},r.reduceMaxBufferLength=function(t,e){var r=this.config,i=Math.max(Math.min(t-e,r.maxBufferLength),e),n=Math.max(t-3*e,r.maxMaxBufferLength/2,i);return n>=i&&(r.maxMaxBufferLength=n,this.warn("Reduce max buffer length to "+n+"s"),!0)},r.getAppendedFrag=function(t,e){var r=this.fragmentTracker.getAppendedFrag(t,Fe);return r&&"fragment"in r?r.fragment:r},r.getNextFragment=function(t,e){var r=e.fragments,i=r.length;if(!i)return null;var n,a=this.config,s=r[0].start;if(e.live){var o=a.initialLiveManifestSize;if(i<o)return this.warn("Not enough fragments to start playback (have: "+i+", need: "+o+")"),null;(!e.PTSKnown&&!this.startFragRequested&&-1===this.startPosition||t<s)&&(n=this.getInitialLiveFragment(e,r),this.startPosition=this.nextLoadPosition=n?this.hls.liveSyncPosition||n.start:t)}else t<=s&&(n=r[0]);if(!n){var l=a.lowLatencyMode?e.partEnd:e.fragmentEnd;n=this.getFragmentAtPosition(t,l,e)}return this.mapToInitFragWhenRequired(n)},r.isLoopLoading=function(t,e){var r=this.fragmentTracker.getState(t);return(r===Jr||r===Qr&&!!t.gap)&&this.nextLoadPosition>e},r.getNextFragmentLoopLoading=function(t,e,r,i,n){var a=t.gap,s=this.getNextFragment(this.nextLoadPosition,e);if(null===s)return s;if(t=s,a&&t&&!t.gap&&r.nextStart){var o=this.getFwdBufferInfoAtPos(this.mediaBuffer?this.mediaBuffer:this.media,r.nextStart,i);if(null!==o&&r.len+o.len>=n)return this.log('buffer full after gaps in "'+i+'" playlist starting at sn: '+t.sn),null}return t},r.mapToInitFragWhenRequired=function(t){return null==t||!t.initSegment||null!=t&&t.initSegment.data||this.bitrateTest?t:t.initSegment},r.getNextPart=function(t,e,r){for(var i=-1,n=!1,a=!0,s=0,o=t.length;s<o;s++){var l=t[s];if(a=a&&!l.independent,i>-1&&r<l.start)break;var u=l.loaded;u?i=-1:(n||l.independent||a)&&l.fragment===e&&(i=s),n=u}return i},r.loadedEndOfParts=function(t,e){var r=t[t.length-1];return r&&e>r.start&&r.loaded},r.getInitialLiveFragment=function(t,e){var r=this.fragPrevious,i=null;if(r){if(t.hasProgramDateTime&&(this.log("Live playlist, switching playlist, load frag with same PDT: "+r.programDateTime),i=function(t,e,r){if(null===e||!Array.isArray(t)||!t.length||!y(e))return null;if(e<(t[0].programDateTime||0))return null;if(e>=(t[t.length-1].endProgramDateTime||0))return null;r=r||0;for(var i=0;i<t.length;++i){var n=t[i];if(br(e,r,n))return n}return null}(e,r.endProgramDateTime,this.config.maxFragLookUpTolerance)),!i){var n=r.sn+1;if(n>=t.startSN&&n<=t.endSN){var a=e[n-t.startSN];r.cc===a.cc&&(i=a,this.log("Live playlist, switching playlist, load frag with next SN: "+i.sn))}i||(i=function(t,e){return Lr(t,(function(t){return t.cc<e?1:t.cc>e?-1:0}))}(e,r.cc),i&&this.log("Live playlist, switching playlist, load frag with same CC: "+i.sn))}}else{var s=this.hls.liveSyncPosition;null!==s&&(i=this.getFragmentAtPosition(s,this.bitrateTest?t.fragmentEnd:t.edge,t))}return i},r.getFragmentAtPosition=function(t,e,r){var i,n=this.config,a=this.fragPrevious,s=r.fragments,o=r.endSN,l=r.fragmentHint,u=n.maxFragLookUpTolerance,h=r.partList,d=!!(n.lowLatencyMode&&null!=h&&h.length&&l);if(d&&l&&!this.bitrateTest&&(s=s.concat(l),o=l.sn),i=t<e?Ar(a,s,t,t>e-u?0:u):s[s.length-1]){var c=i.sn-r.startSN,f=this.fragmentTracker.getState(i);if((f===Jr||f===Qr&&i.gap)&&(a=i),a&&i.sn===a.sn&&(!d||h[0].fragment.sn>i.sn)&&a&&i.level===a.level){var g=s[c+1];i=i.sn<o&&this.fragmentTracker.getState(g)!==Jr?g:null}}return i},r.synchronizeToLiveEdge=function(t){var e=this.config,r=this.media;if(r){var i=this.hls.liveSyncPosition,n=r.currentTime,a=t.fragments[0].start,s=t.edge,o=n>=a-e.maxFragLookUpTolerance&&n<=s;if(null!==i&&r.duration>i&&(n<i||!o)){var l=void 0!==e.liveMaxLatencyDuration?e.liveMaxLatencyDuration:e.liveMaxLatencyDurationCount*t.targetduration;(!o&&r.readyState<4||n<s-l)&&(this.loadedmetadata||(this.nextLoadPosition=i),r.readyState&&(this.warn("Playback: "+n.toFixed(3)+" is located too far from the end of live sliding playlist: "+s+", reset currentTime to : "+i.toFixed(3)),r.currentTime=i))}}},r.alignPlaylists=function(t,e,r){var i=t.fragments.length;if(!i)return this.warn("No fragments in live playlist"),0;var n=t.fragments[0].start,a=!e,s=t.alignedSliding&&y(n);if(a||!s&&!n){var o=this.fragPrevious;oi(o,r,t);var l=t.fragments[0].start;return this.log("Live playlist sliding: "+l.toFixed(2)+" start-sn: "+(e?e.startSN:"na")+"->"+t.startSN+" prev-sn: "+(o?o.sn:"na")+" fragments: "+i),l}return n},r.waitForCdnTuneIn=function(t){return t.live&&t.canBlockReload&&t.partTarget&&t.tuneInGoal>Math.max(t.partHoldBack,3*t.partTarget)},r.setStartPosition=function(t,e){var r=this.startPosition;if(r<e&&(r=-1),-1===r||-1===this.lastCurrentTime){var i=null!==this.startTimeOffset,n=i?this.startTimeOffset:t.startTimeOffset;null!==n&&y(n)?(r=e+n,n<0&&(r+=t.totalduration),r=Math.min(Math.max(e,r),e+t.totalduration),this.log("Start time offset "+n+" found in "+(i?"multivariant":"media")+" playlist, adjust startPosition to "+r),this.startPosition=r):t.live?r=this.hls.liveSyncPosition||e:this.startPosition=r=0,this.lastCurrentTime=r}this.nextLoadPosition=r},r.getLoadPosition=function(){var t=this.media,e=0;return this.loadedmetadata&&t?e=t.currentTime:this.nextLoadPosition&&(e=this.nextLoadPosition),e},r.handleFragLoadAborted=function(t,e){this.transmuxer&&"initSegment"!==t.sn&&t.stats.aborted&&(this.warn("Fragment "+t.sn+(e?" part "+e.index:"")+" of level "+t.level+" was aborted"),this.resetFragmentLoading(t))},r.resetFragmentLoading=function(t){this.fragCurrent&&(this.fragContextChanged(t)||this.state===Ai)||(this.state=Ti)},r.onFragmentOrKeyLoadError=function(t,e){if(e.chunkMeta&&!e.frag){var r=this.getCurrentContext(e.chunkMeta);r&&(e.frag=r.frag)}var i=e.frag;if(i&&i.type===t&&this.levels)if(this.fragContextChanged(i)){var n;this.warn("Frag load error must match current frag to retry "+i.url+" > "+(null==(n=this.fragCurrent)?void 0:n.url))}else{var a=e.details===A.FRAG_GAP;a&&this.fragmentTracker.fragBuffered(i,!0);var s=e.errorAction,o=s||{},l=o.action,u=o.retryCount,h=void 0===u?0:u,d=o.retryConfig;if(s&&l===wr&&d){this.resetStartWhenNotLoaded(this.levelLastLoaded);var c=Er(d,h);this.warn("Fragment "+i.sn+" of "+t+" "+i.level+" errored with "+e.details+", retrying loading "+(h+1)+"/"+d.maxNumRetry+" in "+c+"ms"),s.resolved=!0,this.retryDate=self.performance.now()+c,this.state=Ai}else if(d&&s){if(this.resetFragmentErrors(t),!(h<d.maxNumRetry))return void w.warn(e.details+" reached or exceeded max retry ("+h+")");a||l===Ir||(s.resolved=!0)}else(null==s?void 0:s.action)===Dr?this.state=Ci:this.state=Ii;this.tickImmediate()}},r.reduceLengthAndFlushBuffer=function(t){if(this.state===bi||this.state===ki){var e=t.frag,r=t.parent,i=this.getFwdBufferInfo(this.mediaBuffer,r),n=i&&i.len>.5;n&&this.reduceMaxBufferLength(i.len,(null==e?void 0:e.duration)||10);var a=!n;return a&&this.warn("Buffer full error while media.currentTime is not buffered, flush "+r+" buffer"),e&&(this.fragmentTracker.removeFragment(e),this.nextLoadPosition=e.start),this.resetLoadingState(),a}return!1},r.resetFragmentErrors=function(t){t===Me&&(this.fragCurrent=null),this.loadedmetadata||(this.startFragRequested=!1),this.state!==Ei&&(this.state=Ti)},r.afterBufferFlushed=function(t,e,r){if(t){var i=ri.getBuffered(t);this.fragmentTracker.detectEvictedFragments(e,i,r),this.state===Di&&this.resetLoadingState()}},r.resetLoadingState=function(){this.log("Reset loading state"),this.fragCurrent=null,this.fragPrevious=null,this.state=Ti},r.resetStartWhenNotLoaded=function(t){if(!this.loadedmetadata){this.startFragRequested=!1;var e=t?t.details:null;null!=e&&e.live?(this.startPosition=-1,this.setStartPosition(e,0),this.resetLoadingState()):this.nextLoadPosition=this.startPosition}},r.resetWhenMissingContext=function(t){this.warn("The loading context changed while buffering fragment "+t.sn+" of level "+t.level+". This chunk will not be buffered."),this.removeUnbufferedFrags(),this.resetStartWhenNotLoaded(this.levelLastLoaded),this.resetLoadingState()},r.removeUnbufferedFrags=function(t){void 0===t&&(t=0),this.fragmentTracker.removeFragmentsInRange(t,1/0,this.playlistType,!1,!0)},r.updateLevelTiming=function(t,e,r,i){var n,a=this,s=r.details;if(s){if(!Object.keys(t.elementaryStreams).reduce((function(e,n){var o=t.elementaryStreams[n];if(o){var l=o.endPTS-o.startPTS;if(l<=0)return a.warn("Could not parse fragment "+t.sn+" "+n+" duration reliably ("+l+")"),e||!1;var u=i?0:hr(s,t,o.startPTS,o.endPTS,o.startDTS,o.endDTS);return a.hls.trigger(S.LEVEL_PTS_UPDATED,{details:s,level:r,drift:u,type:n,frag:t,start:o.startPTS,end:o.endPTS}),!0}return e}),!1)&&null===(null==(n=this.transmuxer)?void 0:n.error)){var o=new Error("Found no media in fragment "+t.sn+" of level "+t.level+" resetting transmuxer to fallback to playlist timing");if(0===r.fragmentError&&(r.fragmentError++,t.gap=!0,this.fragmentTracker.removeFragment(t),this.fragmentTracker.fragBuffered(t,!0)),this.warn(o.message),this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!1,error:o,frag:t,reason:"Found no media in msn "+t.sn+' of level "'+r.url+'"'}),!this.hls)return;this.resetTransmuxer()}this.state=ki,this.hls.trigger(S.FRAG_PARSED,{frag:t,part:e})}else this.warn("level.details undefined")},r.resetTransmuxer=function(){this.transmuxer&&(this.transmuxer.destroy(),this.transmuxer=null)},r.recoverWorkerError=function(t){"demuxerWorker"===t.event&&(this.fragmentTracker.removeAllFragments(),this.resetTransmuxer(),this.resetStartWhenNotLoaded(this.levelLastLoaded),this.resetLoadingState())},s(e,[{key:"state",get:function(){return this._state},set:function(t){var e=this._state;e!==t&&(this._state=t,this.log(e+"->"+t))}}]),e}(qr),xi=function(){function t(){this.chunks=[],this.dataLength=0}var e=t.prototype;return e.push=function(t){this.chunks.push(t),this.dataLength+=t.length},e.flush=function(){var t,e=this.chunks,r=this.dataLength;return e.length?(t=1===e.length?e[0]:function(t,e){for(var r=new Uint8Array(e),i=0,n=0;n<t.length;n++){var a=t[n];r.set(a,i),i+=a.length}return r}(e,r),this.reset(),t):new Uint8Array(0)},e.reset=function(){this.chunks.length=0,this.dataLength=0},t}();function Pi(t,e){return void 0===t&&(t=""),void 0===e&&(e=9e4),{type:t,id:-1,pid:-1,inputTimeScale:e,sequenceNumber:-1,samples:[],dropped:0}}var Fi=function(){function t(){this._audioTrack=void 0,this._id3Track=void 0,this.frameIndex=0,this.cachedData=null,this.basePTS=null,this.initPTS=null,this.lastPTS=null}var e=t.prototype;return e.resetInitSegment=function(t,e,r,i){this._id3Track={type:"id3",id:3,pid:-1,inputTimeScale:9e4,sequenceNumber:0,samples:[],dropped:0}},e.resetTimeStamp=function(t){this.initPTS=t,this.resetContiguity()},e.resetContiguity=function(){this.basePTS=null,this.lastPTS=null,this.frameIndex=0},e.canParse=function(t,e){return!1},e.appendFrame=function(t,e,r){},e.demux=function(t,e){this.cachedData&&(t=Wt(this.cachedData,t),this.cachedData=null);var r,i=ct(t,0),n=i?i.length:0,a=this._audioTrack,s=this._id3Track,o=i?vt(i):void 0,l=t.length;for((null===this.basePTS||0===this.frameIndex&&y(o))&&(this.basePTS=Mi(o,e,this.initPTS),this.lastPTS=this.basePTS),null===this.lastPTS&&(this.lastPTS=this.basePTS),i&&i.length>0&&s.samples.push({pts:this.lastPTS,dts:this.lastPTS,data:i,type:We,duration:Number.POSITIVE_INFINITY});n<l;){if(this.canParse(t,n)){var u=this.appendFrame(a,t,n);u?(this.frameIndex++,this.lastPTS=u.sample.pts,r=n+=u.length):n=l}else gt(t,n)?(i=ct(t,n),s.samples.push({pts:this.lastPTS,dts:this.lastPTS,data:i,type:We,duration:Number.POSITIVE_INFINITY}),r=n+=i.length):n++;if(n===l&&r!==l){var h=lt(t,r);this.cachedData?this.cachedData=Wt(this.cachedData,h):this.cachedData=h}}return{audioTrack:a,videoTrack:Pi(),id3Track:s,textTrack:Pi()}},e.demuxSampleAes=function(t,e,r){return Promise.reject(new Error("["+this+"] This demuxer does not support Sample-AES decryption"))},e.flush=function(t){var e=this.cachedData;return e&&(this.cachedData=null,this.demux(e,0)),{audioTrack:this._audioTrack,videoTrack:Pi(),id3Track:this._id3Track,textTrack:Pi()}},e.destroy=function(){},t}(),Mi=function(t,e,r){return y(t)?90*t:9e4*e+(r?9e4*r.baseTime/r.timescale:0)};function Oi(t,e){return 255===t[e]&&240==(246&t[e+1])}function Ni(t,e){return 1&t[e+1]?7:9}function Ui(t,e){return(3&t[e+3])<<11|t[e+4]<<3|(224&t[e+5])>>>5}function Bi(t,e){return e+1<t.length&&Oi(t,e)}function Gi(t,e){if(Bi(t,e)){var r=Ni(t,e);if(e+r>=t.length)return!1;var i=Ui(t,e);if(i<=r)return!1;var n=e+i;return n===t.length||Bi(t,n)}return!1}function Ki(t,e,r,i,n){if(!t.samplerate){var a=function(t,e,r,i){var n,a,s,o,l=navigator.userAgent.toLowerCase(),u=i,h=[96e3,88200,64e3,48e3,44100,32e3,24e3,22050,16e3,12e3,11025,8e3,7350];n=1+((192&e[r+2])>>>6);var d=(60&e[r+2])>>>2;if(!(d>h.length-1))return s=(1&e[r+2])<<2,s|=(192&e[r+3])>>>6,w.log("manifest codec:"+i+", ADTS type:"+n+", samplingIndex:"+d),/firefox/i.test(l)?d>=6?(n=5,o=new Array(4),a=d-3):(n=2,o=new Array(2),a=d):-1!==l.indexOf("android")?(n=2,o=new Array(2),a=d):(n=5,o=new Array(4),i&&(-1!==i.indexOf("mp4a.40.29")||-1!==i.indexOf("mp4a.40.5"))||!i&&d>=6?a=d-3:((i&&-1!==i.indexOf("mp4a.40.2")&&(d>=6&&1===s||/vivaldi/i.test(l))||!i&&1===s)&&(n=2,o=new Array(2)),a=d)),o[0]=n<<3,o[0]|=(14&d)>>1,o[1]|=(1&d)<<7,o[1]|=s<<3,5===n&&(o[1]|=(14&a)>>1,o[2]=(1&a)<<7,o[2]|=8,o[3]=0),{config:o,samplerate:h[d],channelCount:s,codec:"mp4a.40."+n,manifestCodec:u};var c=new Error("invalid ADTS sampling index:"+d);t.emit(S.ERROR,S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!0,error:c,reason:c.message})}(e,r,i,n);if(!a)return;t.config=a.config,t.samplerate=a.samplerate,t.channelCount=a.channelCount,t.codec=a.codec,t.manifestCodec=a.manifestCodec,w.log("parsed codec:"+t.codec+", rate:"+a.samplerate+", channels:"+a.channelCount)}}function Hi(t){return 9216e4/t}function Vi(t,e,r,i,n){var a,s=i+n*Hi(t.samplerate),o=function(t,e){var r=Ni(t,e);if(e+r<=t.length){var i=Ui(t,e)-r;if(i>0)return{headerLength:r,frameLength:i}}}(e,r);if(o){var l=o.frameLength,u=o.headerLength,h=u+l,d=Math.max(0,r+h-e.length);d?(a=new Uint8Array(h-u)).set(e.subarray(r+u,e.length),0):a=e.subarray(r+u,r+h);var c={unit:a,pts:s};return d||t.samples.push(c),{sample:c,length:h,missing:d}}var f=e.length-r;return(a=new Uint8Array(f)).set(e.subarray(r,e.length),0),{sample:{unit:a,pts:s},length:f,missing:-1}}var Yi=null,Wi=[32,64,96,128,160,192,224,256,288,320,352,384,416,448,32,48,56,64,80,96,112,128,160,192,224,256,320,384,32,40,48,56,64,80,96,112,128,160,192,224,256,320,32,48,56,64,80,96,112,128,144,160,176,192,224,256,8,16,24,32,40,48,56,64,80,96,112,128,144,160],ji=[44100,48e3,32e3,22050,24e3,16e3,11025,12e3,8e3],qi=[[0,72,144,12],[0,0,0,0],[0,72,144,12],[0,144,144,12]],Xi=[0,1,1,4];function zi(t,e,r,i,n){if(!(r+24>e.length)){var a=Qi(e,r);if(a&&r+a.frameLength<=e.length){var s=i+n*(9e4*a.samplesPerFrame/a.sampleRate),o={unit:e.subarray(r,r+a.frameLength),pts:s,dts:s};return t.config=[],t.channelCount=a.channelCount,t.samplerate=a.sampleRate,t.samples.push(o),{sample:o,length:a.frameLength,missing:0}}}}function Qi(t,e){var r=t[e+1]>>3&3,i=t[e+1]>>1&3,n=t[e+2]>>4&15,a=t[e+2]>>2&3;if(1!==r&&0!==n&&15!==n&&3!==a){var s=t[e+2]>>1&1,o=t[e+3]>>6,l=1e3*Wi[14*(3===r?3-i:3===i?3:4)+n-1],u=ji[3*(3===r?0:2===r?1:2)+a],h=3===o?1:2,d=qi[r][i],c=Xi[i],f=8*d*c,g=Math.floor(d*l/u+s)*c;if(null===Yi){var v=(navigator.userAgent||"").match(/Chrome\/(\d+)/i);Yi=v?parseInt(v[1]):0}return!!Yi&&Yi<=87&&2===i&&l>=224e3&&0===o&&(t[e+3]=128|t[e+3]),{sampleRate:u,channelCount:h,frameLength:g,samplesPerFrame:f}}}function Ji(t,e){return 255===t[e]&&224==(224&t[e+1])&&0!=(6&t[e+1])}function $i(t,e){return e+1<t.length&&Ji(t,e)}function Zi(t,e){if(e+1<t.length&&Ji(t,e)){var r=Qi(t,e),i=4;null!=r&&r.frameLength&&(i=r.frameLength);var n=e+i;return n===t.length||$i(t,n)}return!1}var tn=function(t){function e(e,r){var i;return(i=t.call(this)||this).observer=void 0,i.config=void 0,i.observer=e,i.config=r,i}l(e,t);var r=e.prototype;return r.resetInitSegment=function(e,r,i,n){t.prototype.resetInitSegment.call(this,e,r,i,n),this._audioTrack={container:"audio/adts",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"aac",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},e.probe=function(t){if(!t)return!1;var e=ct(t,0),r=(null==e?void 0:e.length)||0;if(Zi(t,r))return!1;for(var i=t.length;r<i;r++)if(Gi(t,r))return w.log("ADTS sync word found !"),!0;return!1},r.canParse=function(t,e){return function(t,e){return function(t,e){return e+5<t.length}(t,e)&&Oi(t,e)&&Ui(t,e)<=t.length-e}(t,e)},r.appendFrame=function(t,e,r){Ki(t,this.observer,e,r,t.manifestCodec);var i=Vi(t,e,r,this.basePTS,this.frameIndex);if(i&&0===i.missing)return i},e}(Fi),en=/\/emsg[-/]ID3/i,rn=function(){function t(t,e){this.remainderData=null,this.timeOffset=0,this.config=void 0,this.videoTrack=void 0,this.audioTrack=void 0,this.id3Track=void 0,this.txtTrack=void 0,this.config=e}var e=t.prototype;return e.resetTimeStamp=function(){},e.resetInitSegment=function(t,e,r,i){var n=this.videoTrack=Pi("video",1),a=this.audioTrack=Pi("audio",1),s=this.txtTrack=Pi("text",1);if(this.id3Track=Pi("id3",1),this.timeOffset=0,null!=t&&t.byteLength){var o=Ut(t);if(o.video){var l=o.video,u=l.id,h=l.timescale,d=l.codec;n.id=u,n.timescale=s.timescale=h,n.codec=d}if(o.audio){var c=o.audio,f=c.id,g=c.timescale,v=c.codec;a.id=f,a.timescale=g,a.codec=v}s.id=wt.text,n.sampleDuration=0,n.duration=a.duration=i}},e.resetContiguity=function(){this.remainderData=null},t.probe=function(t){return function(t){for(var e=t.byteLength,r=0;r<e;){var i=xt(t,r);if(i>8&&109===t[r+4]&&111===t[r+5]&&111===t[r+6]&&102===t[r+7])return!0;r=i>1?r+i:e}return!1}(t)},e.demux=function(t,e){this.timeOffset=e;var r=t,i=this.videoTrack,n=this.txtTrack;if(this.config.progressive){this.remainderData&&(r=Wt(this.remainderData,t));var a=function(t){var e={valid:null,remainder:null},r=Ot(t,["moof"]);if(r.length<2)return e.remainder=t,e;var i=r[r.length-1];return e.valid=lt(t,0,i.byteOffset-8),e.remainder=lt(t,i.byteOffset-8),e}(r);this.remainderData=a.remainder,i.samples=a.valid||new Uint8Array}else i.samples=r;var s=this.extractID3Track(i,e);return n.samples=jt(e,i),{videoTrack:i,audioTrack:this.audioTrack,id3Track:s,textTrack:this.txtTrack}},e.flush=function(){var t=this.timeOffset,e=this.videoTrack,r=this.txtTrack;e.samples=this.remainderData||new Uint8Array,this.remainderData=null;var i=this.extractID3Track(e,this.timeOffset);return r.samples=jt(t,e),{videoTrack:e,audioTrack:Pi(),id3Track:i,textTrack:Pi()}},e.extractID3Track=function(t,e){var r=this.id3Track;if(t.samples.length){var i=Ot(t.samples,["emsg"]);i&&i.forEach((function(t){var i=function(t){var e=t[0],r="",i="",n=0,a=0,s=0,o=0,l=0,u=0;if(0===e){for(;"\0"!==Ct(t.subarray(u,u+1));)r+=Ct(t.subarray(u,u+1)),u+=1;for(r+=Ct(t.subarray(u,u+1)),u+=1;"\0"!==Ct(t.subarray(u,u+1));)i+=Ct(t.subarray(u,u+1)),u+=1;i+=Ct(t.subarray(u,u+1)),u+=1,n=xt(t,12),a=xt(t,16),o=xt(t,20),l=xt(t,24),u=28}else if(1===e){n=xt(t,u+=4);var h=xt(t,u+=4),d=xt(t,u+=4);for(u+=4,s=Math.pow(2,32)*h+d,E(s)||(s=Number.MAX_SAFE_INTEGER,w.warn("Presentation time exceeds safe integer limit and wrapped to max safe integer in parsing emsg box")),o=xt(t,u),l=xt(t,u+=4),u+=4;"\0"!==Ct(t.subarray(u,u+1));)r+=Ct(t.subarray(u,u+1)),u+=1;for(r+=Ct(t.subarray(u,u+1)),u+=1;"\0"!==Ct(t.subarray(u,u+1));)i+=Ct(t.subarray(u,u+1)),u+=1;i+=Ct(t.subarray(u,u+1)),u+=1}return{schemeIdUri:r,value:i,timeScale:n,presentationTime:s,presentationTimeDelta:a,eventDuration:o,id:l,payload:t.subarray(u,t.byteLength)}}(t);if(en.test(i.schemeIdUri)){var n=y(i.presentationTime)?i.presentationTime/i.timeScale:e+i.presentationTimeDelta/i.timeScale,a=4294967295===i.eventDuration?Number.POSITIVE_INFINITY:i.eventDuration/i.timeScale;a<=.001&&(a=Number.POSITIVE_INFINITY);var s=i.payload;r.samples.push({data:s,len:s.byteLength,dts:n,pts:n,type:qe,duration:a})}}))}return r},e.demuxSampleAes=function(t,e,r){return Promise.reject(new Error("The MP4 demuxer does not support SAMPLE-AES decryption"))},e.destroy=function(){},t}(),nn=function(t,e){var r=0,i=5;e+=i;for(var n=new Uint32Array(1),a=new Uint32Array(1),s=new Uint8Array(1);i>0;){s[0]=t[e];var o=Math.min(i,8),l=8-o;a[0]=4278190080>>>24+l<<l,n[0]=(s[0]&a[0])>>l,r=r?r<<o|n[0]:n[0],e+=1,i-=o}return r},an=function(t){function e(e){var r;return(r=t.call(this)||this).observer=void 0,r.observer=e,r}l(e,t);var r=e.prototype;return r.resetInitSegment=function(e,r,i,n){t.prototype.resetInitSegment.call(this,e,r,i,n),this._audioTrack={container:"audio/ac-3",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"ac3",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},r.canParse=function(t,e){return e+64<t.length},r.appendFrame=function(t,e,r){var i=sn(t,e,r,this.basePTS,this.frameIndex);if(-1!==i)return{sample:t.samples[t.samples.length-1],length:i,missing:0}},e.probe=function(t){if(!t)return!1;var e=ct(t,0);if(!e)return!1;var r=e.length;return 11===t[r]&&119===t[r+1]&&void 0!==vt(e)&&nn(t,r)<16},e}(Fi);function sn(t,e,r,i,n){if(r+8>e.length)return-1;if(11!==e[r]||119!==e[r+1])return-1;var a=e[r+4]>>6;if(a>=3)return-1;var s=[48e3,44100,32e3][a],o=63&e[r+4],l=2*[64,69,96,64,70,96,80,87,120,80,88,120,96,104,144,96,105,144,112,121,168,112,122,168,128,139,192,128,140,192,160,174,240,160,175,240,192,208,288,192,209,288,224,243,336,224,244,336,256,278,384,256,279,384,320,348,480,320,349,480,384,417,576,384,418,576,448,487,672,448,488,672,512,557,768,512,558,768,640,696,960,640,697,960,768,835,1152,768,836,1152,896,975,1344,896,976,1344,1024,1114,1536,1024,1115,1536,1152,1253,1728,1152,1254,1728,1280,1393,1920,1280,1394,1920][3*o+a];if(r+l>e.length)return-1;var u=e[r+6]>>5,h=0;2===u?h+=2:(1&u&&1!==u&&(h+=2),4&u&&(h+=2));var d=(e[r+6]<<8|e[r+7])>>12-h&1,c=[2,1,2,3,3,4,4,5][u]+d,f=e[r+5]>>3,g=7&e[r+5],v=new Uint8Array([a<<6|f<<1|g>>2,(3&g)<<6|u<<3|d<<2|o>>4,o<<4&224]),m=i+n*(1536/s*9e4),p=e.subarray(r,r+l);return t.config=v,t.channelCount=c,t.samplerate=s,t.samples.push({unit:p,pts:m}),l}var on=function(){function t(){this.VideoSample=null}var e=t.prototype;return e.createVideoSample=function(t,e,r,i){return{key:t,frame:!1,pts:e,dts:r,units:[],debug:i,length:0}},e.getLastNalUnit=function(t){var e,r,i=this.VideoSample;if(i&&0!==i.units.length||(i=t[t.length-1]),null!=(e=i)&&e.units){var n=i.units;r=n[n.length-1]}return r},e.pushAccessUnit=function(t,e){if(t.units.length&&t.frame){if(void 0===t.pts){var r=e.samples,i=r.length;if(!i)return void e.dropped++;var n=r[i-1];t.pts=n.pts,t.dts=n.dts}e.samples.push(t)}t.debug.length&&w.log(t.pts+"/"+t.dts+":"+t.debug)},t}(),ln=function(){function t(t){this.data=void 0,this.bytesAvailable=void 0,this.word=void 0,this.bitsAvailable=void 0,this.data=t,this.bytesAvailable=t.byteLength,this.word=0,this.bitsAvailable=0}var e=t.prototype;return e.loadWord=function(){var t=this.data,e=this.bytesAvailable,r=t.byteLength-e,i=new Uint8Array(4),n=Math.min(4,e);if(0===n)throw new Error("no bytes available");i.set(t.subarray(r,r+n)),this.word=new DataView(i.buffer).getUint32(0),this.bitsAvailable=8*n,this.bytesAvailable-=n},e.skipBits=function(t){var e;t=Math.min(t,8*this.bytesAvailable+this.bitsAvailable),this.bitsAvailable>t?(this.word<<=t,this.bitsAvailable-=t):(t-=this.bitsAvailable,t-=(e=t>>3)<<3,this.bytesAvailable-=e,this.loadWord(),this.word<<=t,this.bitsAvailable-=t)},e.readBits=function(t){var e=Math.min(this.bitsAvailable,t),r=this.word>>>32-e;if(t>32&&w.error("Cannot read more than 32 bits at a time"),this.bitsAvailable-=e,this.bitsAvailable>0)this.word<<=e;else{if(!(this.bytesAvailable>0))throw new Error("no bits available");this.loadWord()}return(e=t-e)>0&&this.bitsAvailable?r<<e|this.readBits(e):r},e.skipLZ=function(){var t;for(t=0;t<this.bitsAvailable;++t)if(0!=(this.word&2147483648>>>t))return this.word<<=t,this.bitsAvailable-=t,t;return this.loadWord(),t+this.skipLZ()},e.skipUEG=function(){this.skipBits(1+this.skipLZ())},e.skipEG=function(){this.skipBits(1+this.skipLZ())},e.readUEG=function(){var t=this.skipLZ();return this.readBits(t+1)-1},e.readEG=function(){var t=this.readUEG();return 1&t?1+t>>>1:-1*(t>>>1)},e.readBoolean=function(){return 1===this.readBits(1)},e.readUByte=function(){return this.readBits(8)},e.readUShort=function(){return this.readBits(16)},e.readUInt=function(){return this.readBits(32)},e.skipScalingList=function(t){for(var e=8,r=8,i=0;i<t;i++)0!==r&&(r=(e+this.readEG()+256)%256),e=0===r?e:r},e.readSPS=function(){var t,e,r,i=0,n=0,a=0,s=0,o=this.readUByte.bind(this),l=this.readBits.bind(this),u=this.readUEG.bind(this),h=this.readBoolean.bind(this),d=this.skipBits.bind(this),c=this.skipEG.bind(this),f=this.skipUEG.bind(this),g=this.skipScalingList.bind(this);o();var v=o();if(l(5),d(3),o(),f(),100===v||110===v||122===v||244===v||44===v||83===v||86===v||118===v||128===v){var m=u();if(3===m&&d(1),f(),f(),d(1),h())for(e=3!==m?8:12,r=0;r<e;r++)h()&&g(r<6?16:64)}f();var p=u();if(0===p)u();else if(1===p)for(d(1),c(),c(),t=u(),r=0;r<t;r++)c();f(),d(1);var y=u(),E=u(),T=l(1);0===T&&d(1),d(1),h()&&(i=u(),n=u(),a=u(),s=u());var S=[1,1];if(h()&&h())switch(o()){case 1:S=[1,1];break;case 2:S=[12,11];break;case 3:S=[10,11];break;case 4:S=[16,11];break;case 5:S=[40,33];break;case 6:S=[24,11];break;case 7:S=[20,11];break;case 8:S=[32,11];break;case 9:S=[80,33];break;case 10:S=[18,11];break;case 11:S=[15,11];break;case 12:S=[64,33];break;case 13:S=[160,99];break;case 14:S=[4,3];break;case 15:S=[3,2];break;case 16:S=[2,1];break;case 255:S=[o()<<8|o(),o()<<8|o()]}return{width:Math.ceil(16*(y+1)-2*i-2*n),height:(2-T)*(E+1)*16-(T?2:4)*(a+s),pixelRatio:S}},e.readSliceType=function(){return this.readUByte(),this.readUEG(),this.readUEG()},t}(),un=function(t){function e(){return t.apply(this,arguments)||this}l(e,t);var r=e.prototype;return r.parseAVCPES=function(t,e,r,i,n){var a,s=this,o=this.parseAVCNALu(t,r.data),l=this.VideoSample,u=!1;r.data=null,l&&o.length&&!t.audFound&&(this.pushAccessUnit(l,t),l=this.VideoSample=this.createVideoSample(!1,r.pts,r.dts,"")),o.forEach((function(i){var o;switch(i.type){case 1:var h=!1;a=!0;var d,c=i.data;if(u&&c.length>4){var f=new ln(c).readSliceType();2!==f&&4!==f&&7!==f&&9!==f||(h=!0)}h&&null!=(d=l)&&d.frame&&!l.key&&(s.pushAccessUnit(l,t),l=s.VideoSample=null),l||(l=s.VideoSample=s.createVideoSample(!0,r.pts,r.dts,"")),l.frame=!0,l.key=h;break;case 5:a=!0,null!=(o=l)&&o.frame&&!l.key&&(s.pushAccessUnit(l,t),l=s.VideoSample=null),l||(l=s.VideoSample=s.createVideoSample(!0,r.pts,r.dts,"")),l.key=!0,l.frame=!0;break;case 6:a=!0,Xt(i.data,1,r.pts,e.samples);break;case 7:var g,v;a=!0,u=!0;var m=i.data,p=new ln(m).readSPS();if(!t.sps||t.width!==p.width||t.height!==p.height||(null==(g=t.pixelRatio)?void 0:g[0])!==p.pixelRatio[0]||(null==(v=t.pixelRatio)?void 0:v[1])!==p.pixelRatio[1]){t.width=p.width,t.height=p.height,t.pixelRatio=p.pixelRatio,t.sps=[m],t.duration=n;for(var y=m.subarray(1,4),E="avc1.",T=0;T<3;T++){var S=y[T].toString(16);S.length<2&&(S="0"+S),E+=S}t.codec=E}break;case 8:a=!0,t.pps=[i.data];break;case 9:a=!0,t.audFound=!0,l&&s.pushAccessUnit(l,t),l=s.VideoSample=s.createVideoSample(!1,r.pts,r.dts,"");break;case 12:a=!0;break;default:a=!1,l&&(l.debug+="unknown NAL "+i.type+" ")}l&&a&&l.units.push(i)})),i&&l&&(this.pushAccessUnit(l,t),this.VideoSample=null)},r.parseAVCNALu=function(t,e){var r,i,n=e.byteLength,a=t.naluState||0,s=a,o=[],l=0,u=-1,h=0;for(-1===a&&(u=0,h=31&e[0],a=0,l=1);l<n;)if(r=e[l++],a)if(1!==a)if(r)if(1===r){if(i=l-a-1,u>=0){var d={data:e.subarray(u,i),type:h};o.push(d)}else{var c=this.getLastNalUnit(t.samples);c&&(s&&l<=4-s&&c.state&&(c.data=c.data.subarray(0,c.data.byteLength-s)),i>0&&(c.data=Wt(c.data,e.subarray(0,i)),c.state=0))}l<n?(u=l,h=31&e[l],a=0):a=-1}else a=0;else a=3;else a=r?0:2;else a=r?0:1;if(u>=0&&a>=0){var f={data:e.subarray(u,n),type:h,state:a};o.push(f)}if(0===o.length){var g=this.getLastNalUnit(t.samples);g&&(g.data=Wt(g.data,e))}return t.naluState=a,o},e}(on),hn=function(){function t(t,e,r){this.keyData=void 0,this.decrypter=void 0,this.keyData=r,this.decrypter=new pi(e,{removePKCS7Padding:!1})}var e=t.prototype;return e.decryptBuffer=function(t){return this.decrypter.decrypt(t,this.keyData.key.buffer,this.keyData.iv.buffer)},e.decryptAacSample=function(t,e,r){var i=this,n=t[e].unit;if(!(n.length<=16)){var a=n.subarray(16,n.length-n.length%16),s=a.buffer.slice(a.byteOffset,a.byteOffset+a.length);this.decryptBuffer(s).then((function(a){var s=new Uint8Array(a);n.set(s,16),i.decrypter.isSync()||i.decryptAacSamples(t,e+1,r)}))}},e.decryptAacSamples=function(t,e,r){for(;;e++){if(e>=t.length)return void r();if(!(t[e].unit.length<32||(this.decryptAacSample(t,e,r),this.decrypter.isSync())))return}},e.getAvcEncryptedData=function(t){for(var e=16*Math.floor((t.length-48)/160)+16,r=new Int8Array(e),i=0,n=32;n<t.length-16;n+=160,i+=16)r.set(t.subarray(n,n+16),i);return r},e.getAvcDecryptedUnit=function(t,e){for(var r=new Uint8Array(e),i=0,n=32;n<t.length-16;n+=160,i+=16)t.set(r.subarray(i,i+16),n);return t},e.decryptAvcSample=function(t,e,r,i,n){var a=this,s=zt(n.data),o=this.getAvcEncryptedData(s);this.decryptBuffer(o.buffer).then((function(o){n.data=a.getAvcDecryptedUnit(s,o),a.decrypter.isSync()||a.decryptAvcSamples(t,e,r+1,i)}))},e.decryptAvcSamples=function(t,e,r,i){if(t instanceof Uint8Array)throw new Error("Cannot decrypt samples of type Uint8Array");for(;;e++,r=0){if(e>=t.length)return void i();for(var n=t[e].units;!(r>=n.length);r++){var a=n[r];if(!(a.data.length<=48||1!==a.type&&5!==a.type||(this.decryptAvcSample(t,e,r,i,a),this.decrypter.isSync())))return}}},t}(),dn=188,cn=function(){function t(t,e,r){this.observer=void 0,this.config=void 0,this.typeSupported=void 0,this.sampleAes=null,this.pmtParsed=!1,this.audioCodec=void 0,this.videoCodec=void 0,this._duration=0,this._pmtId=-1,this._videoTrack=void 0,this._audioTrack=void 0,this._id3Track=void 0,this._txtTrack=void 0,this.aacOverFlow=null,this.remainderData=null,this.videoParser=void 0,this.observer=t,this.config=e,this.typeSupported=r,this.videoParser=new un}t.probe=function(e){var r=t.syncOffset(e);return r>0&&w.warn("MPEG2-TS detected but first sync word found @ offset "+r),-1!==r},t.syncOffset=function(t){for(var e=t.length,r=Math.min(940,e-dn)+1,i=0;i<r;){for(var n=!1,a=-1,s=0,o=i;o<e;o+=dn){if(71!==t[o]||e-o!==dn&&71!==t[o+dn]){if(s)return-1;break}if(s++,-1===a&&0!==(a=o)&&(r=Math.min(a+18612,t.length-dn)+1),n||(n=0===fn(t,o)),n&&s>1&&(0===a&&s>2||o+dn>r))return a}i++}return-1},t.createTrack=function(t,e){return{container:"video"===t||"audio"===t?"video/mp2t":void 0,type:t,id:wt[t],pid:-1,inputTimeScale:9e4,sequenceNumber:0,samples:[],dropped:0,duration:"audio"===t?e:void 0}};var e=t.prototype;return e.resetInitSegment=function(e,r,i,n){this.pmtParsed=!1,this._pmtId=-1,this._videoTrack=t.createTrack("video"),this._audioTrack=t.createTrack("audio",n),this._id3Track=t.createTrack("id3"),this._txtTrack=t.createTrack("text"),this._audioTrack.segmentCodec="aac",this.aacOverFlow=null,this.remainderData=null,this.audioCodec=r,this.videoCodec=i,this._duration=n},e.resetTimeStamp=function(){},e.resetContiguity=function(){var t=this._audioTrack,e=this._videoTrack,r=this._id3Track;t&&(t.pesData=null),e&&(e.pesData=null),r&&(r.pesData=null),this.aacOverFlow=null,this.remainderData=null},e.demux=function(e,r,i,n){var a;void 0===i&&(i=!1),void 0===n&&(n=!1),i||(this.sampleAes=null);var s=this._videoTrack,o=this._audioTrack,l=this._id3Track,u=this._txtTrack,h=s.pid,d=s.pesData,c=o.pid,f=l.pid,g=o.pesData,v=l.pesData,m=null,p=this.pmtParsed,y=this._pmtId,E=e.length;if(this.remainderData&&(E=(e=Wt(this.remainderData,e)).length,this.remainderData=null),E<dn&&!n)return this.remainderData=e,{audioTrack:o,videoTrack:s,id3Track:l,textTrack:u};var T=Math.max(0,t.syncOffset(e));(E-=(E-T)%dn)<e.byteLength&&!n&&(this.remainderData=new Uint8Array(e.buffer,E,e.buffer.byteLength-E));for(var S=0,L=T;L<E;L+=dn)if(71===e[L]){var A=!!(64&e[L+1]),R=fn(e,L),b=void 0;if((48&e[L+3])>>4>1){if((b=L+5+e[L+4])===L+dn)continue}else b=L+4;switch(R){case h:A&&(d&&(a=yn(d))&&this.videoParser.parseAVCPES(s,u,a,!1,this._duration),d={data:[],size:0}),d&&(d.data.push(e.subarray(b,L+dn)),d.size+=L+dn-b);break;case c:if(A){if(g&&(a=yn(g)))switch(o.segmentCodec){case"aac":this.parseAACPES(o,a);break;case"mp3":this.parseMPEGPES(o,a);break;case"ac3":this.parseAC3PES(o,a)}g={data:[],size:0}}g&&(g.data.push(e.subarray(b,L+dn)),g.size+=L+dn-b);break;case f:A&&(v&&(a=yn(v))&&this.parseID3PES(l,a),v={data:[],size:0}),v&&(v.data.push(e.subarray(b,L+dn)),v.size+=L+dn-b);break;case 0:A&&(b+=e[b]+1),y=this._pmtId=gn(e,b);break;case y:A&&(b+=e[b]+1);var k=vn(e,b,this.typeSupported,i,this.observer);(h=k.videoPid)>0&&(s.pid=h,s.segmentCodec=k.segmentVideoCodec),(c=k.audioPid)>0&&(o.pid=c,o.segmentCodec=k.segmentAudioCodec),(f=k.id3Pid)>0&&(l.pid=f),null===m||p||(w.warn("MPEG-TS PMT found at "+L+" after unknown PID '"+m+"'. Backtracking to sync byte @"+T+" to parse all TS packets."),m=null,L=T-188),p=this.pmtParsed=!0;break;case 17:case 8191:break;default:m=R}}else S++;S>0&&mn(this.observer,new Error("Found "+S+" TS packet/s that do not start with 0x47")),s.pesData=d,o.pesData=g,l.pesData=v;var D={audioTrack:o,videoTrack:s,id3Track:l,textTrack:u};return n&&this.extractRemainingSamples(D),D},e.flush=function(){var t,e=this.remainderData;return this.remainderData=null,t=e?this.demux(e,-1,!1,!0):{videoTrack:this._videoTrack,audioTrack:this._audioTrack,id3Track:this._id3Track,textTrack:this._txtTrack},this.extractRemainingSamples(t),this.sampleAes?this.decrypt(t,this.sampleAes):t},e.extractRemainingSamples=function(t){var e,r=t.audioTrack,i=t.videoTrack,n=t.id3Track,a=t.textTrack,s=i.pesData,o=r.pesData,l=n.pesData;if(s&&(e=yn(s))?(this.videoParser.parseAVCPES(i,a,e,!0,this._duration),i.pesData=null):i.pesData=s,o&&(e=yn(o))){switch(r.segmentCodec){case"aac":this.parseAACPES(r,e);break;case"mp3":this.parseMPEGPES(r,e);break;case"ac3":this.parseAC3PES(r,e)}r.pesData=null}else null!=o&&o.size&&w.log("last AAC PES packet truncated,might overlap between fragments"),r.pesData=o;l&&(e=yn(l))?(this.parseID3PES(n,e),n.pesData=null):n.pesData=l},e.demuxSampleAes=function(t,e,r){var i=this.demux(t,r,!0,!this.config.progressive),n=this.sampleAes=new hn(this.observer,this.config,e);return this.decrypt(i,n)},e.decrypt=function(t,e){return new Promise((function(r){var i=t.audioTrack,n=t.videoTrack;i.samples&&"aac"===i.segmentCodec?e.decryptAacSamples(i.samples,0,(function(){n.samples?e.decryptAvcSamples(n.samples,0,0,(function(){r(t)})):r(t)})):n.samples&&e.decryptAvcSamples(n.samples,0,0,(function(){r(t)}))}))},e.destroy=function(){this._duration=0},e.parseAACPES=function(t,e){var r,i,n,a=0,s=this.aacOverFlow,o=e.data;if(s){this.aacOverFlow=null;var l=s.missing,u=s.sample.unit.byteLength;if(-1===l)o=Wt(s.sample.unit,o);else{var h=u-l;s.sample.unit.set(o.subarray(0,l),h),t.samples.push(s.sample),a=s.missing}}for(r=a,i=o.length;r<i-1&&!Bi(o,r);r++);if(r!==a){var d,c=r<i-1;if(d=c?"AAC PES did not start with ADTS header,offset:"+r:"No ADTS header found in AAC PES",mn(this.observer,new Error(d),c),!c)return}if(Ki(t,this.observer,o,r,this.audioCodec),void 0!==e.pts)n=e.pts;else{if(!s)return void w.warn("[tsdemuxer]: AAC PES unknown PTS");var f=Hi(t.samplerate);n=s.sample.pts+f}for(var g,v=0;r<i;){if(r+=(g=Vi(t,o,r,n,v)).length,g.missing){this.aacOverFlow=g;break}for(v++;r<i-1&&!Bi(o,r);r++);}},e.parseMPEGPES=function(t,e){var r=e.data,i=r.length,n=0,a=0,s=e.pts;if(void 0!==s)for(;a<i;)if($i(r,a)){var o=zi(t,r,a,s,n);if(!o)break;a+=o.length,n++}else a++;else w.warn("[tsdemuxer]: MPEG PES unknown PTS")},e.parseAC3PES=function(t,e){var r=e.data,i=e.pts;if(void 0!==i)for(var n,a=r.length,s=0,o=0;o<a&&(n=sn(t,r,o,i,s++))>0;)o+=n;else w.warn("[tsdemuxer]: AC3 PES unknown PTS")},e.parseID3PES=function(t,e){if(void 0!==e.pts){var r=o({},e,{type:this._videoTrack?qe:We,duration:Number.POSITIVE_INFINITY});t.samples.push(r)}else w.warn("[tsdemuxer]: ID3 PES unknown PTS")},t}();function fn(t,e){return((31&t[e+1])<<8)+t[e+2]}function gn(t,e){return(31&t[e+10])<<8|t[e+11]}function vn(t,e,r,i,n){var a={audioPid:-1,videoPid:-1,id3Pid:-1,segmentVideoCodec:"avc",segmentAudioCodec:"aac"},s=e+3+((15&t[e+1])<<8|t[e+2])-4;for(e+=12+((15&t[e+10])<<8|t[e+11]);e<s;){var o=fn(t,e),l=(15&t[e+3])<<8|t[e+4];switch(t[e]){case 207:if(!i){pn("ADTS AAC");break}case 15:-1===a.audioPid&&(a.audioPid=o);break;case 21:-1===a.id3Pid&&(a.id3Pid=o);break;case 219:if(!i){pn("H.264");break}case 27:-1===a.videoPid&&(a.videoPid=o,a.segmentVideoCodec="avc");break;case 3:case 4:r.mpeg||r.mp3?-1===a.audioPid&&(a.audioPid=o,a.segmentAudioCodec="mp3"):w.log("MPEG audio found, not supported in this browser");break;case 193:if(!i){pn("AC-3");break}case 129:r.ac3?-1===a.audioPid&&(a.audioPid=o,a.segmentAudioCodec="ac3"):w.log("AC-3 audio found, not supported in this browser");break;case 6:if(-1===a.audioPid&&l>0)for(var u=e+5,h=l;h>2;){106===t[u]&&(!0!==r.ac3?w.log("AC-3 audio found, not supported in this browser for now"):(a.audioPid=o,a.segmentAudioCodec="ac3"));var d=t[u+1]+2;u+=d,h-=d}break;case 194:case 135:return mn(n,new Error("Unsupported EC-3 in M2TS found")),a;case 36:return mn(n,new Error("Unsupported HEVC in M2TS found")),a}e+=l+5}return a}function mn(t,e,r){w.warn("parsing error: "+e.message),t.emit(S.ERROR,S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!1,levelRetry:r,error:e,reason:e.message})}function pn(t){w.log(t+" with AES-128-CBC encryption found in unencrypted stream")}function yn(t){var e,r,i,n,a,s=0,o=t.data;if(!t||0===t.size)return null;for(;o[0].length<19&&o.length>1;)o[0]=Wt(o[0],o[1]),o.splice(1,1);if(1===((e=o[0])[0]<<16)+(e[1]<<8)+e[2]){if((r=(e[4]<<8)+e[5])&&r>t.size-6)return null;var l=e[7];192&l&&(n=536870912*(14&e[9])+4194304*(255&e[10])+16384*(254&e[11])+128*(255&e[12])+(254&e[13])/2,64&l?n-(a=536870912*(14&e[14])+4194304*(255&e[15])+16384*(254&e[16])+128*(255&e[17])+(254&e[18])/2)>54e5&&(w.warn(Math.round((n-a)/9e4)+"s delta between PTS and DTS, align them"),n=a):a=n);var u=(i=e[8])+9;if(t.size<=u)return null;t.size-=u;for(var h=new Uint8Array(t.size),d=0,c=o.length;d<c;d++){var f=(e=o[d]).byteLength;if(u){if(u>f){u-=f;continue}e=e.subarray(u),f-=u,u=0}h.set(e,s),s+=f}return r&&(r-=i+3),{data:h,pts:n,dts:a,len:r}}return null}var En=function(t){function e(){return t.apply(this,arguments)||this}l(e,t);var r=e.prototype;return r.resetInitSegment=function(e,r,i,n){t.prototype.resetInitSegment.call(this,e,r,i,n),this._audioTrack={container:"audio/mpeg",type:"audio",id:2,pid:-1,sequenceNumber:0,segmentCodec:"mp3",samples:[],manifestCodec:r,duration:n,inputTimeScale:9e4,dropped:0}},e.probe=function(t){if(!t)return!1;var e=ct(t,0),r=(null==e?void 0:e.length)||0;if(e&&11===t[r]&&119===t[r+1]&&void 0!==vt(e)&&nn(t,r)<=16)return!1;for(var i=t.length;r<i;r++)if(Zi(t,r))return w.log("MPEG Audio sync word found !"),!0;return!1},r.canParse=function(t,e){return function(t,e){return Ji(t,e)&&4<=t.length-e}(t,e)},r.appendFrame=function(t,e,r){if(null!==this.basePTS)return zi(t,e,r,this.basePTS,this.frameIndex)},e}(Fi),Tn=function(){function t(){}return t.getSilentFrame=function(t,e){if("mp4a.40.2"===t){if(1===e)return new Uint8Array([0,200,0,128,35,128]);if(2===e)return new Uint8Array([33,0,73,144,2,25,0,35,128]);if(3===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,142]);if(4===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,128,44,128,8,2,56]);if(5===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,56]);if(6===e)return new Uint8Array([0,200,0,128,32,132,1,38,64,8,100,0,130,48,4,153,0,33,144,2,0,178,0,32,8,224])}else{if(1===e)return new Uint8Array([1,64,34,128,163,78,230,128,186,8,0,0,0,28,6,241,193,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(2===e)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94]);if(3===e)return new Uint8Array([1,64,34,128,163,94,230,128,186,8,0,0,0,0,149,0,6,241,161,10,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,90,94])}},t}(),Sn=Math.pow(2,32)-1,Ln=function(){function t(){}return t.init=function(){var e;for(e in t.types={avc1:[],avcC:[],btrt:[],dinf:[],dref:[],esds:[],ftyp:[],hdlr:[],mdat:[],mdhd:[],mdia:[],mfhd:[],minf:[],moof:[],moov:[],mp4a:[],".mp3":[],dac3:[],"ac-3":[],mvex:[],mvhd:[],pasp:[],sdtp:[],stbl:[],stco:[],stsc:[],stsd:[],stsz:[],stts:[],tfdt:[],tfhd:[],traf:[],trak:[],trun:[],trex:[],tkhd:[],vmhd:[],smhd:[]},t.types)t.types.hasOwnProperty(e)&&(t.types[e]=[e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2),e.charCodeAt(3)]);var r=new Uint8Array([0,0,0,0,0,0,0,0,118,105,100,101,0,0,0,0,0,0,0,0,0,0,0,0,86,105,100,101,111,72,97,110,100,108,101,114,0]),i=new Uint8Array([0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0,83,111,117,110,100,72,97,110,100,108,101,114,0]);t.HDLR_TYPES={video:r,audio:i};var n=new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,12,117,114,108,32,0,0,0,1]),a=new Uint8Array([0,0,0,0,0,0,0,0]);t.STTS=t.STSC=t.STCO=a,t.STSZ=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0]),t.VMHD=new Uint8Array([0,0,0,1,0,0,0,0,0,0,0,0]),t.SMHD=new Uint8Array([0,0,0,0,0,0,0,0]),t.STSD=new Uint8Array([0,0,0,0,0,0,0,1]);var s=new Uint8Array([105,115,111,109]),o=new Uint8Array([97,118,99,49]),l=new Uint8Array([0,0,0,1]);t.FTYP=t.box(t.types.ftyp,s,l,s,o),t.DINF=t.box(t.types.dinf,t.box(t.types.dref,n))},t.box=function(t){for(var e=8,r=arguments.length,i=new Array(r>1?r-1:0),n=1;n<r;n++)i[n-1]=arguments[n];for(var a=i.length,s=a;a--;)e+=i[a].byteLength;var o=new Uint8Array(e);for(o[0]=e>>24&255,o[1]=e>>16&255,o[2]=e>>8&255,o[3]=255&e,o.set(t,4),a=0,e=8;a<s;a++)o.set(i[a],e),e+=i[a].byteLength;return o},t.hdlr=function(e){return t.box(t.types.hdlr,t.HDLR_TYPES[e])},t.mdat=function(e){return t.box(t.types.mdat,e)},t.mdhd=function(e,r){r*=e;var i=Math.floor(r/(Sn+1)),n=Math.floor(r%(Sn+1));return t.box(t.types.mdhd,new Uint8Array([1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,e>>24&255,e>>16&255,e>>8&255,255&e,i>>24,i>>16&255,i>>8&255,255&i,n>>24,n>>16&255,n>>8&255,255&n,85,196,0,0]))},t.mdia=function(e){return t.box(t.types.mdia,t.mdhd(e.timescale,e.duration),t.hdlr(e.type),t.minf(e))},t.mfhd=function(e){return t.box(t.types.mfhd,new Uint8Array([0,0,0,0,e>>24,e>>16&255,e>>8&255,255&e]))},t.minf=function(e){return"audio"===e.type?t.box(t.types.minf,t.box(t.types.smhd,t.SMHD),t.DINF,t.stbl(e)):t.box(t.types.minf,t.box(t.types.vmhd,t.VMHD),t.DINF,t.stbl(e))},t.moof=function(e,r,i){return t.box(t.types.moof,t.mfhd(e),t.traf(i,r))},t.moov=function(e){for(var r=e.length,i=[];r--;)i[r]=t.trak(e[r]);return t.box.apply(null,[t.types.moov,t.mvhd(e[0].timescale,e[0].duration)].concat(i).concat(t.mvex(e)))},t.mvex=function(e){for(var r=e.length,i=[];r--;)i[r]=t.trex(e[r]);return t.box.apply(null,[t.types.mvex].concat(i))},t.mvhd=function(e,r){r*=e;var i=Math.floor(r/(Sn+1)),n=Math.floor(r%(Sn+1)),a=new Uint8Array([1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,e>>24&255,e>>16&255,e>>8&255,255&e,i>>24,i>>16&255,i>>8&255,255&i,n>>24,n>>16&255,n>>8&255,255&n,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255]);return t.box(t.types.mvhd,a)},t.sdtp=function(e){var r,i,n=e.samples||[],a=new Uint8Array(4+n.length);for(r=0;r<n.length;r++)i=n[r].flags,a[r+4]=i.dependsOn<<4|i.isDependedOn<<2|i.hasRedundancy;return t.box(t.types.sdtp,a)},t.stbl=function(e){return t.box(t.types.stbl,t.stsd(e),t.box(t.types.stts,t.STTS),t.box(t.types.stsc,t.STSC),t.box(t.types.stsz,t.STSZ),t.box(t.types.stco,t.STCO))},t.avc1=function(e){var r,i,n,a=[],s=[];for(r=0;r<e.sps.length;r++)n=(i=e.sps[r]).byteLength,a.push(n>>>8&255),a.push(255&n),a=a.concat(Array.prototype.slice.call(i));for(r=0;r<e.pps.length;r++)n=(i=e.pps[r]).byteLength,s.push(n>>>8&255),s.push(255&n),s=s.concat(Array.prototype.slice.call(i));var o=t.box(t.types.avcC,new Uint8Array([1,a[3],a[4],a[5],255,224|e.sps.length].concat(a).concat([e.pps.length]).concat(s))),l=e.width,u=e.height,h=e.pixelRatio[0],d=e.pixelRatio[1];return t.box(t.types.avc1,new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,l>>8&255,255&l,u>>8&255,255&u,0,72,0,0,0,72,0,0,0,0,0,0,0,1,18,100,97,105,108,121,109,111,116,105,111,110,47,104,108,115,46,106,115,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,17,17]),o,t.box(t.types.btrt,new Uint8Array([0,28,156,128,0,45,198,192,0,45,198,192])),t.box(t.types.pasp,new Uint8Array([h>>24,h>>16&255,h>>8&255,255&h,d>>24,d>>16&255,d>>8&255,255&d])))},t.esds=function(t){var e=t.config.length;return new Uint8Array([0,0,0,0,3,23+e,0,1,0,4,15+e,64,21,0,0,0,0,0,0,0,0,0,0,0,5].concat([e]).concat(t.config).concat([6,1,2]))},t.audioStsd=function(t){var e=t.samplerate;return new Uint8Array([0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,t.channelCount,0,16,0,0,0,0,e>>8&255,255&e,0,0])},t.mp4a=function(e){return t.box(t.types.mp4a,t.audioStsd(e),t.box(t.types.esds,t.esds(e)))},t.mp3=function(e){return t.box(t.types[".mp3"],t.audioStsd(e))},t.ac3=function(e){return t.box(t.types["ac-3"],t.audioStsd(e),t.box(t.types.dac3,e.config))},t.stsd=function(e){return"audio"===e.type?"mp3"===e.segmentCodec&&"mp3"===e.codec?t.box(t.types.stsd,t.STSD,t.mp3(e)):"ac3"===e.segmentCodec?t.box(t.types.stsd,t.STSD,t.ac3(e)):t.box(t.types.stsd,t.STSD,t.mp4a(e)):t.box(t.types.stsd,t.STSD,t.avc1(e))},t.tkhd=function(e){var r=e.id,i=e.duration*e.timescale,n=e.width,a=e.height,s=Math.floor(i/(Sn+1)),o=Math.floor(i%(Sn+1));return t.box(t.types.tkhd,new Uint8Array([1,0,0,7,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,3,r>>24&255,r>>16&255,r>>8&255,255&r,0,0,0,0,s>>24,s>>16&255,s>>8&255,255&s,o>>24,o>>16&255,o>>8&255,255&o,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,0,0,0,n>>8&255,255&n,0,0,a>>8&255,255&a,0,0]))},t.traf=function(e,r){var i=t.sdtp(e),n=e.id,a=Math.floor(r/(Sn+1)),s=Math.floor(r%(Sn+1));return t.box(t.types.traf,t.box(t.types.tfhd,new Uint8Array([0,0,0,0,n>>24,n>>16&255,n>>8&255,255&n])),t.box(t.types.tfdt,new Uint8Array([1,0,0,0,a>>24,a>>16&255,a>>8&255,255&a,s>>24,s>>16&255,s>>8&255,255&s])),t.trun(e,i.length+16+20+8+16+8+8),i)},t.trak=function(e){return e.duration=e.duration||4294967295,t.box(t.types.trak,t.tkhd(e),t.mdia(e))},t.trex=function(e){var r=e.id;return t.box(t.types.trex,new Uint8Array([0,0,0,0,r>>24,r>>16&255,r>>8&255,255&r,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1]))},t.trun=function(e,r){var i,n,a,s,o,l,u=e.samples||[],h=u.length,d=12+16*h,c=new Uint8Array(d);for(r+=8+d,c.set(["video"===e.type?1:0,0,15,1,h>>>24&255,h>>>16&255,h>>>8&255,255&h,r>>>24&255,r>>>16&255,r>>>8&255,255&r],0),i=0;i<h;i++)a=(n=u[i]).duration,s=n.size,o=n.flags,l=n.cts,c.set([a>>>24&255,a>>>16&255,a>>>8&255,255&a,s>>>24&255,s>>>16&255,s>>>8&255,255&s,o.isLeading<<2|o.dependsOn,o.isDependedOn<<6|o.hasRedundancy<<4|o.paddingValue<<1|o.isNonSync,61440&o.degradPrio,15&o.degradPrio,l>>>24&255,l>>>16&255,l>>>8&255,255&l],12+16*i);return t.box(t.types.trun,c)},t.initSegment=function(e){t.types||t.init();var r=t.moov(e);return Wt(t.FTYP,r)},t}();Ln.types=void 0,Ln.HDLR_TYPES=void 0,Ln.STTS=void 0,Ln.STSC=void 0,Ln.STCO=void 0,Ln.STSZ=void 0,Ln.VMHD=void 0,Ln.SMHD=void 0,Ln.STSD=void 0,Ln.FTYP=void 0,Ln.DINF=void 0;var An=9e4;function Rn(t,e,r,i){void 0===r&&(r=1),void 0===i&&(i=!1);var n=t*e*r;return i?Math.round(n):n}function bn(t,e){return void 0===e&&(e=!1),Rn(t,1e3,1/An,e)}var kn=null,Dn=null,In=function(){function t(t,e,r,i){if(this.observer=void 0,this.config=void 0,this.typeSupported=void 0,this.ISGenerated=!1,this._initPTS=null,this._initDTS=null,this.nextAvcDts=null,this.nextAudioPts=null,this.videoSampleDuration=null,this.isAudioContiguous=!1,this.isVideoContiguous=!1,this.videoTrackConfig=void 0,this.observer=t,this.config=e,this.typeSupported=r,this.ISGenerated=!1,null===kn){var n=(navigator.userAgent||"").match(/Chrome\/(\d+)/i);kn=n?parseInt(n[1]):0}if(null===Dn){var a=navigator.userAgent.match(/Safari\/(\d+)/i);Dn=a?parseInt(a[1]):0}}var e=t.prototype;return e.destroy=function(){this.config=this.videoTrackConfig=this._initPTS=this._initDTS=null},e.resetTimeStamp=function(t){w.log("[mp4-remuxer]: initPTS & initDTS reset"),this._initPTS=this._initDTS=t},e.resetNextTimestamp=function(){w.log("[mp4-remuxer]: reset next timestamp"),this.isVideoContiguous=!1,this.isAudioContiguous=!1},e.resetInitSegment=function(){w.log("[mp4-remuxer]: ISGenerated flag reset"),this.ISGenerated=!1,this.videoTrackConfig=void 0},e.getVideoStartPts=function(t){var e=!1,r=t.reduce((function(t,r){var i=r.pts-t;return i<-4294967296?(e=!0,wn(t,r.pts)):i>0?t:r.pts}),t[0].pts);return e&&w.debug("PTS rollover detected"),r},e.remux=function(t,e,r,i,n,a,s,o){var l,u,h,d,c,f,g=n,v=n,m=t.pid>-1,p=e.pid>-1,y=e.samples.length,E=t.samples.length>0,T=s&&y>0||y>1;if((!m||E)&&(!p||T)||this.ISGenerated||s){if(this.ISGenerated){var S,L,A,R,b=this.videoTrackConfig;!b||e.width===b.width&&e.height===b.height&&(null==(S=e.pixelRatio)?void 0:S[0])===(null==(L=b.pixelRatio)?void 0:L[0])&&(null==(A=e.pixelRatio)?void 0:A[1])===(null==(R=b.pixelRatio)?void 0:R[1])||this.resetInitSegment()}else h=this.generateIS(t,e,n,a);var k,D=this.isVideoContiguous,I=-1;if(T&&(I=function(t){for(var e=0;e<t.length;e++)if(t[e].key)return e;return-1}(e.samples),!D&&this.config.forceKeyFrameOnDiscontinuity))if(f=!0,I>0){w.warn("[mp4-remuxer]: Dropped "+I+" out of "+y+" video samples due to a missing keyframe");var C=this.getVideoStartPts(e.samples);e.samples=e.samples.slice(I),e.dropped+=I,k=v+=(e.samples[0].pts-C)/e.inputTimeScale}else-1===I&&(w.warn("[mp4-remuxer]: No keyframe found out of "+y+" video samples"),f=!1);if(this.ISGenerated){if(E&&T){var _=this.getVideoStartPts(e.samples),x=(wn(t.samples[0].pts,_)-_)/e.inputTimeScale;g+=Math.max(0,x),v+=Math.max(0,-x)}if(E){if(t.samplerate||(w.warn("[mp4-remuxer]: regenerate InitSegment as audio detected"),h=this.generateIS(t,e,n,a)),u=this.remuxAudio(t,g,this.isAudioContiguous,a,p||T||o===Me?v:void 0),T){var P=u?u.endPTS-u.startPTS:0;e.inputTimeScale||(w.warn("[mp4-remuxer]: regenerate InitSegment as video detected"),h=this.generateIS(t,e,n,a)),l=this.remuxVideo(e,v,D,P)}}else T&&(l=this.remuxVideo(e,v,D,0));l&&(l.firstKeyFrame=I,l.independent=-1!==I,l.firstKeyFramePTS=k)}}return this.ISGenerated&&this._initPTS&&this._initDTS&&(r.samples.length&&(c=Cn(r,n,this._initPTS,this._initDTS)),i.samples.length&&(d=_n(i,n,this._initPTS))),{audio:u,video:l,initSegment:h,independent:f,text:d,id3:c}},e.generateIS=function(t,e,r,i){var n,a,s,o=t.samples,l=e.samples,u=this.typeSupported,h={},d=this._initPTS,c=!d||i,f="audio/mp4";if(c&&(n=a=1/0),t.config&&o.length){switch(t.timescale=t.samplerate,t.segmentCodec){case"mp3":u.mpeg?(f="audio/mpeg",t.codec=""):u.mp3&&(t.codec="mp3");break;case"ac3":t.codec="ac-3"}h.audio={id:"audio",container:f,codec:t.codec,initSegment:"mp3"===t.segmentCodec&&u.mpeg?new Uint8Array(0):Ln.initSegment([t]),metadata:{channelCount:t.channelCount}},c&&(s=t.inputTimeScale,d&&s===d.timescale?c=!1:n=a=o[0].pts-Math.round(s*r))}if(e.sps&&e.pps&&l.length){if(e.timescale=e.inputTimeScale,h.video={id:"main",container:"video/mp4",codec:e.codec,initSegment:Ln.initSegment([e]),metadata:{width:e.width,height:e.height}},c)if(s=e.inputTimeScale,d&&s===d.timescale)c=!1;else{var g=this.getVideoStartPts(l),v=Math.round(s*r);a=Math.min(a,wn(l[0].dts,g)-v),n=Math.min(n,g-v)}this.videoTrackConfig={width:e.width,height:e.height,pixelRatio:e.pixelRatio}}if(Object.keys(h).length)return this.ISGenerated=!0,c?(this._initPTS={baseTime:n,timescale:s},this._initDTS={baseTime:a,timescale:s}):n=s=void 0,{tracks:h,initPTS:n,timescale:s}},e.remuxVideo=function(t,e,r,i){var n,a,s=t.inputTimeScale,l=t.samples,u=[],h=l.length,d=this._initPTS,c=this.nextAvcDts,f=8,g=this.videoSampleDuration,v=Number.POSITIVE_INFINITY,m=Number.NEGATIVE_INFINITY,p=!1;if(!r||null===c){var y=e*s,E=l[0].pts-wn(l[0].dts,l[0].pts);kn&&null!==c&&Math.abs(y-E-c)<15e3?r=!0:c=y-E}for(var T=d.baseTime*s/d.timescale,R=0;R<h;R++){var b=l[R];b.pts=wn(b.pts-T,c),b.dts=wn(b.dts-T,c),b.dts<l[R>0?R-1:R].dts&&(p=!0)}p&&l.sort((function(t,e){var r=t.dts-e.dts,i=t.pts-e.pts;return r||i})),n=l[0].dts;var k=(a=l[l.length-1].dts)-n,D=k?Math.round(k/(h-1)):g||t.inputTimeScale/30;if(r){var I=n-c,C=I>D,_=I<-1;if((C||_)&&(C?w.warn("AVC: "+bn(I,!0)+" ms ("+I+"dts) hole between fragments detected at "+e.toFixed(3)):w.warn("AVC: "+bn(-I,!0)+" ms ("+I+"dts) overlapping between fragments detected at "+e.toFixed(3)),!_||c>=l[0].pts||kn)){n=c;var x=l[0].pts-I;if(C)l[0].dts=n,l[0].pts=x;else for(var P=0;P<l.length&&!(l[P].dts>x);P++)l[P].dts-=I,l[P].pts-=I;w.log("Video: Initial PTS/DTS adjusted: "+bn(x,!0)+"/"+bn(n,!0)+", delta: "+bn(I,!0)+" ms")}}for(var F=0,M=0,O=n=Math.max(0,n),N=0;N<h;N++){for(var U=l[N],B=U.units,G=B.length,K=0,H=0;H<G;H++)K+=B[H].data.length;M+=K,F+=G,U.length=K,U.dts<O?(U.dts=O,O+=D/4|0||1):O=U.dts,v=Math.min(U.pts,v),m=Math.max(U.pts,m)}a=l[h-1].dts;var V,Y=M+4*F+8;try{V=new Uint8Array(Y)}catch(t){return void this.observer.emit(S.ERROR,S.ERROR,{type:L.MUX_ERROR,details:A.REMUX_ALLOC_ERROR,fatal:!1,error:t,bytes:Y,reason:"fail allocating video mdat "+Y})}var W=new DataView(V.buffer);W.setUint32(0,Y),V.set(Ln.types.mdat,4);for(var j=!1,q=Number.POSITIVE_INFINITY,X=Number.POSITIVE_INFINITY,z=Number.NEGATIVE_INFINITY,Q=Number.NEGATIVE_INFINITY,J=0;J<h;J++){for(var $=l[J],Z=$.units,tt=0,et=0,rt=Z.length;et<rt;et++){var it=Z[et],nt=it.data,at=it.data.byteLength;W.setUint32(f,at),f+=4,V.set(nt,f),f+=at,tt+=4+at}var st=void 0;if(J<h-1)g=l[J+1].dts-$.dts,st=l[J+1].pts-$.pts;else{var ot=this.config,lt=J>0?$.dts-l[J-1].dts:D;if(st=J>0?$.pts-l[J-1].pts:D,ot.stretchShortVideoTrack&&null!==this.nextAudioPts){var ut=Math.floor(ot.maxBufferHole*s),ht=(i?v+i*s:this.nextAudioPts)-$.pts;ht>ut?((g=ht-lt)<0?g=lt:j=!0,w.log("[mp4-remuxer]: It is approximately "+ht/90+" ms to the next segment; using duration "+g/90+" ms for the last video frame.")):g=lt}else g=lt}var dt=Math.round($.pts-$.dts);q=Math.min(q,g),z=Math.max(z,g),X=Math.min(X,st),Q=Math.max(Q,st),u.push(new Pn($.key,g,tt,dt))}if(u.length)if(kn){if(kn<70){var ct=u[0].flags;ct.dependsOn=2,ct.isNonSync=0}}else if(Dn&&Q-X<z-q&&D/z<.025&&0===u[0].cts){w.warn("Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.");for(var ft=n,gt=0,vt=u.length;gt<vt;gt++){var mt=ft+u[gt].duration,pt=ft+u[gt].cts;if(gt<vt-1){var yt=mt+u[gt+1].cts;u[gt].duration=yt-pt}else u[gt].duration=gt?u[gt-1].duration:D;u[gt].cts=0,ft=mt}}g=j||!g?D:g,this.nextAvcDts=c=a+g,this.videoSampleDuration=g,this.isVideoContiguous=!0;var Et={data1:Ln.moof(t.sequenceNumber++,n,o({},t,{samples:u})),data2:V,startPTS:v/s,endPTS:(m+g)/s,startDTS:n/s,endDTS:c/s,type:"video",hasAudio:!1,hasVideo:!0,nb:u.length,dropped:t.dropped};return t.samples=[],t.dropped=0,Et},e.getSamplesPerFrame=function(t){switch(t.segmentCodec){case"mp3":return 1152;case"ac3":return 1536;default:return 1024}},e.remuxAudio=function(t,e,r,i,n){var a=t.inputTimeScale,s=a/(t.samplerate?t.samplerate:a),l=this.getSamplesPerFrame(t),u=l*s,h=this._initPTS,d="mp3"===t.segmentCodec&&this.typeSupported.mpeg,c=[],f=void 0!==n,g=t.samples,v=d?0:8,m=this.nextAudioPts||-1,p=e*a,y=h.baseTime*a/h.timescale;if(this.isAudioContiguous=r=r||g.length&&m>0&&(i&&Math.abs(p-m)<9e3||Math.abs(wn(g[0].pts-y,p)-m)<20*u),g.forEach((function(t){t.pts=wn(t.pts-y,p)})),!r||m<0){if(g=g.filter((function(t){return t.pts>=0})),!g.length)return;m=0===n?0:i&&!f?Math.max(0,p):g[0].pts}if("aac"===t.segmentCodec)for(var E=this.config.maxAudioFramesDrift,T=0,R=m;T<g.length;T++){var b=g[T],k=b.pts,D=k-R,I=Math.abs(1e3*D/a);if(D<=-E*u&&f)0===T&&(w.warn("Audio frame @ "+(k/a).toFixed(3)+"s overlaps nextAudioPts by "+Math.round(1e3*D/a)+" ms."),this.nextAudioPts=m=R=k);else if(D>=E*u&&I<1e4&&f){var C=Math.round(D/u);(R=k-C*u)<0&&(C--,R+=u),0===T&&(this.nextAudioPts=m=R),w.warn("[mp4-remuxer]: Injecting "+C+" audio frame @ "+(R/a).toFixed(3)+"s due to "+Math.round(1e3*D/a)+" ms gap.");for(var _=0;_<C;_++){var x=Math.max(R,0),P=Tn.getSilentFrame(t.manifestCodec||t.codec,t.channelCount);P||(w.log("[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead."),P=b.unit.subarray()),g.splice(T,0,{unit:P,pts:x}),R+=u,T++}}b.pts=R,R+=u}for(var F,M=null,O=null,N=0,U=g.length;U--;)N+=g[U].unit.byteLength;for(var B=0,G=g.length;B<G;B++){var K=g[B],H=K.unit,V=K.pts;if(null!==O)c[B-1].duration=Math.round((V-O)/s);else{if(r&&"aac"===t.segmentCodec&&(V=m),M=V,!(N>0))return;N+=v;try{F=new Uint8Array(N)}catch(t){return void this.observer.emit(S.ERROR,S.ERROR,{type:L.MUX_ERROR,details:A.REMUX_ALLOC_ERROR,fatal:!1,error:t,bytes:N,reason:"fail allocating audio mdat "+N})}d||(new DataView(F.buffer).setUint32(0,N),F.set(Ln.types.mdat,4))}F.set(H,v);var Y=H.byteLength;v+=Y,c.push(new Pn(!0,l,Y,0)),O=V}var W=c.length;if(W){var j=c[c.length-1];this.nextAudioPts=m=O+s*j.duration;var q=d?new Uint8Array(0):Ln.moof(t.sequenceNumber++,M/s,o({},t,{samples:c}));t.samples=[];var X=M/a,z=m/a,Q={data1:q,data2:F,startPTS:X,endPTS:z,startDTS:X,endDTS:z,type:"audio",hasAudio:!0,hasVideo:!1,nb:W};return this.isAudioContiguous=!0,Q}},e.remuxEmptyAudio=function(t,e,r,i){var n=t.inputTimeScale,a=n/(t.samplerate?t.samplerate:n),s=this.nextAudioPts,o=this._initDTS,l=9e4*o.baseTime/o.timescale,u=(null!==s?s:i.startDTS*n)+l,h=i.endDTS*n+l,d=1024*a,c=Math.ceil((h-u)/d),f=Tn.getSilentFrame(t.manifestCodec||t.codec,t.channelCount);if(w.warn("[mp4-remuxer]: remux empty Audio"),f){for(var g=[],v=0;v<c;v++){var m=u+v*d;g.push({unit:f,pts:m,dts:m})}return t.samples=g,this.remuxAudio(t,e,r,!1)}w.trace("[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec")},t}();function wn(t,e){var r;if(null===e)return t;for(r=e<t?-8589934592:8589934592;Math.abs(t-e)>4294967296;)t+=r;return t}function Cn(t,e,r,i){var n=t.samples.length;if(n){for(var a=t.inputTimeScale,s=0;s<n;s++){var o=t.samples[s];o.pts=wn(o.pts-r.baseTime*a/r.timescale,e*a)/a,o.dts=wn(o.dts-i.baseTime*a/i.timescale,e*a)/a}var l=t.samples;return t.samples=[],{samples:l}}}function _n(t,e,r){var i=t.samples.length;if(i){for(var n=t.inputTimeScale,a=0;a<i;a++){var s=t.samples[a];s.pts=wn(s.pts-r.baseTime*n/r.timescale,e*n)/n}t.samples.sort((function(t,e){return t.pts-e.pts}));var o=t.samples;return t.samples=[],{samples:o}}}var xn,Pn=function(t,e,r,i){this.size=void 0,this.duration=void 0,this.cts=void 0,this.flags=void 0,this.duration=e,this.size=r,this.cts=i,this.flags={isLeading:0,isDependedOn:0,hasRedundancy:0,degradPrio:0,dependsOn:t?2:1,isNonSync:t?0:1}},Fn=function(){function t(){this.emitInitSegment=!1,this.audioCodec=void 0,this.videoCodec=void 0,this.initData=void 0,this.initPTS=null,this.initTracks=void 0,this.lastEndTime=null}var e=t.prototype;return e.destroy=function(){},e.resetTimeStamp=function(t){this.initPTS=t,this.lastEndTime=null},e.resetNextTimestamp=function(){this.lastEndTime=null},e.resetInitSegment=function(t,e,r,i){this.audioCodec=e,this.videoCodec=r,this.generateInitSegment(function(t,e){if(!t||!e)return t;var r=e.keyId;return r&&e.isCommonEncryption&&Ot(t,["moov","trak"]).forEach((function(t){var e=Ot(t,["mdia","minf","stbl","stsd"])[0].subarray(8),i=Ot(e,["enca"]),n=i.length>0;n||(i=Ot(e,["encv"])),i.forEach((function(t){Ot(n?t.subarray(28):t.subarray(78),["sinf"]).forEach((function(t){var e=Vt(t);if(e){var i=e.subarray(8,24);i.some((function(t){return 0!==t}))||(w.log("[eme] Patching keyId in 'enc"+(n?"a":"v")+">sinf>>tenc' box: "+kt.hexDump(i)+" -> "+kt.hexDump(r)),e.set(r,8))}}))}))})),t}(t,i)),this.emitInitSegment=!0},e.generateInitSegment=function(t){var e=this.audioCodec,r=this.videoCodec;if(null==t||!t.byteLength)return this.initTracks=void 0,void(this.initData=void 0);var i=this.initData=Ut(t);i.audio&&(e=Mn(i.audio,O)),i.video&&(r=Mn(i.video,N));var n={};i.audio&&i.video?n.audiovideo={container:"video/mp4",codec:e+","+r,initSegment:t,id:"main"}:i.audio?n.audio={container:"audio/mp4",codec:e,initSegment:t,id:"audio"}:i.video?n.video={container:"video/mp4",codec:r,initSegment:t,id:"main"}:w.warn("[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes."),this.initTracks=n},e.remux=function(t,e,r,i,n,a){var s,o,l=this.initPTS,u=this.lastEndTime,h={audio:void 0,video:void 0,text:i,id3:r,initSegment:void 0};y(u)||(u=this.lastEndTime=n||0);var d=e.samples;if(null==d||!d.length)return h;var c={initPTS:void 0,timescale:1},f=this.initData;if(null!=(s=f)&&s.length||(this.generateInitSegment(d),f=this.initData),null==(o=f)||!o.length)return w.warn("[passthrough-remuxer.ts]: Failed to generate initSegment."),h;this.emitInitSegment&&(c.tracks=this.initTracks,this.emitInitSegment=!1);var g=function(t,e){for(var r=0,i=0,n=0,a=Ot(t,["moof","traf"]),s=0;s<a.length;s++){var o=a[s],l=Ot(o,["tfhd"])[0],u=e[xt(l,4)];if(u){var h=u.default,d=xt(l,0)|(null==h?void 0:h.flags),c=null==h?void 0:h.duration;8&d&&(c=xt(l,2&d?12:8));for(var f=u.timescale||9e4,g=Ot(o,["trun"]),v=0;v<g.length;v++)!(r=Yt(g[v]))&&c&&(r=c*xt(g[v],4)),u.type===N?i+=r/f:u.type===O&&(n+=r/f)}}if(0===i&&0===n){for(var m=1/0,p=0,E=0,T=Ot(t,["sidx"]),S=0;S<T.length;S++){var L=Nt(T[S]);if(null!=L&&L.references){m=Math.min(m,L.earliestPresentationTime/L.timescale);var A=L.references.reduce((function(t,e){return t+e.info.duration||0}),0);E=(p=Math.max(p,A+L.earliestPresentationTime/L.timescale))-m}}if(E&&y(E))return E}return i||n}(d,f),v=function(t,e){return Ot(e,["moof","traf"]).reduce((function(e,r){var i=Ot(r,["tfdt"])[0],n=i[0],a=Ot(r,["tfhd"]).reduce((function(e,r){var a=xt(r,4),s=t[a];if(s){var o=xt(i,4);if(1===n){if(o===Dt)return w.warn("[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time"),e;o*=Dt+1,o+=xt(i,8)}var l=o/(s.timescale||9e4);if(y(l)&&(null===e||l<e))return l}return e}),null);return null!==a&&y(a)&&(null===e||a<e)?a:e}),null)}(f,d),m=null===v?n:v;(function(t,e,r,i){if(null===t)return!0;var n=Math.max(i,1),a=e-t.baseTime/t.timescale;return Math.abs(a-r)>n}(l,m,n,g)||c.timescale!==l.timescale&&a)&&(c.initPTS=m-n,l&&1===l.timescale&&w.warn("Adjusting initPTS by "+(c.initPTS-l.baseTime)),this.initPTS=l={baseTime:c.initPTS,timescale:1});var p=t?m-l.baseTime/l.timescale:u,E=p+g;!function(t,e,r){Ot(e,["moof","traf"]).forEach((function(e){Ot(e,["tfhd"]).forEach((function(i){var n=xt(i,4),a=t[n];if(a){var s=a.timescale||9e4;Ot(e,["tfdt"]).forEach((function(t){var e=t[0],i=r*s;if(i){var n=xt(t,4);if(0===e)n-=i,Mt(t,4,n=Math.max(n,0));else{n*=Math.pow(2,32),n+=xt(t,8),n-=i,n=Math.max(n,0);var a=Math.floor(n/(Dt+1)),o=Math.floor(n%(Dt+1));Mt(t,4,a),Mt(t,8,o)}}}))}}))}))}(f,d,l.baseTime/l.timescale),g>0?this.lastEndTime=E:(w.warn("Duration parsed from mp4 should be greater than zero"),this.resetNextTimestamp());var T=!!f.audio,S=!!f.video,L="";T&&(L+="audio"),S&&(L+="video");var A={data1:d,startPTS:p,startDTS:p,endPTS:E,endDTS:E,type:L,hasAudio:T,hasVideo:S,nb:1,dropped:0};return h.audio="audio"===A.type?A:void 0,h.video="audio"!==A.type?A:void 0,h.initSegment=c,h.id3=Cn(r,n,l,l),i.samples.length&&(h.text=_n(i,n,l)),h},t}();function Mn(t,e){var r=null==t?void 0:t.codec;if(r&&r.length>4)return r;if(e===O){if("ec-3"===r||"ac-3"===r||"alac"===r)return r;if("fLaC"===r||"Opus"===r)return ve(r,!1);var i="mp4a.40.5";return w.info('Parsed audio codec "'+r+'" or audio object type not handled. Using "'+i+'"'),i}return w.warn('Unhandled video codec "'+r+'"'),"hvc1"===r||"hev1"===r?"hvc1.1.6.L120.90":"av01"===r?"av01.0.04M.08":"avc1.42e01e"}try{xn=self.performance.now.bind(self.performance)}catch(t){w.debug("Unable to use Performance API on this environment"),xn=null==j?void 0:j.Date.now}var On=[{demux:rn,remux:Fn},{demux:cn,remux:In},{demux:tn,remux:In},{demux:En,remux:In}];On.splice(2,0,{demux:an,remux:In});var Nn=function(){function t(t,e,r,i,n){this.async=!1,this.observer=void 0,this.typeSupported=void 0,this.config=void 0,this.vendor=void 0,this.id=void 0,this.demuxer=void 0,this.remuxer=void 0,this.decrypter=void 0,this.probe=void 0,this.decryptionPromise=null,this.transmuxConfig=void 0,this.currentTransmuxState=void 0,this.observer=t,this.typeSupported=e,this.config=r,this.vendor=i,this.id=n}var e=t.prototype;return e.configure=function(t){this.transmuxConfig=t,this.decrypter&&this.decrypter.reset()},e.push=function(t,e,r,i){var n=this,a=r.transmuxing;a.executeStart=xn();var s=new Uint8Array(t),o=this.currentTransmuxState,l=this.transmuxConfig;i&&(this.currentTransmuxState=i);var u=i||o,h=u.contiguous,d=u.discontinuity,c=u.trackSwitch,f=u.accurateTimeOffset,g=u.timeOffset,v=u.initSegmentChange,m=l.audioCodec,p=l.videoCodec,y=l.defaultInitPts,E=l.duration,T=l.initSegmentData,R=function(t,e){var r=null;return t.byteLength>0&&null!=(null==e?void 0:e.key)&&null!==e.iv&&null!=e.method&&(r=e),r}(s,e);if(R&&"AES-128"===R.method){var b=this.getDecrypter();if(!b.isSync())return this.decryptionPromise=b.webCryptoDecrypt(s,R.key.buffer,R.iv.buffer).then((function(t){var e=n.push(t,null,r);return n.decryptionPromise=null,e})),this.decryptionPromise;var k=b.softwareDecrypt(s,R.key.buffer,R.iv.buffer);if(r.part>-1&&(k=b.flush()),!k)return a.executeEnd=xn(),Un(r);s=new Uint8Array(k)}var D=this.needsProbing(d,c);if(D){var I=this.configureTransmuxer(s);if(I)return w.warn("[transmuxer] "+I.message),this.observer.emit(S.ERROR,S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,fatal:!1,error:I,reason:I.message}),a.executeEnd=xn(),Un(r)}(d||c||v||D)&&this.resetInitSegment(T,m,p,E,e),(d||v||D)&&this.resetInitialTimestamp(y),h||this.resetContiguity();var C=this.transmux(s,R,g,f,r),_=this.currentTransmuxState;return _.contiguous=!0,_.discontinuity=!1,_.trackSwitch=!1,a.executeEnd=xn(),C},e.flush=function(t){var e=this,r=t.transmuxing;r.executeStart=xn();var i=this.decrypter,n=this.currentTransmuxState,a=this.decryptionPromise;if(a)return a.then((function(){return e.flush(t)}));var s=[],o=n.timeOffset;if(i){var l=i.flush();l&&s.push(this.push(l,null,t))}var u=this.demuxer,h=this.remuxer;if(!u||!h)return r.executeEnd=xn(),[Un(t)];var d=u.flush(o);return Bn(d)?d.then((function(r){return e.flushRemux(s,r,t),s})):(this.flushRemux(s,d,t),s)},e.flushRemux=function(t,e,r){var i=e.audioTrack,n=e.videoTrack,a=e.id3Track,s=e.textTrack,o=this.currentTransmuxState,l=o.accurateTimeOffset,u=o.timeOffset;w.log("[transmuxer.ts]: Flushed fragment "+r.sn+(r.part>-1?" p: "+r.part:"")+" of level "+r.level);var h=this.remuxer.remux(i,n,a,s,u,l,!0,this.id);t.push({remuxResult:h,chunkMeta:r}),r.transmuxing.executeEnd=xn()},e.resetInitialTimestamp=function(t){var e=this.demuxer,r=this.remuxer;e&&r&&(e.resetTimeStamp(t),r.resetTimeStamp(t))},e.resetContiguity=function(){var t=this.demuxer,e=this.remuxer;t&&e&&(t.resetContiguity(),e.resetNextTimestamp())},e.resetInitSegment=function(t,e,r,i,n){var a=this.demuxer,s=this.remuxer;a&&s&&(a.resetInitSegment(t,e,r,i),s.resetInitSegment(t,e,r,n))},e.destroy=function(){this.demuxer&&(this.demuxer.destroy(),this.demuxer=void 0),this.remuxer&&(this.remuxer.destroy(),this.remuxer=void 0)},e.transmux=function(t,e,r,i,n){return e&&"SAMPLE-AES"===e.method?this.transmuxSampleAes(t,e,r,i,n):this.transmuxUnencrypted(t,r,i,n)},e.transmuxUnencrypted=function(t,e,r,i){var n=this.demuxer.demux(t,e,!1,!this.config.progressive),a=n.audioTrack,s=n.videoTrack,o=n.id3Track,l=n.textTrack;return{remuxResult:this.remuxer.remux(a,s,o,l,e,r,!1,this.id),chunkMeta:i}},e.transmuxSampleAes=function(t,e,r,i,n){var a=this;return this.demuxer.demuxSampleAes(t,e,r).then((function(t){return{remuxResult:a.remuxer.remux(t.audioTrack,t.videoTrack,t.id3Track,t.textTrack,r,i,!1,a.id),chunkMeta:n}}))},e.configureTransmuxer=function(t){for(var e,r=this.config,i=this.observer,n=this.typeSupported,a=this.vendor,s=0,o=On.length;s<o;s++){var l;if(null!=(l=On[s].demux)&&l.probe(t)){e=On[s];break}}if(!e)return new Error("Failed to find demuxer by probing fragment data");var u=this.demuxer,h=this.remuxer,d=e.remux,c=e.demux;h&&h instanceof d||(this.remuxer=new d(i,r,n,a)),u&&u instanceof c||(this.demuxer=new c(i,r,n),this.probe=c.probe)},e.needsProbing=function(t,e){return!this.demuxer||!this.remuxer||t||e},e.getDecrypter=function(){var t=this.decrypter;return t||(t=this.decrypter=new pi(this.config)),t},t}(),Un=function(t){return{remuxResult:{},chunkMeta:t}};function Bn(t){return"then"in t&&t.then instanceof Function}var Gn=function(t,e,r,i,n){this.audioCodec=void 0,this.videoCodec=void 0,this.initSegmentData=void 0,this.duration=void 0,this.defaultInitPts=void 0,this.audioCodec=t,this.videoCodec=e,this.initSegmentData=r,this.duration=i,this.defaultInitPts=n||null},Kn=function(t,e,r,i,n,a){this.discontinuity=void 0,this.contiguous=void 0,this.accurateTimeOffset=void 0,this.trackSwitch=void 0,this.timeOffset=void 0,this.initSegmentChange=void 0,this.discontinuity=t,this.contiguous=e,this.accurateTimeOffset=r,this.trackSwitch=i,this.timeOffset=n,this.initSegmentChange=a},Hn={exports:{}};!function(t){var e=Object.prototype.hasOwnProperty,r="~";function i(){}function n(t,e,r){this.fn=t,this.context=e,this.once=r||!1}function a(t,e,i,a,s){if("function"!=typeof i)throw new TypeError("The listener must be a function");var o=new n(i,a||t,s),l=r?r+e:e;return t._events[l]?t._events[l].fn?t._events[l]=[t._events[l],o]:t._events[l].push(o):(t._events[l]=o,t._eventsCount++),t}function s(t,e){0==--t._eventsCount?t._events=new i:delete t._events[e]}function o(){this._events=new i,this._eventsCount=0}Object.create&&(i.prototype=Object.create(null),(new i).__proto__||(r=!1)),o.prototype.eventNames=function(){var t,i,n=[];if(0===this._eventsCount)return n;for(i in t=this._events)e.call(t,i)&&n.push(r?i.slice(1):i);return Object.getOwnPropertySymbols?n.concat(Object.getOwnPropertySymbols(t)):n},o.prototype.listeners=function(t){var e=r?r+t:t,i=this._events[e];if(!i)return[];if(i.fn)return[i.fn];for(var n=0,a=i.length,s=new Array(a);n<a;n++)s[n]=i[n].fn;return s},o.prototype.listenerCount=function(t){var e=r?r+t:t,i=this._events[e];return i?i.fn?1:i.length:0},o.prototype.emit=function(t,e,i,n,a,s){var o=r?r+t:t;if(!this._events[o])return!1;var l,u,h=this._events[o],d=arguments.length;if(h.fn){switch(h.once&&this.removeListener(t,h.fn,void 0,!0),d){case 1:return h.fn.call(h.context),!0;case 2:return h.fn.call(h.context,e),!0;case 3:return h.fn.call(h.context,e,i),!0;case 4:return h.fn.call(h.context,e,i,n),!0;case 5:return h.fn.call(h.context,e,i,n,a),!0;case 6:return h.fn.call(h.context,e,i,n,a,s),!0}for(u=1,l=new Array(d-1);u<d;u++)l[u-1]=arguments[u];h.fn.apply(h.context,l)}else{var c,f=h.length;for(u=0;u<f;u++)switch(h[u].once&&this.removeListener(t,h[u].fn,void 0,!0),d){case 1:h[u].fn.call(h[u].context);break;case 2:h[u].fn.call(h[u].context,e);break;case 3:h[u].fn.call(h[u].context,e,i);break;case 4:h[u].fn.call(h[u].context,e,i,n);break;default:if(!l)for(c=1,l=new Array(d-1);c<d;c++)l[c-1]=arguments[c];h[u].fn.apply(h[u].context,l)}}return!0},o.prototype.on=function(t,e,r){return a(this,t,e,r,!1)},o.prototype.once=function(t,e,r){return a(this,t,e,r,!0)},o.prototype.removeListener=function(t,e,i,n){var a=r?r+t:t;if(!this._events[a])return this;if(!e)return s(this,a),this;var o=this._events[a];if(o.fn)o.fn!==e||n&&!o.once||i&&o.context!==i||s(this,a);else{for(var l=0,u=[],h=o.length;l<h;l++)(o[l].fn!==e||n&&!o[l].once||i&&o[l].context!==i)&&u.push(o[l]);u.length?this._events[a]=1===u.length?u[0]:u:s(this,a)}return this},o.prototype.removeAllListeners=function(t){var e;return t?(e=r?r+t:t,this._events[e]&&s(this,e)):(this._events=new i,this._eventsCount=0),this},o.prototype.off=o.prototype.removeListener,o.prototype.addListener=o.prototype.on,o.prefixed=r,o.EventEmitter=o,t.exports=o}(Hn);var Vn=v(Hn.exports);function Yn(t,e){if(!((r=e.remuxResult).audio||r.video||r.text||r.id3||r.initSegment))return!1;var r,i=[],n=e.remuxResult,a=n.audio,s=n.video;return a&&Wn(i,a),s&&Wn(i,s),t.postMessage({event:"transmuxComplete",data:e},i),!0}function Wn(t,e){e.data1&&t.push(e.data1.buffer),e.data2&&t.push(e.data2.buffer)}function jn(t,e,r){e.reduce((function(e,r){return Yn(t,r)||e}),!1)||t.postMessage({event:"transmuxComplete",data:e[0]}),t.postMessage({event:"flush",data:r})}void 0!==e&&e&&function(t){var e=new Vn,r=function(e,r){t.postMessage({event:e,data:r})};e.on(S.FRAG_DECRYPTED,r),e.on(S.ERROR,r);var i=function(){var t=function(t){var e=function(e){r("workerLog",{logType:t,message:e})};w[t]=e};for(var e in w)t(e)};t.addEventListener("message",(function(n){var a=n.data;switch(a.cmd){case"init":var s=JSON.parse(a.config);t.transmuxer=new Nn(e,a.typeSupported,s,"",a.id),I(s.debug,a.id),i(),r("init",null);break;case"configure":t.transmuxer.configure(a.config);break;case"demux":var o=t.transmuxer.push(a.data,a.decryptdata,a.chunkMeta,a.state);Bn(o)?(t.transmuxer.async=!0,o.then((function(e){Yn(t,e)})).catch((function(t){r(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,chunkMeta:a.chunkMeta,fatal:!1,error:t,err:t,reason:"transmuxer-worker push error"})}))):(t.transmuxer.async=!1,Yn(t,o));break;case"flush":var l=a.chunkMeta,u=t.transmuxer.flush(l);Bn(u)||t.transmuxer.async?(Bn(u)||(u=Promise.resolve(u)),u.then((function(e){jn(t,e,l)})).catch((function(t){r(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,chunkMeta:a.chunkMeta,fatal:!1,error:t,err:t,reason:"transmuxer-worker flush error"})}))):jn(t,u,l)}}))}(self);var qn=function(){function e(e,r,i,n){var a=this;this.error=null,this.hls=void 0,this.id=void 0,this.observer=void 0,this.frag=null,this.part=null,this.useWorker=void 0,this.workerContext=null,this.onwmsg=void 0,this.transmuxer=null,this.onTransmuxComplete=void 0,this.onFlush=void 0;var s=e.config;this.hls=e,this.id=r,this.useWorker=!!s.enableWorker,this.onTransmuxComplete=i,this.onFlush=n;var o=function(t,e){(e=e||{}).frag=a.frag,e.id=a.id,t===S.ERROR&&(a.error=e.error),a.hls.trigger(t,e)};this.observer=new Vn,this.observer.on(S.FRAG_DECRYPTED,o),this.observer.on(S.ERROR,o);var l,u,h,d,c=se(s.preferManagedMediaSource)||{isTypeSupported:function(){return!1}},f={mpeg:c.isTypeSupported("audio/mpeg"),mp3:c.isTypeSupported('audio/mp4; codecs="mp3"'),ac3:c.isTypeSupported('audio/mp4; codecs="ac-3"')};if(!this.useWorker||"undefined"==typeof Worker||(s.workerPath,0))this.transmuxer=new Nn(this.observer,f,s,"",r);else try{s.workerPath?(w.log("loading Web Worker "+s.workerPath+' for "'+r+'"'),this.workerContext=(h=s.workerPath,d=new self.URL(h,self.location.href).href,{worker:new self.Worker(d),scriptURL:d})):(w.log('injecting Web Worker for "'+r+'"'),this.workerContext=(l=new self.Blob(["var exports={};var module={exports:exports};function define(f){f()};define.amd=true;("+t.toString()+")(true);"],{type:"text/javascript"}),u=self.URL.createObjectURL(l),{worker:new self.Worker(u),objectURL:u})),this.onwmsg=function(t){return a.onWorkerMessage(t)};var g=this.workerContext.worker;g.addEventListener("message",this.onwmsg),g.onerror=function(t){var e=new Error(t.message+" ("+t.filename+":"+t.lineno+")");s.enableWorker=!1,w.warn('Error in "'+r+'" Web Worker, fallback to inline'),a.hls.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.INTERNAL_EXCEPTION,fatal:!1,event:"demuxerWorker",error:e})},g.postMessage({cmd:"init",typeSupported:f,vendor:"",id:r,config:JSON.stringify(s)})}catch(t){w.warn('Error setting up "'+r+'" Web Worker, fallback to inline',t),this.resetWorker(),this.error=null,this.transmuxer=new Nn(this.observer,f,s,"",r)}}var r=e.prototype;return r.resetWorker=function(){if(this.workerContext){var t=this.workerContext,e=t.worker,r=t.objectURL;r&&self.URL.revokeObjectURL(r),e.removeEventListener("message",this.onwmsg),e.onerror=null,e.terminate(),this.workerContext=null}},r.destroy=function(){if(this.workerContext)this.resetWorker(),this.onwmsg=void 0;else{var t=this.transmuxer;t&&(t.destroy(),this.transmuxer=null)}var e=this.observer;e&&e.removeAllListeners(),this.frag=null,this.observer=null,this.hls=null},r.push=function(t,e,r,i,n,a,s,o,l,u){var h,d,c=this;l.transmuxing.start=self.performance.now();var f=this.transmuxer,g=a?a.start:n.start,v=n.decryptdata,m=this.frag,p=!(m&&n.cc===m.cc),y=!(m&&l.level===m.level),E=m?l.sn-m.sn:-1,T=this.part?l.part-this.part.index:-1,S=0===E&&l.id>1&&l.id===(null==m?void 0:m.stats.chunkCount),L=!y&&(1===E||0===E&&(1===T||S&&T<=0)),A=self.performance.now();(y||E||0===n.stats.parsing.start)&&(n.stats.parsing.start=A),!a||!T&&L||(a.stats.parsing.start=A);var R=!(m&&(null==(h=n.initSegment)?void 0:h.url)===(null==(d=m.initSegment)?void 0:d.url)),b=new Kn(p,L,o,y,g,R);if(!L||p||R){w.log("[transmuxer-interface, "+n.type+"]: Starting new transmux session for sn: "+l.sn+" p: "+l.part+" level: "+l.level+" id: "+l.id+"\n discontinuity: "+p+"\n trackSwitch: "+y+"\n contiguous: "+L+"\n accurateTimeOffset: "+o+"\n timeOffset: "+g+"\n initSegmentChange: "+R);var k=new Gn(r,i,e,s,u);this.configureTransmuxer(k)}if(this.frag=n,this.part=a,this.workerContext)this.workerContext.worker.postMessage({cmd:"demux",data:t,decryptdata:v,chunkMeta:l,state:b},t instanceof ArrayBuffer?[t]:[]);else if(f){var D=f.push(t,v,l,b);Bn(D)?(f.async=!0,D.then((function(t){c.handleTransmuxComplete(t)})).catch((function(t){c.transmuxerError(t,l,"transmuxer-interface push error")}))):(f.async=!1,this.handleTransmuxComplete(D))}},r.flush=function(t){var e=this;t.transmuxing.start=self.performance.now();var r=this.transmuxer;if(this.workerContext)this.workerContext.worker.postMessage({cmd:"flush",chunkMeta:t});else if(r){var i=r.flush(t);Bn(i)||r.async?(Bn(i)||(i=Promise.resolve(i)),i.then((function(r){e.handleFlushResult(r,t)})).catch((function(r){e.transmuxerError(r,t,"transmuxer-interface flush error")}))):this.handleFlushResult(i,t)}},r.transmuxerError=function(t,e,r){this.hls&&(this.error=t,this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_PARSING_ERROR,chunkMeta:e,frag:this.frag||void 0,fatal:!1,error:t,err:t,reason:r}))},r.handleFlushResult=function(t,e){var r=this;t.forEach((function(t){r.handleTransmuxComplete(t)})),this.onFlush(e)},r.onWorkerMessage=function(t){var e=t.data;if(null!=e&&e.event){var r=this.hls;if(this.hls)switch(e.event){case"init":var i,n=null==(i=this.workerContext)?void 0:i.objectURL;n&&self.URL.revokeObjectURL(n);break;case"transmuxComplete":this.handleTransmuxComplete(e.data);break;case"flush":this.onFlush(e.data);break;case"workerLog":w[e.data.logType]&&w[e.data.logType](e.data.message);break;default:e.data=e.data||{},e.data.frag=this.frag,e.data.id=this.id,r.trigger(e.event,e.data)}}else w.warn("worker message received with no "+(e?"event name":"data"))},r.configureTransmuxer=function(t){var e=this.transmuxer;this.workerContext?this.workerContext.worker.postMessage({cmd:"configure",config:t}):e&&e.configure(t)},r.handleTransmuxComplete=function(t){t.chunkMeta.transmuxing.end=self.performance.now(),this.onTransmuxComplete(t)},e}();function Xn(t,e){if(t.length!==e.length)return!1;for(var r=0;r<t.length;r++)if(!zn(t[r].attrs,e[r].attrs))return!1;return!0}function zn(t,e,r){var i=t["STABLE-RENDITION-ID"];return i&&!r?i===e["STABLE-RENDITION-ID"]:!(r||["LANGUAGE","NAME","CHARACTERISTICS","AUTOSELECT","DEFAULT","FORCED","ASSOC-LANGUAGE"]).some((function(r){return t[r]!==e[r]}))}function Qn(t,e){return e.label.toLowerCase()===t.name.toLowerCase()&&(!e.language||e.language.toLowerCase()===(t.lang||"").toLowerCase())}var Jn=function(t){function e(e,r,i){var n;return(n=t.call(this,e,r,i,"[audio-stream-controller]",Me)||this).videoBuffer=null,n.videoTrackCC=-1,n.waitingVideoCC=-1,n.bufferedTrack=null,n.switchingTrack=null,n.trackId=-1,n.waitingData=null,n.mainDetails=null,n.flushing=!1,n.bufferFlushed=!1,n.cachedTrackLoadedData=null,n._registerListeners(),n}l(e,t);var r=e.prototype;return r.onHandlerDestroying=function(){this._unregisterListeners(),t.prototype.onHandlerDestroying.call(this),this.mainDetails=null,this.bufferedTrack=null,this.switchingTrack=null},r._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.AUDIO_TRACKS_UPDATED,this.onAudioTracksUpdated,this),t.on(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.on(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.on(S.ERROR,this.onError,this),t.on(S.BUFFER_RESET,this.onBufferReset,this),t.on(S.BUFFER_CREATED,this.onBufferCreated,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.on(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.AUDIO_TRACKS_UPDATED,this.onAudioTracksUpdated,this),t.off(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.off(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.off(S.ERROR,this.onError,this),t.off(S.BUFFER_RESET,this.onBufferReset,this),t.off(S.BUFFER_CREATED,this.onBufferCreated,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.off(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this)},r.onInitPtsFound=function(t,e){var r=e.frag,i=e.id,n=e.initPTS,a=e.timescale;if("main"===i){var s=r.cc;this.initPTS[r.cc]={baseTime:n,timescale:a},this.log("InitPTS for cc: "+s+" found from main: "+n),this.videoTrackCC=s,this.state===wi&&this.tick()}},r.startLoad=function(t){if(!this.levels)return this.startPosition=t,void(this.state=Ei);var e=this.lastCurrentTime;this.stopLoad(),this.setInterval(100),e>0&&-1===t?(this.log("Override startPosition with lastCurrentTime @"+e.toFixed(3)),t=e,this.state=Ti):(this.loadedmetadata=!1,this.state=Ri),this.nextLoadPosition=this.startPosition=this.lastCurrentTime=t,this.tick()},r.doTick=function(){switch(this.state){case Ti:this.doTickIdle();break;case Ri:var e,r=this.levels,i=this.trackId,n=null==r||null==(e=r[i])?void 0:e.details;if(n){if(this.waitForCdnTuneIn(n))break;this.state=wi}break;case Ai:var a,s=performance.now(),o=this.retryDate;if(!o||s>=o||null!=(a=this.media)&&a.seeking){var l=this.levels,u=this.trackId;this.log("RetryDate reached, switch back to IDLE state"),this.resetStartWhenNotLoaded((null==l?void 0:l[u])||null),this.state=Ti}break;case wi:var h=this.waitingData;if(h){var d=h.frag,c=h.part,f=h.cache,g=h.complete;if(void 0!==this.initPTS[d.cc]){this.waitingData=null,this.waitingVideoCC=-1,this.state=Li;var v={frag:d,part:c,payload:f.flush(),networkDetails:null};this._handleFragmentLoadProgress(v),g&&t.prototype._handleFragmentLoadComplete.call(this,v)}else if(this.videoTrackCC!==this.waitingVideoCC)this.log("Waiting fragment cc ("+d.cc+") cancelled because video is at cc "+this.videoTrackCC),this.clearWaitingFragment();else{var m=this.getLoadPosition(),p=ri.bufferInfo(this.mediaBuffer,m,this.config.maxBufferHole);Rr(p.end,this.config.maxFragLookUpTolerance,d)<0&&(this.log("Waiting fragment cc ("+d.cc+") @ "+d.start+" cancelled because another fragment at "+p.end+" is needed"),this.clearWaitingFragment())}}else this.state=Ti}this.onTickEnd()},r.clearWaitingFragment=function(){var t=this.waitingData;t&&(this.fragmentTracker.removeFragment(t.frag),this.waitingData=null,this.waitingVideoCC=-1,this.state=Ti)},r.resetLoadingState=function(){this.clearWaitingFragment(),t.prototype.resetLoadingState.call(this)},r.onTickEnd=function(){var t=this.media;null!=t&&t.readyState&&(this.lastCurrentTime=t.currentTime)},r.doTickIdle=function(){var t=this.hls,e=this.levels,r=this.media,i=this.trackId,n=t.config;if((r||!this.startFragRequested&&n.startFragPrefetch)&&null!=e&&e[i]){var a=e[i],s=a.details;if(!s||s.live&&this.levelLastLoaded!==a||this.waitForCdnTuneIn(s))this.state=Ri;else{var o=this.mediaBuffer?this.mediaBuffer:this.media;this.bufferFlushed&&o&&(this.bufferFlushed=!1,this.afterBufferFlushed(o,O,Me));var l=this.getFwdBufferInfo(o,Me);if(null!==l){var u=this.bufferedTrack,h=this.switchingTrack;if(!h&&this._streamEnded(l,s))return t.trigger(S.BUFFER_EOS,{type:"audio"}),void(this.state=Di);var d=this.getFwdBufferInfo(this.videoBuffer?this.videoBuffer:this.media,Fe),c=l.len,f=this.getMaxBufferLength(null==d?void 0:d.len),g=s.fragments,v=g[0].start,m=this.flushing?this.getLoadPosition():l.end;if(h&&r){var p=this.getLoadPosition();u&&!zn(h.attrs,u.attrs)&&(m=p),s.PTSKnown&&p<v&&(l.end>v||l.nextStart)&&(this.log("Alt audio track ahead of main track, seek to start of alt audio track"),r.currentTime=v+.05)}if(!(c>=f&&!h&&m<g[g.length-1].start)){var y=this.getNextFragment(m,s),E=!1;if(y&&this.isLoopLoading(y,m)&&(E=!!y.gap,y=this.getNextFragmentLoopLoading(y,s,l,Fe,f)),y){var T=d&&y.start>d.end+s.targetduration;if(T||(null==d||!d.len)&&l.len){var L=this.getAppendedFrag(y.start,Fe);if(null===L)return;if(E||(E=!!L.gap||!!T&&0===d.len),T&&!E||E&&l.nextStart&&l.nextStart<L.end)return}this.loadFragment(y,a,m)}else this.bufferFlushed=!0}}}}},r.getMaxBufferLength=function(e){var r=t.prototype.getMaxBufferLength.call(this);return e?Math.min(Math.max(r,e),this.config.maxMaxBufferLength):r},r.onMediaDetaching=function(){this.videoBuffer=null,this.bufferFlushed=this.flushing=!1,t.prototype.onMediaDetaching.call(this)},r.onAudioTracksUpdated=function(t,e){var r=e.audioTracks;this.resetTransmuxer(),this.levels=r.map((function(t){return new or(t)}))},r.onAudioTrackSwitching=function(t,e){var r=!!e.url;this.trackId=e.id;var i=this.fragCurrent;i&&(i.abortRequests(),this.removeUnbufferedFrags(i.start)),this.resetLoadingState(),r?this.setInterval(100):this.resetTransmuxer(),r?(this.switchingTrack=e,this.state=Ti,this.flushAudioIfNeeded(e)):(this.switchingTrack=null,this.bufferedTrack=e,this.state=Ei),this.tick()},r.onManifestLoading=function(){this.fragmentTracker.removeAllFragments(),this.startPosition=this.lastCurrentTime=0,this.bufferFlushed=this.flushing=!1,this.levels=this.mainDetails=this.waitingData=this.bufferedTrack=this.cachedTrackLoadedData=this.switchingTrack=null,this.startFragRequested=!1,this.trackId=this.videoTrackCC=this.waitingVideoCC=-1},r.onLevelLoaded=function(t,e){this.mainDetails=e.details,null!==this.cachedTrackLoadedData&&(this.hls.trigger(S.AUDIO_TRACK_LOADED,this.cachedTrackLoadedData),this.cachedTrackLoadedData=null)},r.onAudioTrackLoaded=function(t,e){var r;if(null!=this.mainDetails){var i=this.levels,n=e.details,a=e.id;if(i){this.log("Audio track "+a+" loaded ["+n.startSN+","+n.endSN+"]"+(n.lastPartSn?"[part-"+n.lastPartSn+"-"+n.lastPartIndex+"]":"")+",duration:"+n.totalduration);var s=i[a],o=0;if(n.live||null!=(r=s.details)&&r.live){this.checkLiveUpdate(n);var l,u=this.mainDetails;if(n.deltaUpdateFailed||!u)return;!s.details&&n.hasProgramDateTime&&u.hasProgramDateTime?(li(n,u),o=n.fragments[0].start):o=this.alignPlaylists(n,s.details,null==(l=this.levelLastLoaded)?void 0:l.details)}s.details=n,this.levelLastLoaded=s,this.startFragRequested||!this.mainDetails&&n.live||this.setStartPosition(this.mainDetails||n,o),this.state!==Ri||this.waitForCdnTuneIn(n)||(this.state=Ti),this.tick()}else this.warn("Audio tracks were reset while loading level "+a)}else this.cachedTrackLoadedData=e},r._handleFragmentLoadProgress=function(t){var e,r=t.frag,i=t.part,n=t.payload,a=this.config,s=this.trackId,o=this.levels;if(o){var l=o[s];if(l){var u=l.details;if(!u)return this.warn("Audio track details undefined on fragment load progress"),void this.removeUnbufferedFrags(r.start);var h=a.defaultAudioCodec||l.audioCodec||"mp4a.40.2",d=this.transmuxer;d||(d=this.transmuxer=new qn(this.hls,Me,this._handleTransmuxComplete.bind(this),this._handleTransmuxerFlush.bind(this)));var c=this.initPTS[r.cc],f=null==(e=r.initSegment)?void 0:e.data;if(void 0!==c){var g=i?i.index:-1,v=-1!==g,m=new ii(r.level,r.sn,r.stats.chunkCount,n.byteLength,g,v);d.push(n,f,h,"",r,i,u.totalduration,!1,m,c)}else this.log("Unknown video PTS for cc "+r.cc+", waiting for video PTS before demuxing audio frag "+r.sn+" of ["+u.startSN+" ,"+u.endSN+"],track "+s),(this.waitingData=this.waitingData||{frag:r,part:i,cache:new xi,complete:!1}).cache.push(new Uint8Array(n)),this.waitingVideoCC=this.videoTrackCC,this.state=wi}else this.warn("Audio track is undefined on fragment load progress")}else this.warn("Audio tracks were reset while fragment load was in progress. Fragment "+r.sn+" of level "+r.level+" will not be buffered")},r._handleFragmentLoadComplete=function(e){this.waitingData?this.waitingData.complete=!0:t.prototype._handleFragmentLoadComplete.call(this,e)},r.onBufferReset=function(){this.mediaBuffer=this.videoBuffer=null,this.loadedmetadata=!1},r.onBufferCreated=function(t,e){var r=e.tracks.audio;r&&(this.mediaBuffer=r.buffer||null),e.tracks.video&&(this.videoBuffer=e.tracks.video.buffer||null)},r.onFragBuffered=function(t,e){var r=e.frag,n=e.part;if(r.type===Me)if(this.fragContextChanged(r))this.warn("Fragment "+r.sn+(n?" p: "+n.index:"")+" of level "+r.level+" finished buffering, but was aborted. state: "+this.state+", audioSwitch: "+(this.switchingTrack?this.switchingTrack.name:"false"));else{if("initSegment"!==r.sn){this.fragPrevious=r;var a=this.switchingTrack;a&&(this.bufferedTrack=a,this.switchingTrack=null,this.hls.trigger(S.AUDIO_TRACK_SWITCHED,i({},a)))}this.fragBufferedComplete(r,n)}else if(!this.loadedmetadata&&r.type===Fe){var s=this.videoBuffer||this.media;s&&ri.getBuffered(s).length&&(this.loadedmetadata=!0)}},r.onError=function(e,r){var i;if(r.fatal)this.state=Ii;else switch(r.details){case A.FRAG_GAP:case A.FRAG_PARSING_ERROR:case A.FRAG_DECRYPT_ERROR:case A.FRAG_LOAD_ERROR:case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_ERROR:case A.KEY_LOAD_TIMEOUT:this.onFragmentOrKeyLoadError(Me,r);break;case A.AUDIO_TRACK_LOAD_ERROR:case A.AUDIO_TRACK_LOAD_TIMEOUT:case A.LEVEL_PARSING_ERROR:r.levelRetry||this.state!==Ri||(null==(i=r.context)?void 0:i.type)!==xe||(this.state=Ti);break;case A.BUFFER_APPEND_ERROR:case A.BUFFER_FULL_ERROR:if(!r.parent||"audio"!==r.parent)return;if(r.details===A.BUFFER_APPEND_ERROR)return void this.resetLoadingState();this.reduceLengthAndFlushBuffer(r)&&(this.bufferedTrack=null,t.prototype.flushMainBuffer.call(this,0,Number.POSITIVE_INFINITY,"audio"));break;case A.INTERNAL_EXCEPTION:this.recoverWorkerError(r)}},r.onBufferFlushing=function(t,e){e.type!==N&&(this.flushing=!0)},r.onBufferFlushed=function(t,e){var r=e.type;if(r!==N){this.flushing=!1,this.bufferFlushed=!0,this.state===Di&&(this.state=Ti);var i=this.mediaBuffer||this.media;i&&(this.afterBufferFlushed(i,r,Me),this.tick())}},r._handleTransmuxComplete=function(t){var e,r="audio",i=this.hls,n=t.remuxResult,a=t.chunkMeta,s=this.getCurrentContext(a);if(s){var l=s.frag,u=s.part,h=s.level,d=h.details,c=n.audio,f=n.text,g=n.id3,v=n.initSegment;if(!this.fragContextChanged(l)&&d){if(this.state=bi,this.switchingTrack&&c&&this.completeAudioSwitch(this.switchingTrack),null!=v&&v.tracks){var m=l.initSegment||l;this._bufferInitSegment(h,v.tracks,m,a),i.trigger(S.FRAG_PARSING_INIT_SEGMENT,{frag:m,id:r,tracks:v.tracks})}if(c){var p=c.startPTS,y=c.endPTS,E=c.startDTS,T=c.endDTS;u&&(u.elementaryStreams[O]={startPTS:p,endPTS:y,startDTS:E,endDTS:T}),l.setElementaryStreamInfo(O,p,y,E,T),this.bufferFragmentData(c,l,u,a)}if(null!=g&&null!=(e=g.samples)&&e.length){var L=o({id:r,frag:l,details:d},g);i.trigger(S.FRAG_PARSING_METADATA,L)}if(f){var A=o({id:r,frag:l,details:d},f);i.trigger(S.FRAG_PARSING_USERDATA,A)}}else this.fragmentTracker.removeFragment(l)}else this.resetWhenMissingContext(a)},r._bufferInitSegment=function(t,e,r,i){if(this.state===bi){e.video&&delete e.video;var n=e.audio;if(n){n.id="audio";var a=t.audioCodec;this.log("Init audio buffer, container:"+n.container+", codecs[level/parsed]=["+a+"/"+n.codec+"]"),a&&1===a.split(",").length&&(n.levelCodec=a),this.hls.trigger(S.BUFFER_CODECS,e);var s=n.initSegment;if(null!=s&&s.byteLength){var o={type:"audio",frag:r,part:null,chunkMeta:i,parent:r.type,data:s};this.hls.trigger(S.BUFFER_APPENDING,o)}this.tickImmediate()}}},r.loadFragment=function(e,r,i){var n,a=this.fragmentTracker.getState(e);if(this.fragCurrent=e,this.switchingTrack||a===Xr||a===Qr)if("initSegment"===e.sn)this._loadInitSegment(e,r);else if(null!=(n=r.details)&&n.live&&!this.initPTS[e.cc]){this.log("Waiting for video PTS in continuity counter "+e.cc+" of live stream before loading audio fragment "+e.sn+" of level "+this.trackId),this.state=wi;var s=this.mainDetails;s&&s.fragments[0].start!==r.details.fragments[0].start&&li(r.details,s)}else this.startFragRequested=!0,t.prototype.loadFragment.call(this,e,r,i);else this.clearTrackerIfNeeded(e)},r.flushAudioIfNeeded=function(e){var r=this.media,i=this.bufferedTrack,n=null==i?void 0:i.attrs,a=e.attrs;r&&n&&(n.CHANNELS!==a.CHANNELS||i.name!==e.name||i.lang!==e.lang)&&(this.log("Switching audio track : flushing all audio"),t.prototype.flushMainBuffer.call(this,0,Number.POSITIVE_INFINITY,"audio"),this.bufferedTrack=null)},r.completeAudioSwitch=function(t){var e=this.hls;this.flushAudioIfNeeded(t),this.bufferedTrack=t,this.switchingTrack=null,e.trigger(S.AUDIO_TRACK_SWITCHED,i({},t))},e}(_i),$n=function(t){function e(e){var r;return(r=t.call(this,e,"[audio-track-controller]")||this).tracks=[],r.groupIds=null,r.tracksInGroup=[],r.trackId=-1,r.currentTrack=null,r.selectDefaultTrack=!0,r.registerListeners(),r}l(e,t);var r=e.prototype;return r.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.on(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.on(S.ERROR,this.onError,this)},r.unregisterListeners=function(){var t=this.hls;t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVEL_LOADING,this.onLevelLoading,this),t.off(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.off(S.AUDIO_TRACK_LOADED,this.onAudioTrackLoaded,this),t.off(S.ERROR,this.onError,this)},r.destroy=function(){this.unregisterListeners(),this.tracks.length=0,this.tracksInGroup.length=0,this.currentTrack=null,t.prototype.destroy.call(this)},r.onManifestLoading=function(){this.tracks=[],this.tracksInGroup=[],this.groupIds=null,this.currentTrack=null,this.trackId=-1,this.selectDefaultTrack=!0},r.onManifestParsed=function(t,e){this.tracks=e.audioTracks||[]},r.onAudioTrackLoaded=function(t,e){var r=e.id,i=e.groupId,n=e.details,a=this.tracksInGroup[r];if(a&&a.groupId===i){var s=a.details;a.details=e.details,this.log("Audio track "+r+' "'+a.name+'" lang:'+a.lang+" group:"+i+" loaded ["+n.startSN+"-"+n.endSN+"]"),r===this.trackId&&this.playlistLoaded(r,e,s)}else this.warn("Audio track with id:"+r+" and group:"+i+" not found in active group "+(null==a?void 0:a.groupId))},r.onLevelLoading=function(t,e){this.switchLevel(e.level)},r.onLevelSwitching=function(t,e){this.switchLevel(e.level)},r.switchLevel=function(t){var e=this.hls.levels[t];if(e){var r=e.audioGroups||null,i=this.groupIds,n=this.currentTrack;if(!r||(null==i?void 0:i.length)!==(null==r?void 0:r.length)||null!=r&&r.some((function(t){return-1===(null==i?void 0:i.indexOf(t))}))){this.groupIds=r,this.trackId=-1,this.currentTrack=null;var a=this.tracks.filter((function(t){return!r||-1!==r.indexOf(t.groupId)}));if(a.length)this.selectDefaultTrack&&!a.some((function(t){return t.default}))&&(this.selectDefaultTrack=!1),a.forEach((function(t,e){t.id=e}));else if(!n&&!this.tracksInGroup.length)return;this.tracksInGroup=a;var s=this.hls.config.audioPreference;if(!n&&s){var o=Hr(s,a,Yr);if(o>-1)n=a[o];else{var l=Hr(s,this.tracks);n=this.tracks[l]}}var u=this.findTrackId(n);-1===u&&n&&(u=this.findTrackId(null));var h={audioTracks:a};this.log("Updating audio tracks, "+a.length+" track(s) found in group(s): "+(null==r?void 0:r.join(","))),this.hls.trigger(S.AUDIO_TRACKS_UPDATED,h);var d=this.trackId;if(-1!==u&&-1===d)this.setAudioTrack(u);else if(a.length&&-1===d){var c,f=new Error("No audio track selected for current audio group-ID(s): "+(null==(c=this.groupIds)?void 0:c.join(","))+" track count: "+a.length);this.warn(f.message),this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.AUDIO_TRACK_LOAD_ERROR,fatal:!0,error:f})}}else this.shouldReloadPlaylist(n)&&this.setAudioTrack(this.trackId)}},r.onError=function(t,e){!e.fatal&&e.context&&(e.context.type!==xe||e.context.id!==this.trackId||this.groupIds&&-1===this.groupIds.indexOf(e.context.groupId)||(this.requestScheduled=-1,this.checkRetry(e)))},r.setAudioOption=function(t){var e=this.hls;if(e.config.audioPreference=t,t){var r=this.allAudioTracks;if(this.selectDefaultTrack=!1,r.length){var i=this.currentTrack;if(i&&Vr(t,i,Yr))return i;var n=Hr(t,this.tracksInGroup,Yr);if(n>-1){var a=this.tracksInGroup[n];return this.setAudioTrack(n),a}if(i){var s=e.loadLevel;-1===s&&(s=e.firstAutoLevel);var o=function(t,e,r,i,n){var a=e[i],s=e.reduce((function(t,e,r){var i=e.uri;return(t[i]||(t[i]=[])).push(r),t}),{})[a.uri];s.length>1&&(i=Math.max.apply(Math,s));var o=a.videoRange,l=a.frameRate,u=a.codecSet.substring(0,4),h=Wr(e,i,(function(e){if(e.videoRange!==o||e.frameRate!==l||e.codecSet.substring(0,4)!==u)return!1;var i=e.audioGroups,a=r.filter((function(t){return!i||-1!==i.indexOf(t.groupId)}));return Hr(t,a,n)>-1}));return h>-1?h:Wr(e,i,(function(e){var i=e.audioGroups,a=r.filter((function(t){return!i||-1!==i.indexOf(t.groupId)}));return Hr(t,a,n)>-1}))}(t,e.levels,r,s,Yr);if(-1===o)return null;e.nextLoadLevel=o}if(t.channels||t.audioCodec){var l=Hr(t,r);if(l>-1)return r[l]}}}return null},r.setAudioTrack=function(t){var e=this.tracksInGroup;if(t<0||t>=e.length)this.warn("Invalid audio track id: "+t);else{this.clearTimer(),this.selectDefaultTrack=!1;var r=this.currentTrack,n=e[t],a=n.details&&!n.details.live;if(!(t===this.trackId&&n===r&&a||(this.log("Switching to audio-track "+t+' "'+n.name+'" lang:'+n.lang+" group:"+n.groupId+" channels:"+n.channels),this.trackId=t,this.currentTrack=n,this.hls.trigger(S.AUDIO_TRACK_SWITCHING,i({},n)),a))){var s=this.switchParams(n.url,null==r?void 0:r.details,n.details);this.loadPlaylist(s)}}},r.findTrackId=function(t){for(var e=this.tracksInGroup,r=0;r<e.length;r++){var i=e[r];if((!this.selectDefaultTrack||i.default)&&(!t||Vr(t,i,Yr)))return r}if(t){for(var n=t.name,a=t.lang,s=t.assocLang,o=t.characteristics,l=t.audioCodec,u=t.channels,h=0;h<e.length;h++)if(Vr({name:n,lang:a,assocLang:s,characteristics:o,audioCodec:l,channels:u},e[h],Yr))return h;for(var d=0;d<e.length;d++){var c=e[d];if(zn(t.attrs,c.attrs,["LANGUAGE","ASSOC-LANGUAGE","CHARACTERISTICS"]))return d}for(var f=0;f<e.length;f++){var g=e[f];if(zn(t.attrs,g.attrs,["LANGUAGE"]))return f}}return-1},r.loadPlaylist=function(e){var r=this.currentTrack;if(this.shouldLoadPlaylist(r)&&r){t.prototype.loadPlaylist.call(this);var i=r.id,n=r.groupId,a=r.url;if(e)try{a=e.addDirectives(a)}catch(t){this.warn("Could not construct new URL with HLS Delivery Directives: "+t)}this.log("loading audio-track playlist "+i+' "'+r.name+'" lang:'+r.lang+" group:"+n),this.clearTimer(),this.hls.trigger(S.AUDIO_TRACK_LOADING,{url:a,id:i,groupId:n,deliveryDirectives:e||null})}},s(e,[{key:"allAudioTracks",get:function(){return this.tracks}},{key:"audioTracks",get:function(){return this.tracksInGroup}},{key:"audioTrack",get:function(){return this.trackId},set:function(t){this.selectDefaultTrack=!1,this.setAudioTrack(t)}}]),e}(Fr),Zn=function(t){function e(e,r,i){var n;return(n=t.call(this,e,r,i,"[subtitle-stream-controller]",Oe)||this).currentTrackId=-1,n.tracksBuffered=[],n.mainDetails=null,n._registerListeners(),n}l(e,t);var r=e.prototype;return r.onHandlerDestroying=function(){this._unregisterListeners(),t.prototype.onHandlerDestroying.call(this),this.mainDetails=null},r._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.ERROR,this.onError,this),t.on(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.on(S.SUBTITLE_TRACK_SWITCH,this.onSubtitleTrackSwitch,this),t.on(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.on(S.SUBTITLE_FRAG_PROCESSED,this.onSubtitleFragProcessed,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.ERROR,this.onError,this),t.off(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.off(S.SUBTITLE_TRACK_SWITCH,this.onSubtitleTrackSwitch,this),t.off(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.off(S.SUBTITLE_FRAG_PROCESSED,this.onSubtitleFragProcessed,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this)},r.startLoad=function(t){this.stopLoad(),this.state=Ti,this.setInterval(500),this.nextLoadPosition=this.startPosition=this.lastCurrentTime=t,this.tick()},r.onManifestLoading=function(){this.mainDetails=null,this.fragmentTracker.removeAllFragments()},r.onMediaDetaching=function(){this.tracksBuffered=[],t.prototype.onMediaDetaching.call(this)},r.onLevelLoaded=function(t,e){this.mainDetails=e.details},r.onSubtitleFragProcessed=function(t,e){var r=e.frag,i=e.success;if(this.fragPrevious=r,this.state=Ti,i){var n=this.tracksBuffered[this.currentTrackId];if(n){for(var a,s=r.start,o=0;o<n.length;o++)if(s>=n[o].start&&s<=n[o].end){a=n[o];break}var l=r.start+r.duration;a?a.end=l:(a={start:s,end:l},n.push(a)),this.fragmentTracker.fragBuffered(r),this.fragBufferedComplete(r,null)}}},r.onBufferFlushing=function(t,e){var r=e.startOffset,i=e.endOffset;if(0===r&&i!==Number.POSITIVE_INFINITY){var n=i-1;if(n<=0)return;e.endOffsetSubtitles=Math.max(0,n),this.tracksBuffered.forEach((function(t){for(var e=0;e<t.length;)if(t[e].end<=n)t.shift();else{if(!(t[e].start<n))break;t[e].start=n,e++}})),this.fragmentTracker.removeFragmentsInRange(r,n,Oe)}},r.onFragBuffered=function(t,e){var r;this.loadedmetadata||e.frag.type!==Fe||null!=(r=this.media)&&r.buffered.length&&(this.loadedmetadata=!0)},r.onError=function(t,e){var r=e.frag;(null==r?void 0:r.type)===Oe&&(e.details===A.FRAG_GAP&&this.fragmentTracker.fragBuffered(r,!0),this.fragCurrent&&this.fragCurrent.abortRequests(),this.state!==Ei&&(this.state=Ti))},r.onSubtitleTracksUpdated=function(t,e){var r=this,i=e.subtitleTracks;this.levels&&Xn(this.levels,i)?this.levels=i.map((function(t){return new or(t)})):(this.tracksBuffered=[],this.levels=i.map((function(t){var e=new or(t);return r.tracksBuffered[e.id]=[],e})),this.fragmentTracker.removeFragmentsInRange(0,Number.POSITIVE_INFINITY,Oe),this.fragPrevious=null,this.mediaBuffer=null)},r.onSubtitleTrackSwitch=function(t,e){var r;if(this.currentTrackId=e.id,null!=(r=this.levels)&&r.length&&-1!==this.currentTrackId){var i=this.levels[this.currentTrackId];null!=i&&i.details?this.mediaBuffer=this.mediaBufferTimeRanges:this.mediaBuffer=null,i&&this.setInterval(500)}else this.clearInterval()},r.onSubtitleTrackLoaded=function(t,e){var r,i=this.currentTrackId,n=this.levels,a=e.details,s=e.id;if(n){var o=n[s];if(!(s>=n.length)&&o){this.log("Subtitle track "+s+" loaded ["+a.startSN+","+a.endSN+"]"+(a.lastPartSn?"[part-"+a.lastPartSn+"-"+a.lastPartIndex+"]":"")+",duration:"+a.totalduration),this.mediaBuffer=this.mediaBufferTimeRanges;var l=0;if(a.live||null!=(r=o.details)&&r.live){var u=this.mainDetails;if(a.deltaUpdateFailed||!u)return;var h,d=u.fragments[0];o.details?0===(l=this.alignPlaylists(a,o.details,null==(h=this.levelLastLoaded)?void 0:h.details))&&d&&fr(a,l=d.start):a.hasProgramDateTime&&u.hasProgramDateTime?(li(a,u),l=a.fragments[0].start):d&&fr(a,l=d.start)}o.details=a,this.levelLastLoaded=o,s===i&&(this.startFragRequested||!this.mainDetails&&a.live||this.setStartPosition(this.mainDetails||a,l),this.tick(),a.live&&!this.fragCurrent&&this.media&&this.state===Ti&&(Ar(null,a.fragments,this.media.currentTime,0)||(this.warn("Subtitle playlist not aligned with playback"),o.details=void 0)))}}else this.warn("Subtitle tracks were reset while loading level "+s)},r._handleFragmentLoadComplete=function(t){var e=this,r=t.frag,i=t.payload,n=r.decryptdata,a=this.hls;if(!this.fragContextChanged(r)&&i&&i.byteLength>0&&null!=n&&n.key&&n.iv&&"AES-128"===n.method){var s=performance.now();this.decrypter.decrypt(new Uint8Array(i),n.key.buffer,n.iv.buffer).catch((function(t){throw a.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.FRAG_DECRYPT_ERROR,fatal:!1,error:t,reason:t.message,frag:r}),t})).then((function(t){var e=performance.now();a.trigger(S.FRAG_DECRYPTED,{frag:r,payload:t,stats:{tstart:s,tdecrypt:e}})})).catch((function(t){e.warn(t.name+": "+t.message),e.state=Ti}))}},r.doTick=function(){if(this.media){if(this.state===Ti){var t=this.currentTrackId,e=this.levels,r=null==e?void 0:e[t];if(!r||!e.length||!r.details)return;var i=this.config,n=this.getLoadPosition(),a=ri.bufferedInfo(this.tracksBuffered[this.currentTrackId]||[],n,i.maxBufferHole),s=a.end,o=a.len,l=this.getFwdBufferInfo(this.media,Fe),u=r.details;if(o>this.getMaxBufferLength(null==l?void 0:l.len)+u.levelTargetDuration)return;var h=u.fragments,d=h.length,c=u.edge,f=null,g=this.fragPrevious;if(s<c){var v=i.maxFragLookUpTolerance,m=s>c-v?0:v;!(f=Ar(g,h,Math.max(h[0].start,s),m))&&g&&g.start<h[0].start&&(f=h[0])}else f=h[d-1];if(!f)return;if("initSegment"!==(f=this.mapToInitFragWhenRequired(f)).sn){var p=h[f.sn-u.startSN-1];p&&p.cc===f.cc&&this.fragmentTracker.getState(p)===Xr&&(f=p)}this.fragmentTracker.getState(f)===Xr&&this.loadFragment(f,r,s)}}else this.state=Ti},r.getMaxBufferLength=function(e){var r=t.prototype.getMaxBufferLength.call(this);return e?Math.max(r,e):r},r.loadFragment=function(e,r,i){this.fragCurrent=e,"initSegment"===e.sn?this._loadInitSegment(e,r):(this.startFragRequested=!0,t.prototype.loadFragment.call(this,e,r,i))},s(e,[{key:"mediaBufferTimeRanges",get:function(){return new ta(this.tracksBuffered[this.currentTrackId]||[])}}]),e}(_i),ta=function(t){this.buffered=void 0;var e=function(e,r,i){if((r>>>=0)>i-1)throw new DOMException("Failed to execute '"+e+"' on 'TimeRanges': The index provided ("+r+") is greater than the maximum bound ("+i+")");return t[r][e]};this.buffered={get length(){return t.length},end:function(r){return e("end",r,t.length)},start:function(r){return e("start",r,t.length)}}},ea=function(t){function e(e){var r;return(r=t.call(this,e,"[subtitle-track-controller]")||this).media=null,r.tracks=[],r.groupIds=null,r.tracksInGroup=[],r.trackId=-1,r.currentTrack=null,r.selectDefaultTrack=!0,r.queuedDefaultTrack=-1,r.asyncPollTrackChange=function(){return r.pollTrackChange(0)},r.useTextTrackPolling=!1,r.subtitlePollingInterval=-1,r._subtitleDisplay=!0,r.onTextTracksChanged=function(){if(r.useTextTrackPolling||self.clearInterval(r.subtitlePollingInterval),r.media&&r.hls.config.renderTextTracksNatively){for(var t=null,e=Ye(r.media.textTracks),i=0;i<e.length;i++)if("hidden"===e[i].mode)t=e[i];else if("showing"===e[i].mode){t=e[i];break}var n=r.findTrackForTextTrack(t);r.subtitleTrack!==n&&r.setSubtitleTrack(n)}},r.registerListeners(),r}l(e,t);var r=e.prototype;return r.destroy=function(){this.unregisterListeners(),this.tracks.length=0,this.tracksInGroup.length=0,this.currentTrack=null,this.onTextTracksChanged=this.asyncPollTrackChange=null,t.prototype.destroy.call(this)},r.registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.on(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.on(S.ERROR,this.onError,this)},r.unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVEL_LOADING,this.onLevelLoading,this),t.off(S.LEVEL_SWITCHING,this.onLevelSwitching,this),t.off(S.SUBTITLE_TRACK_LOADED,this.onSubtitleTrackLoaded,this),t.off(S.ERROR,this.onError,this)},r.onMediaAttached=function(t,e){this.media=e.media,this.media&&(this.queuedDefaultTrack>-1&&(this.subtitleTrack=this.queuedDefaultTrack,this.queuedDefaultTrack=-1),this.useTextTrackPolling=!(this.media.textTracks&&"onchange"in this.media.textTracks),this.useTextTrackPolling?this.pollTrackChange(500):this.media.textTracks.addEventListener("change",this.asyncPollTrackChange))},r.pollTrackChange=function(t){self.clearInterval(this.subtitlePollingInterval),this.subtitlePollingInterval=self.setInterval(this.onTextTracksChanged,t)},r.onMediaDetaching=function(){this.media&&(self.clearInterval(this.subtitlePollingInterval),this.useTextTrackPolling||this.media.textTracks.removeEventListener("change",this.asyncPollTrackChange),this.trackId>-1&&(this.queuedDefaultTrack=this.trackId),Ye(this.media.textTracks).forEach((function(t){He(t)})),this.subtitleTrack=-1,this.media=null)},r.onManifestLoading=function(){this.tracks=[],this.groupIds=null,this.tracksInGroup=[],this.trackId=-1,this.currentTrack=null,this.selectDefaultTrack=!0},r.onManifestParsed=function(t,e){this.tracks=e.subtitleTracks},r.onSubtitleTrackLoaded=function(t,e){var r=e.id,i=e.groupId,n=e.details,a=this.tracksInGroup[r];if(a&&a.groupId===i){var s=a.details;a.details=e.details,this.log("Subtitle track "+r+' "'+a.name+'" lang:'+a.lang+" group:"+i+" loaded ["+n.startSN+"-"+n.endSN+"]"),r===this.trackId&&this.playlistLoaded(r,e,s)}else this.warn("Subtitle track with id:"+r+" and group:"+i+" not found in active group "+(null==a?void 0:a.groupId))},r.onLevelLoading=function(t,e){this.switchLevel(e.level)},r.onLevelSwitching=function(t,e){this.switchLevel(e.level)},r.switchLevel=function(t){var e=this.hls.levels[t];if(e){var r=e.subtitleGroups||null,i=this.groupIds,n=this.currentTrack;if(!r||(null==i?void 0:i.length)!==(null==r?void 0:r.length)||null!=r&&r.some((function(t){return-1===(null==i?void 0:i.indexOf(t))}))){this.groupIds=r,this.trackId=-1,this.currentTrack=null;var a=this.tracks.filter((function(t){return!r||-1!==r.indexOf(t.groupId)}));if(a.length)this.selectDefaultTrack&&!a.some((function(t){return t.default}))&&(this.selectDefaultTrack=!1),a.forEach((function(t,e){t.id=e}));else if(!n&&!this.tracksInGroup.length)return;this.tracksInGroup=a;var s=this.hls.config.subtitlePreference;if(!n&&s){this.selectDefaultTrack=!1;var o=Hr(s,a);if(o>-1)n=a[o];else{var l=Hr(s,this.tracks);n=this.tracks[l]}}var u=this.findTrackId(n);-1===u&&n&&(u=this.findTrackId(null));var h={subtitleTracks:a};this.log("Updating subtitle tracks, "+a.length+' track(s) found in "'+(null==r?void 0:r.join(","))+'" group-id'),this.hls.trigger(S.SUBTITLE_TRACKS_UPDATED,h),-1!==u&&-1===this.trackId&&this.setSubtitleTrack(u)}else this.shouldReloadPlaylist(n)&&this.setSubtitleTrack(this.trackId)}},r.findTrackId=function(t){for(var e=this.tracksInGroup,r=this.selectDefaultTrack,i=0;i<e.length;i++){var n=e[i];if((!r||n.default)&&(r||t)&&(!t||Vr(n,t)))return i}if(t){for(var a=0;a<e.length;a++){var s=e[a];if(zn(t.attrs,s.attrs,["LANGUAGE","ASSOC-LANGUAGE","CHARACTERISTICS"]))return a}for(var o=0;o<e.length;o++){var l=e[o];if(zn(t.attrs,l.attrs,["LANGUAGE"]))return o}}return-1},r.findTrackForTextTrack=function(t){if(t)for(var e=this.tracksInGroup,r=0;r<e.length;r++)if(Qn(e[r],t))return r;return-1},r.onError=function(t,e){!e.fatal&&e.context&&(e.context.type!==Pe||e.context.id!==this.trackId||this.groupIds&&-1===this.groupIds.indexOf(e.context.groupId)||this.checkRetry(e))},r.setSubtitleOption=function(t){if(this.hls.config.subtitlePreference=t,t){var e=this.allSubtitleTracks;if(this.selectDefaultTrack=!1,e.length){var r=this.currentTrack;if(r&&Vr(t,r))return r;var i=Hr(t,this.tracksInGroup);if(i>-1){var n=this.tracksInGroup[i];return this.setSubtitleTrack(i),n}if(r)return null;var a=Hr(t,e);if(a>-1)return e[a]}}return null},r.loadPlaylist=function(e){t.prototype.loadPlaylist.call(this);var r=this.currentTrack;if(this.shouldLoadPlaylist(r)&&r){var i=r.id,n=r.groupId,a=r.url;if(e)try{a=e.addDirectives(a)}catch(t){this.warn("Could not construct new URL with HLS Delivery Directives: "+t)}this.log("Loading subtitle playlist for id "+i),this.hls.trigger(S.SUBTITLE_TRACK_LOADING,{url:a,id:i,groupId:n,deliveryDirectives:e||null})}},r.toggleTrackModes=function(){var t=this.media;if(t){var e,r=Ye(t.textTracks),i=this.currentTrack;if(i&&((e=r.filter((function(t){return Qn(i,t)}))[0])||this.warn('Unable to find subtitle TextTrack with name "'+i.name+'" and language "'+i.lang+'"')),[].slice.call(r).forEach((function(t){"disabled"!==t.mode&&t!==e&&(t.mode="disabled")})),e){var n=this.subtitleDisplay?"showing":"hidden";e.mode!==n&&(e.mode=n)}}},r.setSubtitleTrack=function(t){var e=this.tracksInGroup;if(this.media)if(t<-1||t>=e.length||!y(t))this.warn("Invalid subtitle track id: "+t);else{this.clearTimer(),this.selectDefaultTrack=!1;var r=this.currentTrack,i=e[t]||null;if(this.trackId=t,this.currentTrack=i,this.toggleTrackModes(),i){var n=!!i.details&&!i.details.live;if(t!==this.trackId||i!==r||!n){this.log("Switching to subtitle-track "+t+(i?' "'+i.name+'" lang:'+i.lang+" group:"+i.groupId:""));var a=i.id,s=i.groupId,o=void 0===s?"":s,l=i.name,u=i.type,h=i.url;this.hls.trigger(S.SUBTITLE_TRACK_SWITCH,{id:a,groupId:o,name:l,type:u,url:h});var d=this.switchParams(i.url,null==r?void 0:r.details,i.details);this.loadPlaylist(d)}}else this.hls.trigger(S.SUBTITLE_TRACK_SWITCH,{id:t})}else this.queuedDefaultTrack=t},s(e,[{key:"subtitleDisplay",get:function(){return this._subtitleDisplay},set:function(t){this._subtitleDisplay=t,this.trackId>-1&&this.toggleTrackModes()}},{key:"allSubtitleTracks",get:function(){return this.tracks}},{key:"subtitleTracks",get:function(){return this.tracksInGroup}},{key:"subtitleTrack",get:function(){return this.trackId},set:function(t){this.selectDefaultTrack=!1,this.setSubtitleTrack(t)}}]),e}(Fr),ra=function(){function t(t){this.buffers=void 0,this.queues={video:[],audio:[],audiovideo:[]},this.buffers=t}var e=t.prototype;return e.append=function(t,e,r){var i=this.queues[e];i.push(t),1!==i.length||r||this.executeNext(e)},e.insertAbort=function(t,e){this.queues[e].unshift(t),this.executeNext(e)},e.appendBlocker=function(t){var e,r=new Promise((function(t){e=t})),i={execute:e,onStart:function(){},onComplete:function(){},onError:function(){}};return this.append(i,t),r},e.executeNext=function(t){var e=this.queues[t];if(e.length){var r=e[0];try{r.execute()}catch(e){w.warn('[buffer-operation-queue]: Exception executing "'+t+'" SourceBuffer operation: '+e),r.onError(e);var i=this.buffers[t];null!=i&&i.updating||this.shiftAndExecuteNext(t)}}},e.shiftAndExecuteNext=function(t){this.queues[t].shift(),this.executeNext(t)},e.current=function(t){return this.queues[t][0]},t}(),ia=/(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/,na=function(){function t(t){var e=this;this.details=null,this._objectUrl=null,this.operationQueue=void 0,this.listeners=void 0,this.hls=void 0,this.bufferCodecEventsExpected=0,this._bufferCodecEventsTotal=0,this.media=null,this.mediaSource=null,this.lastMpegAudioChunk=null,this.appendSource=void 0,this.appendErrors={audio:0,video:0,audiovideo:0},this.tracks={},this.pendingTracks={},this.sourceBuffer=void 0,this.log=void 0,this.warn=void 0,this.error=void 0,this._onEndStreaming=function(t){e.hls&&e.hls.pauseBuffering()},this._onStartStreaming=function(t){e.hls&&e.hls.resumeBuffering()},this._onMediaSourceOpen=function(){var t=e.media,r=e.mediaSource;e.log("Media source opened"),t&&(t.removeEventListener("emptied",e._onMediaEmptied),e.updateMediaElementDuration(),e.hls.trigger(S.MEDIA_ATTACHED,{media:t,mediaSource:r})),r&&r.removeEventListener("sourceopen",e._onMediaSourceOpen),e.checkPendingTracks()},this._onMediaSourceClose=function(){e.log("Media source closed")},this._onMediaSourceEnded=function(){e.log("Media source ended")},this._onMediaEmptied=function(){var t=e.mediaSrc,r=e._objectUrl;t!==r&&w.error("Media element src was set while attaching MediaSource ("+r+" > "+t+")")},this.hls=t;var r,i="[buffer-controller]";this.appendSource=(r=se(t.config.preferManagedMediaSource),"undefined"!=typeof self&&r===self.ManagedMediaSource),this.log=w.log.bind(w,i),this.warn=w.warn.bind(w,i),this.error=w.error.bind(w,i),this._initSourceBuffer(),this.registerListeners()}var e=t.prototype;return e.hasSourceTypes=function(){return this.getSourceBufferTypes().length>0||Object.keys(this.pendingTracks).length>0},e.destroy=function(){this.unregisterListeners(),this.details=null,this.lastMpegAudioChunk=null,this.hls=null},e.registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.BUFFER_RESET,this.onBufferReset,this),t.on(S.BUFFER_APPENDING,this.onBufferAppending,this),t.on(S.BUFFER_CODECS,this.onBufferCodecs,this),t.on(S.BUFFER_EOS,this.onBufferEos,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.on(S.LEVEL_UPDATED,this.onLevelUpdated,this),t.on(S.FRAG_PARSED,this.onFragParsed,this),t.on(S.FRAG_CHANGED,this.onFragChanged,this)},e.unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.BUFFER_RESET,this.onBufferReset,this),t.off(S.BUFFER_APPENDING,this.onBufferAppending,this),t.off(S.BUFFER_CODECS,this.onBufferCodecs,this),t.off(S.BUFFER_EOS,this.onBufferEos,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),t.off(S.LEVEL_UPDATED,this.onLevelUpdated,this),t.off(S.FRAG_PARSED,this.onFragParsed,this),t.off(S.FRAG_CHANGED,this.onFragChanged,this)},e._initSourceBuffer=function(){this.sourceBuffer={},this.operationQueue=new ra(this.sourceBuffer),this.listeners={audio:[],video:[],audiovideo:[]},this.appendErrors={audio:0,video:0,audiovideo:0},this.lastMpegAudioChunk=null},e.onManifestLoading=function(){this.bufferCodecEventsExpected=this._bufferCodecEventsTotal=0,this.details=null},e.onManifestParsed=function(t,e){var r=2;(e.audio&&!e.video||!e.altAudio)&&(r=1),this.bufferCodecEventsExpected=this._bufferCodecEventsTotal=r,this.log(this.bufferCodecEventsExpected+" bufferCodec event(s) expected")},e.onMediaAttaching=function(t,e){var r=this.media=e.media,i=se(this.appendSource);if(r&&i){var n,a=this.mediaSource=new i;this.log("created media source: "+(null==(n=a.constructor)?void 0:n.name)),a.addEventListener("sourceopen",this._onMediaSourceOpen),a.addEventListener("sourceended",this._onMediaSourceEnded),a.addEventListener("sourceclose",this._onMediaSourceClose),this.appendSource&&(a.addEventListener("startstreaming",this._onStartStreaming),a.addEventListener("endstreaming",this._onEndStreaming));var s=this._objectUrl=self.URL.createObjectURL(a);if(this.appendSource)try{r.removeAttribute("src");var o=self.ManagedMediaSource;r.disableRemotePlayback=r.disableRemotePlayback||o&&a instanceof o,aa(r),function(t,e){var r=self.document.createElement("source");r.type="video/mp4",r.src=e,t.appendChild(r)}(r,s),r.load()}catch(t){r.src=s}else r.src=s;r.addEventListener("emptied",this._onMediaEmptied)}},e.onMediaDetaching=function(){var t=this.media,e=this.mediaSource,r=this._objectUrl;if(e){if(this.log("media source detaching"),"open"===e.readyState)try{e.endOfStream()}catch(t){this.warn("onMediaDetaching: "+t.message+" while calling endOfStream")}this.onBufferReset(),e.removeEventListener("sourceopen",this._onMediaSourceOpen),e.removeEventListener("sourceended",this._onMediaSourceEnded),e.removeEventListener("sourceclose",this._onMediaSourceClose),this.appendSource&&(e.removeEventListener("startstreaming",this._onStartStreaming),e.removeEventListener("endstreaming",this._onEndStreaming)),t&&(t.removeEventListener("emptied",this._onMediaEmptied),r&&self.URL.revokeObjectURL(r),this.mediaSrc===r?(t.removeAttribute("src"),this.appendSource&&aa(t),t.load()):this.warn("media|source.src was changed by a third party - skip cleanup")),this.mediaSource=null,this.media=null,this._objectUrl=null,this.bufferCodecEventsExpected=this._bufferCodecEventsTotal,this.pendingTracks={},this.tracks={}}this.hls.trigger(S.MEDIA_DETACHED,void 0)},e.onBufferReset=function(){var t=this;this.getSourceBufferTypes().forEach((function(e){t.resetBuffer(e)})),this._initSourceBuffer()},e.resetBuffer=function(t){var e=this.sourceBuffer[t];try{var r;e&&(this.removeBufferListeners(t),this.sourceBuffer[t]=void 0,null!=(r=this.mediaSource)&&r.sourceBuffers.length&&this.mediaSource.removeSourceBuffer(e))}catch(e){this.warn("onBufferReset "+t,e)}},e.onBufferCodecs=function(t,e){var r=this,i=this.getSourceBufferTypes().length,n=Object.keys(e);if(n.forEach((function(t){if(i){var n=r.tracks[t];if(n&&"function"==typeof n.buffer.changeType){var a,s=e[t],o=s.id,l=s.codec,u=s.levelCodec,h=s.container,d=s.metadata,c=me(n.codec,n.levelCodec),f=null==c?void 0:c.replace(ia,"$1"),g=me(l,u),v=null==(a=g)?void 0:a.replace(ia,"$1");if(g&&f!==v){"audio"===t.slice(0,5)&&(g=ve(g,r.appendSource));var m=h+";codecs="+g;r.appendChangeType(t,m),r.log("switching codec "+c+" to "+g),r.tracks[t]={buffer:n.buffer,codec:l,container:h,levelCodec:u,metadata:d,id:o}}}}else r.pendingTracks[t]=e[t]})),!i){var a=Math.max(this.bufferCodecEventsExpected-1,0);this.bufferCodecEventsExpected!==a&&(this.log(a+" bufferCodec event(s) expected "+n.join(",")),this.bufferCodecEventsExpected=a),this.mediaSource&&"open"===this.mediaSource.readyState&&this.checkPendingTracks()}},e.appendChangeType=function(t,e){var r=this,i=this.operationQueue,n={execute:function(){var n=r.sourceBuffer[t];n&&(r.log("changing "+t+" sourceBuffer type to "+e),n.changeType(e)),i.shiftAndExecuteNext(t)},onStart:function(){},onComplete:function(){},onError:function(e){r.warn("Failed to change "+t+" SourceBuffer type",e)}};i.append(n,t,!!this.pendingTracks[t])},e.onBufferAppending=function(t,e){var r=this,i=this.hls,n=this.operationQueue,a=this.tracks,s=e.data,o=e.type,l=e.frag,u=e.part,h=e.chunkMeta,d=h.buffering[o],c=self.performance.now();d.start=c;var f=l.stats.buffering,g=u?u.stats.buffering:null;0===f.start&&(f.start=c),g&&0===g.start&&(g.start=c);var v=a.audio,m=!1;"audio"===o&&"audio/mpeg"===(null==v?void 0:v.container)&&(m=!this.lastMpegAudioChunk||1===h.id||this.lastMpegAudioChunk.sn!==h.sn,this.lastMpegAudioChunk=h);var p=l.start,y={execute:function(){if(d.executeStart=self.performance.now(),m){var t=r.sourceBuffer[o];if(t){var e=p-t.timestampOffset;Math.abs(e)>=.1&&(r.log("Updating audio SourceBuffer timestampOffset to "+p+" (delta: "+e+") sn: "+l.sn+")"),t.timestampOffset=p)}}r.appendExecutor(s,o)},onStart:function(){},onComplete:function(){var t=self.performance.now();d.executeEnd=d.end=t,0===f.first&&(f.first=t),g&&0===g.first&&(g.first=t);var e=r.sourceBuffer,i={};for(var n in e)i[n]=ri.getBuffered(e[n]);r.appendErrors[o]=0,"audio"===o||"video"===o?r.appendErrors.audiovideo=0:(r.appendErrors.audio=0,r.appendErrors.video=0),r.hls.trigger(S.BUFFER_APPENDED,{type:o,frag:l,part:u,chunkMeta:h,parent:l.type,timeRanges:i})},onError:function(t){var e={type:L.MEDIA_ERROR,parent:l.type,details:A.BUFFER_APPEND_ERROR,sourceBufferName:o,frag:l,part:u,chunkMeta:h,error:t,err:t,fatal:!1};if(t.code===DOMException.QUOTA_EXCEEDED_ERR)e.details=A.BUFFER_FULL_ERROR;else{var n=++r.appendErrors[o];e.details=A.BUFFER_APPEND_ERROR,r.warn("Failed "+n+"/"+i.config.appendErrorMaxRetry+' times to append segment in "'+o+'" sourceBuffer'),n>=i.config.appendErrorMaxRetry&&(e.fatal=!0)}i.trigger(S.ERROR,e)}};n.append(y,o,!!this.pendingTracks[o])},e.onBufferFlushing=function(t,e){var r=this,i=this.operationQueue,n=function(t){return{execute:r.removeExecutor.bind(r,t,e.startOffset,e.endOffset),onStart:function(){},onComplete:function(){r.hls.trigger(S.BUFFER_FLUSHED,{type:t})},onError:function(e){r.warn("Failed to remove from "+t+" SourceBuffer",e)}}};e.type?i.append(n(e.type),e.type):this.getSourceBufferTypes().forEach((function(t){i.append(n(t),t)}))},e.onFragParsed=function(t,e){var r=this,i=e.frag,n=e.part,a=[],s=n?n.elementaryStreams:i.elementaryStreams;s[U]?a.push("audiovideo"):(s[O]&&a.push("audio"),s[N]&&a.push("video")),0===a.length&&this.warn("Fragments must have at least one ElementaryStreamType set. type: "+i.type+" level: "+i.level+" sn: "+i.sn),this.blockBuffers((function(){var t=self.performance.now();i.stats.buffering.end=t,n&&(n.stats.buffering.end=t);var e=n?n.stats:i.stats;r.hls.trigger(S.FRAG_BUFFERED,{frag:i,part:n,stats:e,id:i.type})}),a)},e.onFragChanged=function(t,e){this.trimBuffers()},e.onBufferEos=function(t,e){var r=this;this.getSourceBufferTypes().reduce((function(t,i){var n=r.sourceBuffer[i];return!n||e.type&&e.type!==i||(n.ending=!0,n.ended||(n.ended=!0,r.log(i+" sourceBuffer now EOS"))),t&&!(n&&!n.ended)}),!0)&&(this.log("Queueing mediaSource.endOfStream()"),this.blockBuffers((function(){r.getSourceBufferTypes().forEach((function(t){var e=r.sourceBuffer[t];e&&(e.ending=!1)}));var t=r.mediaSource;t&&"open"===t.readyState?(r.log("Calling mediaSource.endOfStream()"),t.endOfStream()):t&&r.log("Could not call mediaSource.endOfStream(). mediaSource.readyState: "+t.readyState)})))},e.onLevelUpdated=function(t,e){var r=e.details;r.fragments.length&&(this.details=r,this.getSourceBufferTypes().length?this.blockBuffers(this.updateMediaElementDuration.bind(this)):this.updateMediaElementDuration())},e.trimBuffers=function(){var t=this.hls,e=this.details,r=this.media;if(r&&null!==e&&this.getSourceBufferTypes().length){var i=t.config,n=r.currentTime,a=e.levelTargetDuration,s=e.live&&null!==i.liveBackBufferLength?i.liveBackBufferLength:i.backBufferLength;if(y(s)&&s>0){var o=Math.max(s,a),l=Math.floor(n/a)*a-o;this.flushBackBuffer(n,a,l)}if(y(i.frontBufferFlushThreshold)&&i.frontBufferFlushThreshold>0){var u=Math.max(i.maxBufferLength,i.frontBufferFlushThreshold),h=Math.max(u,a),d=Math.floor(n/a)*a+h;this.flushFrontBuffer(n,a,d)}}},e.flushBackBuffer=function(t,e,r){var i=this,n=this.details,a=this.sourceBuffer;this.getSourceBufferTypes().forEach((function(s){var o=a[s];if(o){var l=ri.getBuffered(o);if(l.length>0&&r>l.start(0)){if(i.hls.trigger(S.BACK_BUFFER_REACHED,{bufferEnd:r}),null!=n&&n.live)i.hls.trigger(S.LIVE_BACK_BUFFER_REACHED,{bufferEnd:r});else if(o.ended&&l.end(l.length-1)-t<2*e)return void i.log("Cannot flush "+s+" back buffer while SourceBuffer is in ended state");i.hls.trigger(S.BUFFER_FLUSHING,{startOffset:0,endOffset:r,type:s})}}}))},e.flushFrontBuffer=function(t,e,r){var i=this,n=this.sourceBuffer;this.getSourceBufferTypes().forEach((function(a){var s=n[a];if(s){var o=ri.getBuffered(s),l=o.length;if(l<2)return;var u=o.start(l-1),h=o.end(l-1);if(r>u||t>=u&&t<=h)return;if(s.ended&&t-h<2*e)return void i.log("Cannot flush "+a+" front buffer while SourceBuffer is in ended state");i.hls.trigger(S.BUFFER_FLUSHING,{startOffset:u,endOffset:1/0,type:a})}}))},e.updateMediaElementDuration=function(){if(this.details&&this.media&&this.mediaSource&&"open"===this.mediaSource.readyState){var t=this.details,e=this.hls,r=this.media,i=this.mediaSource,n=t.fragments[0].start+t.totalduration,a=r.duration,s=y(i.duration)?i.duration:0;t.live&&e.config.liveDurationInfinity?(i.duration=1/0,this.updateSeekableRange(t)):(n>s&&n>a||!y(a))&&(this.log("Updating Media Source duration to "+n.toFixed(3)),i.duration=n)}},e.updateSeekableRange=function(t){var e=this.mediaSource,r=t.fragments;if(r.length&&t.live&&null!=e&&e.setLiveSeekableRange){var i=Math.max(0,r[0].start),n=Math.max(i,i+t.totalduration);this.log("Media Source duration is set to "+e.duration+". Setting seekable range to "+i+"-"+n+"."),e.setLiveSeekableRange(i,n)}},e.checkPendingTracks=function(){var t=this.bufferCodecEventsExpected,e=this.operationQueue,r=this.pendingTracks,i=Object.keys(r).length;if(i&&(!t||2===i||"audiovideo"in r)){this.createSourceBuffers(r),this.pendingTracks={};var n=this.getSourceBufferTypes();if(n.length)this.hls.trigger(S.BUFFER_CREATED,{tracks:this.tracks}),n.forEach((function(t){e.executeNext(t)}));else{var a=new Error("could not create source buffer for media codec(s)");this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_INCOMPATIBLE_CODECS_ERROR,fatal:!0,error:a,reason:a.message})}}},e.createSourceBuffers=function(t){var e=this,r=this.sourceBuffer,i=this.mediaSource;if(!i)throw Error("createSourceBuffers called when mediaSource was null");var n=function(n){if(!r[n]){var a,s=t[n];if(!s)throw Error("source buffer exists for track "+n+", however track does not");var o=-1===(null==(a=s.levelCodec)?void 0:a.indexOf(","))?s.levelCodec:s.codec;o&&"audio"===n.slice(0,5)&&(o=ve(o,e.appendSource));var l=s.container+";codecs="+o;e.log("creating sourceBuffer("+l+")");try{var u=r[n]=i.addSourceBuffer(l),h=n;e.addBufferListener(h,"updatestart",e._onSBUpdateStart),e.addBufferListener(h,"updateend",e._onSBUpdateEnd),e.addBufferListener(h,"error",e._onSBUpdateError),e.appendSource&&e.addBufferListener(h,"bufferedchange",(function(t,r){var i=r.removedRanges;null!=i&&i.length&&e.hls.trigger(S.BUFFER_FLUSHED,{type:n})})),e.tracks[n]={buffer:u,codec:o,container:s.container,levelCodec:s.levelCodec,metadata:s.metadata,id:s.id}}catch(t){e.error("error while trying to add sourceBuffer: "+t.message),e.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_ADD_CODEC_ERROR,fatal:!1,error:t,sourceBufferName:n,mimeType:l})}}};for(var a in t)n(a)},e._onSBUpdateStart=function(t){this.operationQueue.current(t).onStart()},e._onSBUpdateEnd=function(t){var e;if("closed"!==(null==(e=this.mediaSource)?void 0:e.readyState)){var r=this.operationQueue;r.current(t).onComplete(),r.shiftAndExecuteNext(t)}else this.resetBuffer(t)},e._onSBUpdateError=function(t,e){var r,i=new Error(t+" SourceBuffer error. MediaSource readyState: "+(null==(r=this.mediaSource)?void 0:r.readyState));this.error(""+i,e),this.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_APPENDING_ERROR,sourceBufferName:t,error:i,fatal:!1});var n=this.operationQueue.current(t);n&&n.onError(i)},e.removeExecutor=function(t,e,r){var i=this.media,n=this.mediaSource,a=this.operationQueue,s=this.sourceBuffer[t];if(!i||!n||!s)return this.warn("Attempting to remove from the "+t+" SourceBuffer, but it does not exist"),void a.shiftAndExecuteNext(t);var o=y(i.duration)?i.duration:1/0,l=y(n.duration)?n.duration:1/0,u=Math.max(0,e),h=Math.min(r,o,l);h>u&&(!s.ending||s.ended)?(s.ended=!1,this.log("Removing ["+u+","+h+"] from the "+t+" SourceBuffer"),s.remove(u,h)):a.shiftAndExecuteNext(t)},e.appendExecutor=function(t,e){var r=this.sourceBuffer[e];if(r)r.ended=!1,r.appendBuffer(t);else if(!this.pendingTracks[e])throw new Error("Attempting to append to the "+e+" SourceBuffer, but it does not exist")},e.blockBuffers=function(t,e){var r=this;if(void 0===e&&(e=this.getSourceBufferTypes()),!e.length)return this.log("Blocking operation requested, but no SourceBuffers exist"),void Promise.resolve().then(t);var i=this.operationQueue,n=e.map((function(t){return i.appendBlocker(t)}));Promise.all(n).then((function(){t(),e.forEach((function(t){var e=r.sourceBuffer[t];null!=e&&e.updating||i.shiftAndExecuteNext(t)}))}))},e.getSourceBufferTypes=function(){return Object.keys(this.sourceBuffer)},e.addBufferListener=function(t,e,r){var i=this.sourceBuffer[t];if(i){var n=r.bind(this,t);this.listeners[t].push({event:e,listener:n}),i.addEventListener(e,n)}},e.removeBufferListeners=function(t){var e=this.sourceBuffer[t];e&&this.listeners[t].forEach((function(t){e.removeEventListener(t.event,t.listener)}))},s(t,[{key:"mediaSrc",get:function(){var t,e,r=(null==(t=this.media)||null==(e=t.querySelector)?void 0:e.call(t,"source"))||this.media;return null==r?void 0:r.src}}]),t}();function aa(t){var e=t.querySelectorAll("source");[].slice.call(e).forEach((function(e){t.removeChild(e)}))}var sa={42:225,92:233,94:237,95:243,96:250,123:231,124:247,125:209,126:241,127:9608,128:174,129:176,130:189,131:191,132:8482,133:162,134:163,135:9834,136:224,137:32,138:232,139:226,140:234,141:238,142:244,143:251,144:193,145:201,146:211,147:218,148:220,149:252,150:8216,151:161,152:42,153:8217,154:9473,155:169,156:8480,157:8226,158:8220,159:8221,160:192,161:194,162:199,163:200,164:202,165:203,166:235,167:206,168:207,169:239,170:212,171:217,172:249,173:219,174:171,175:187,176:195,177:227,178:205,179:204,180:236,181:210,182:242,183:213,184:245,185:123,186:125,187:92,188:94,189:95,190:124,191:8764,192:196,193:228,194:214,195:246,196:223,197:165,198:164,199:9475,200:197,201:229,202:216,203:248,204:9487,205:9491,206:9495,207:9499},oa=function(t){return String.fromCharCode(sa[t]||t)},la=15,ua=100,ha={17:1,18:3,21:5,22:7,23:9,16:11,19:12,20:14},da={17:2,18:4,21:6,22:8,23:10,19:13,20:15},ca={25:1,26:3,29:5,30:7,31:9,24:11,27:12,28:14},fa={25:2,26:4,29:6,30:8,31:10,27:13,28:15},ga=["white","green","blue","cyan","red","yellow","magenta","black","transparent"],va=function(){function t(){this.time=null,this.verboseLevel=0}return t.prototype.log=function(t,e){if(this.verboseLevel>=t){var r="function"==typeof e?e():e;w.log(this.time+" ["+t+"] "+r)}},t}(),ma=function(t){for(var e=[],r=0;r<t.length;r++)e.push(t[r].toString(16));return e},pa=function(){function t(){this.foreground="white",this.underline=!1,this.italics=!1,this.background="black",this.flash=!1}var e=t.prototype;return e.reset=function(){this.foreground="white",this.underline=!1,this.italics=!1,this.background="black",this.flash=!1},e.setStyles=function(t){for(var e=["foreground","underline","italics","background","flash"],r=0;r<e.length;r++){var i=e[r];t.hasOwnProperty(i)&&(this[i]=t[i])}},e.isDefault=function(){return"white"===this.foreground&&!this.underline&&!this.italics&&"black"===this.background&&!this.flash},e.equals=function(t){return this.foreground===t.foreground&&this.underline===t.underline&&this.italics===t.italics&&this.background===t.background&&this.flash===t.flash},e.copy=function(t){this.foreground=t.foreground,this.underline=t.underline,this.italics=t.italics,this.background=t.background,this.flash=t.flash},e.toString=function(){return"color="+this.foreground+", underline="+this.underline+", italics="+this.italics+", background="+this.background+", flash="+this.flash},t}(),ya=function(){function t(){this.uchar=" ",this.penState=new pa}var e=t.prototype;return e.reset=function(){this.uchar=" ",this.penState.reset()},e.setChar=function(t,e){this.uchar=t,this.penState.copy(e)},e.setPenState=function(t){this.penState.copy(t)},e.equals=function(t){return this.uchar===t.uchar&&this.penState.equals(t.penState)},e.copy=function(t){this.uchar=t.uchar,this.penState.copy(t.penState)},e.isEmpty=function(){return" "===this.uchar&&this.penState.isDefault()},t}(),Ea=function(){function t(t){this.chars=[],this.pos=0,this.currPenState=new pa,this.cueStartTime=null,this.logger=void 0;for(var e=0;e<ua;e++)this.chars.push(new ya);this.logger=t}var e=t.prototype;return e.equals=function(t){for(var e=0;e<ua;e++)if(!this.chars[e].equals(t.chars[e]))return!1;return!0},e.copy=function(t){for(var e=0;e<ua;e++)this.chars[e].copy(t.chars[e])},e.isEmpty=function(){for(var t=!0,e=0;e<ua;e++)if(!this.chars[e].isEmpty()){t=!1;break}return t},e.setCursor=function(t){this.pos!==t&&(this.pos=t),this.pos<0?(this.logger.log(3,"Negative cursor position "+this.pos),this.pos=0):this.pos>ua&&(this.logger.log(3,"Too large cursor position "+this.pos),this.pos=ua)},e.moveCursor=function(t){var e=this.pos+t;if(t>1)for(var r=this.pos+1;r<e+1;r++)this.chars[r].setPenState(this.currPenState);this.setCursor(e)},e.backSpace=function(){this.moveCursor(-1),this.chars[this.pos].setChar(" ",this.currPenState)},e.insertChar=function(t){var e=this;t>=144&&this.backSpace();var r=oa(t);this.pos>=ua?this.logger.log(0,(function(){return"Cannot insert "+t.toString(16)+" ("+r+") at position "+e.pos+". Skipping it!"})):(this.chars[this.pos].setChar(r,this.currPenState),this.moveCursor(1))},e.clearFromPos=function(t){var e;for(e=t;e<ua;e++)this.chars[e].reset()},e.clear=function(){this.clearFromPos(0),this.pos=0,this.currPenState.reset()},e.clearToEndOfRow=function(){this.clearFromPos(this.pos)},e.getTextString=function(){for(var t=[],e=!0,r=0;r<ua;r++){var i=this.chars[r].uchar;" "!==i&&(e=!1),t.push(i)}return e?"":t.join("")},e.setPenStyles=function(t){this.currPenState.setStyles(t),this.chars[this.pos].setPenState(this.currPenState)},t}(),Ta=function(){function t(t){this.rows=[],this.currRow=14,this.nrRollUpRows=null,this.lastOutputScreen=null,this.logger=void 0;for(var e=0;e<la;e++)this.rows.push(new Ea(t));this.logger=t}var e=t.prototype;return e.reset=function(){for(var t=0;t<la;t++)this.rows[t].clear();this.currRow=14},e.equals=function(t){for(var e=!0,r=0;r<la;r++)if(!this.rows[r].equals(t.rows[r])){e=!1;break}return e},e.copy=function(t){for(var e=0;e<la;e++)this.rows[e].copy(t.rows[e])},e.isEmpty=function(){for(var t=!0,e=0;e<la;e++)if(!this.rows[e].isEmpty()){t=!1;break}return t},e.backSpace=function(){this.rows[this.currRow].backSpace()},e.clearToEndOfRow=function(){this.rows[this.currRow].clearToEndOfRow()},e.insertChar=function(t){this.rows[this.currRow].insertChar(t)},e.setPen=function(t){this.rows[this.currRow].setPenStyles(t)},e.moveCursor=function(t){this.rows[this.currRow].moveCursor(t)},e.setCursor=function(t){this.logger.log(2,"setCursor: "+t),this.rows[this.currRow].setCursor(t)},e.setPAC=function(t){this.logger.log(2,(function(){return"pacData = "+JSON.stringify(t)}));var e=t.row-1;if(this.nrRollUpRows&&e<this.nrRollUpRows-1&&(e=this.nrRollUpRows-1),this.nrRollUpRows&&this.currRow!==e){for(var r=0;r<la;r++)this.rows[r].clear();var i=this.currRow+1-this.nrRollUpRows,n=this.lastOutputScreen;if(n){var a=n.rows[i].cueStartTime,s=this.logger.time;if(null!==a&&null!==s&&a<s)for(var o=0;o<this.nrRollUpRows;o++)this.rows[e-this.nrRollUpRows+o+1].copy(n.rows[i+o])}}this.currRow=e;var l=this.rows[this.currRow];if(null!==t.indent){var u=t.indent,h=Math.max(u-1,0);l.setCursor(t.indent),t.color=l.chars[h].penState.foreground}var d={foreground:t.color,underline:t.underline,italics:t.italics,background:"black",flash:!1};this.setPen(d)},e.setBkgData=function(t){this.logger.log(2,(function(){return"bkgData = "+JSON.stringify(t)})),this.backSpace(),this.setPen(t),this.insertChar(32)},e.setRollUpRows=function(t){this.nrRollUpRows=t},e.rollUp=function(){var t=this;if(null!==this.nrRollUpRows){this.logger.log(1,(function(){return t.getDisplayText()}));var e=this.currRow+1-this.nrRollUpRows,r=this.rows.splice(e,1)[0];r.clear(),this.rows.splice(this.currRow,0,r),this.logger.log(2,"Rolling up")}else this.logger.log(3,"roll_up but nrRollUpRows not set yet")},e.getDisplayText=function(t){t=t||!1;for(var e=[],r="",i=-1,n=0;n<la;n++){var a=this.rows[n].getTextString();a&&(i=n+1,t?e.push("Row "+i+": '"+a+"'"):e.push(a.trim()))}return e.length>0&&(r=t?"["+e.join(" | ")+"]":e.join("\n")),r},e.getTextAndFormat=function(){return this.rows},t}(),Sa=function(){function t(t,e,r){this.chNr=void 0,this.outputFilter=void 0,this.mode=void 0,this.verbose=void 0,this.displayedMemory=void 0,this.nonDisplayedMemory=void 0,this.lastOutputScreen=void 0,this.currRollUpRow=void 0,this.writeScreen=void 0,this.cueStartTime=void 0,this.logger=void 0,this.chNr=t,this.outputFilter=e,this.mode=null,this.verbose=0,this.displayedMemory=new Ta(r),this.nonDisplayedMemory=new Ta(r),this.lastOutputScreen=new Ta(r),this.currRollUpRow=this.displayedMemory.rows[14],this.writeScreen=this.displayedMemory,this.mode=null,this.cueStartTime=null,this.logger=r}var e=t.prototype;return e.reset=function(){this.mode=null,this.displayedMemory.reset(),this.nonDisplayedMemory.reset(),this.lastOutputScreen.reset(),this.outputFilter.reset(),this.currRollUpRow=this.displayedMemory.rows[14],this.writeScreen=this.displayedMemory,this.mode=null,this.cueStartTime=null},e.getHandler=function(){return this.outputFilter},e.setHandler=function(t){this.outputFilter=t},e.setPAC=function(t){this.writeScreen.setPAC(t)},e.setBkgData=function(t){this.writeScreen.setBkgData(t)},e.setMode=function(t){t!==this.mode&&(this.mode=t,this.logger.log(2,(function(){return"MODE="+t})),"MODE_POP-ON"===this.mode?this.writeScreen=this.nonDisplayedMemory:(this.writeScreen=this.displayedMemory,this.writeScreen.reset()),"MODE_ROLL-UP"!==this.mode&&(this.displayedMemory.nrRollUpRows=null,this.nonDisplayedMemory.nrRollUpRows=null),this.mode=t)},e.insertChars=function(t){for(var e=this,r=0;r<t.length;r++)this.writeScreen.insertChar(t[r]);var i=this.writeScreen===this.displayedMemory?"DISP":"NON_DISP";this.logger.log(2,(function(){return i+": "+e.writeScreen.getDisplayText(!0)})),"MODE_PAINT-ON"!==this.mode&&"MODE_ROLL-UP"!==this.mode||(this.logger.log(1,(function(){return"DISPLAYED: "+e.displayedMemory.getDisplayText(!0)})),this.outputDataUpdate())},e.ccRCL=function(){this.logger.log(2,"RCL - Resume Caption Loading"),this.setMode("MODE_POP-ON")},e.ccBS=function(){this.logger.log(2,"BS - BackSpace"),"MODE_TEXT"!==this.mode&&(this.writeScreen.backSpace(),this.writeScreen===this.displayedMemory&&this.outputDataUpdate())},e.ccAOF=function(){},e.ccAON=function(){},e.ccDER=function(){this.logger.log(2,"DER- Delete to End of Row"),this.writeScreen.clearToEndOfRow(),this.outputDataUpdate()},e.ccRU=function(t){this.logger.log(2,"RU("+t+") - Roll Up"),this.writeScreen=this.displayedMemory,this.setMode("MODE_ROLL-UP"),this.writeScreen.setRollUpRows(t)},e.ccFON=function(){this.logger.log(2,"FON - Flash On"),this.writeScreen.setPen({flash:!0})},e.ccRDC=function(){this.logger.log(2,"RDC - Resume Direct Captioning"),this.setMode("MODE_PAINT-ON")},e.ccTR=function(){this.logger.log(2,"TR"),this.setMode("MODE_TEXT")},e.ccRTD=function(){this.logger.log(2,"RTD"),this.setMode("MODE_TEXT")},e.ccEDM=function(){this.logger.log(2,"EDM - Erase Displayed Memory"),this.displayedMemory.reset(),this.outputDataUpdate(!0)},e.ccCR=function(){this.logger.log(2,"CR - Carriage Return"),this.writeScreen.rollUp(),this.outputDataUpdate(!0)},e.ccENM=function(){this.logger.log(2,"ENM - Erase Non-displayed Memory"),this.nonDisplayedMemory.reset()},e.ccEOC=function(){var t=this;if(this.logger.log(2,"EOC - End Of Caption"),"MODE_POP-ON"===this.mode){var e=this.displayedMemory;this.displayedMemory=this.nonDisplayedMemory,this.nonDisplayedMemory=e,this.writeScreen=this.nonDisplayedMemory,this.logger.log(1,(function(){return"DISP: "+t.displayedMemory.getDisplayText()}))}this.outputDataUpdate(!0)},e.ccTO=function(t){this.logger.log(2,"TO("+t+") - Tab Offset"),this.writeScreen.moveCursor(t)},e.ccMIDROW=function(t){var e={flash:!1};if(e.underline=t%2==1,e.italics=t>=46,e.italics)e.foreground="white";else{var r=Math.floor(t/2)-16;e.foreground=["white","green","blue","cyan","red","yellow","magenta"][r]}this.logger.log(2,"MIDROW: "+JSON.stringify(e)),this.writeScreen.setPen(e)},e.outputDataUpdate=function(t){void 0===t&&(t=!1);var e=this.logger.time;null!==e&&this.outputFilter&&(null!==this.cueStartTime||this.displayedMemory.isEmpty()?this.displayedMemory.equals(this.lastOutputScreen)||(this.outputFilter.newCue(this.cueStartTime,e,this.lastOutputScreen),t&&this.outputFilter.dispatchCue&&this.outputFilter.dispatchCue(),this.cueStartTime=this.displayedMemory.isEmpty()?null:e):this.cueStartTime=e,this.lastOutputScreen.copy(this.displayedMemory))},e.cueSplitAtTime=function(t){this.outputFilter&&(this.displayedMemory.isEmpty()||(this.outputFilter.newCue&&this.outputFilter.newCue(this.cueStartTime,t,this.displayedMemory),this.cueStartTime=t))},t}(),La=function(){function t(t,e,r){this.channels=void 0,this.currentChannel=0,this.cmdHistory={a:null,b:null},this.logger=void 0;var i=this.logger=new va;this.channels=[null,new Sa(t,e,i),new Sa(t+1,r,i)]}var e=t.prototype;return e.getHandler=function(t){return this.channels[t].getHandler()},e.setHandler=function(t,e){this.channels[t].setHandler(e)},e.addData=function(t,e){var r=this;this.logger.time=t;for(var i=function(t){var i=127&e[t],n=127&e[t+1],a=!1,s=null;if(0===i&&0===n)return 0;r.logger.log(3,(function(){return"["+ma([e[t],e[t+1]])+"] -> ("+ma([i,n])+")"}));var o=r.cmdHistory;if(i>=16&&i<=31){if(function(t,e,r){return r.a===t&&r.b===e}(i,n,o))return Aa(null,null,o),r.logger.log(3,(function(){return"Repeated command ("+ma([i,n])+") is dropped"})),0;Aa(i,n,r.cmdHistory),(a=r.parseCmd(i,n))||(a=r.parseMidrow(i,n)),a||(a=r.parsePAC(i,n)),a||(a=r.parseBackgroundAttributes(i,n))}else Aa(null,null,o);if(!a&&(s=r.parseChars(i,n))){var l=r.currentChannel;l&&l>0?r.channels[l].insertChars(s):r.logger.log(2,"No channel found yet. TEXT-MODE?")}a||s||r.logger.log(2,(function(){return"Couldn't parse cleaned data "+ma([i,n])+" orig: "+ma([e[t],e[t+1]])}))},n=0;n<e.length;n+=2)i(n)},e.parseCmd=function(t,e){if(!((20===t||28===t||21===t||29===t)&&e>=32&&e<=47||(23===t||31===t)&&e>=33&&e<=35))return!1;var r=20===t||21===t||23===t?1:2,i=this.channels[r];return 20===t||21===t||28===t||29===t?32===e?i.ccRCL():33===e?i.ccBS():34===e?i.ccAOF():35===e?i.ccAON():36===e?i.ccDER():37===e?i.ccRU(2):38===e?i.ccRU(3):39===e?i.ccRU(4):40===e?i.ccFON():41===e?i.ccRDC():42===e?i.ccTR():43===e?i.ccRTD():44===e?i.ccEDM():45===e?i.ccCR():46===e?i.ccENM():47===e&&i.ccEOC():i.ccTO(e-32),this.currentChannel=r,!0},e.parseMidrow=function(t,e){var r=0;if((17===t||25===t)&&e>=32&&e<=47){if((r=17===t?1:2)!==this.currentChannel)return this.logger.log(0,"Mismatch channel in midrow parsing"),!1;var i=this.channels[r];return!!i&&(i.ccMIDROW(e),this.logger.log(3,(function(){return"MIDROW ("+ma([t,e])+")"})),!0)}return!1},e.parsePAC=function(t,e){var r;if(!((t>=17&&t<=23||t>=25&&t<=31)&&e>=64&&e<=127||(16===t||24===t)&&e>=64&&e<=95))return!1;var i=t<=23?1:2;r=e>=64&&e<=95?1===i?ha[t]:ca[t]:1===i?da[t]:fa[t];var n=this.channels[i];return!!n&&(n.setPAC(this.interpretPAC(r,e)),this.currentChannel=i,!0)},e.interpretPAC=function(t,e){var r,i={color:null,italics:!1,indent:null,underline:!1,row:t};return r=e>95?e-96:e-64,i.underline=1==(1&r),r<=13?i.color=["white","green","blue","cyan","red","yellow","magenta","white"][Math.floor(r/2)]:r<=15?(i.italics=!0,i.color="white"):i.indent=4*Math.floor((r-16)/2),i},e.parseChars=function(t,e){var r,i,n=null,a=null;return t>=25?(r=2,a=t-8):(r=1,a=t),a>=17&&a<=19?(i=17===a?e+80:18===a?e+112:e+144,this.logger.log(2,(function(){return"Special char '"+oa(i)+"' in channel "+r})),n=[i]):t>=32&&t<=127&&(n=0===e?[t]:[t,e]),n&&this.logger.log(3,(function(){return"Char codes = "+ma(n).join(",")})),n},e.parseBackgroundAttributes=function(t,e){var r;if(!((16===t||24===t)&&e>=32&&e<=47||(23===t||31===t)&&e>=45&&e<=47))return!1;var i={};16===t||24===t?(r=Math.floor((e-32)/2),i.background=ga[r],e%2==1&&(i.background=i.background+"_semi")):45===e?i.background="transparent":(i.foreground="black",47===e&&(i.underline=!0));var n=t<=23?1:2;return this.channels[n].setBkgData(i),!0},e.reset=function(){for(var t=0;t<Object.keys(this.channels).length;t++){var e=this.channels[t];e&&e.reset()}Aa(null,null,this.cmdHistory)},e.cueSplitAtTime=function(t){for(var e=0;e<this.channels.length;e++){var r=this.channels[e];r&&r.cueSplitAtTime(t)}},t}();function Aa(t,e,r){r.a=t,r.b=e}var Ra=function(){function t(t,e){this.timelineController=void 0,this.cueRanges=[],this.trackName=void 0,this.startTime=null,this.endTime=null,this.screen=null,this.timelineController=t,this.trackName=e}var e=t.prototype;return e.dispatchCue=function(){null!==this.startTime&&(this.timelineController.addCues(this.trackName,this.startTime,this.endTime,this.screen,this.cueRanges),this.startTime=null)},e.newCue=function(t,e,r){(null===this.startTime||this.startTime>t)&&(this.startTime=t),this.endTime=e,this.screen=r,this.timelineController.createCaptionsTrack(this.trackName)},e.reset=function(){this.cueRanges=[],this.startTime=null},t}(),ba=function(){if(null!=j&&j.VTTCue)return self.VTTCue;var t=["","lr","rl"],e=["start","middle","end","left","right"];function r(t,e){if("string"!=typeof e)return!1;if(!Array.isArray(t))return!1;var r=e.toLowerCase();return!!~t.indexOf(r)&&r}function i(t){return r(e,t)}function n(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),i=1;i<e;i++)r[i-1]=arguments[i];for(var n=1;n<arguments.length;n++){var a=arguments[n];for(var s in a)t[s]=a[s]}return t}function a(e,a,s){var o=this,l={enumerable:!0};o.hasBeenReset=!1;var u="",h=!1,d=e,c=a,f=s,g=null,v="",m=!0,p="auto",y="start",E=50,T="middle",S=50,L="middle";Object.defineProperty(o,"id",n({},l,{get:function(){return u},set:function(t){u=""+t}})),Object.defineProperty(o,"pauseOnExit",n({},l,{get:function(){return h},set:function(t){h=!!t}})),Object.defineProperty(o,"startTime",n({},l,{get:function(){return d},set:function(t){if("number"!=typeof t)throw new TypeError("Start time must be set to a number.");d=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"endTime",n({},l,{get:function(){return c},set:function(t){if("number"!=typeof t)throw new TypeError("End time must be set to a number.");c=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"text",n({},l,{get:function(){return f},set:function(t){f=""+t,this.hasBeenReset=!0}})),Object.defineProperty(o,"region",n({},l,{get:function(){return g},set:function(t){g=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"vertical",n({},l,{get:function(){return v},set:function(e){var i=function(e){return r(t,e)}(e);if(!1===i)throw new SyntaxError("An invalid or illegal string was specified.");v=i,this.hasBeenReset=!0}})),Object.defineProperty(o,"snapToLines",n({},l,{get:function(){return m},set:function(t){m=!!t,this.hasBeenReset=!0}})),Object.defineProperty(o,"line",n({},l,{get:function(){return p},set:function(t){if("number"!=typeof t&&"auto"!==t)throw new SyntaxError("An invalid number or illegal string was specified.");p=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"lineAlign",n({},l,{get:function(){return y},set:function(t){var e=i(t);if(!e)throw new SyntaxError("An invalid or illegal string was specified.");y=e,this.hasBeenReset=!0}})),Object.defineProperty(o,"position",n({},l,{get:function(){return E},set:function(t){if(t<0||t>100)throw new Error("Position must be between 0 and 100.");E=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"positionAlign",n({},l,{get:function(){return T},set:function(t){var e=i(t);if(!e)throw new SyntaxError("An invalid or illegal string was specified.");T=e,this.hasBeenReset=!0}})),Object.defineProperty(o,"size",n({},l,{get:function(){return S},set:function(t){if(t<0||t>100)throw new Error("Size must be between 0 and 100.");S=t,this.hasBeenReset=!0}})),Object.defineProperty(o,"align",n({},l,{get:function(){return L},set:function(t){var e=i(t);if(!e)throw new SyntaxError("An invalid or illegal string was specified.");L=e,this.hasBeenReset=!0}})),o.displayState=void 0}return a.prototype.getCueAsHTML=function(){return self.WebVTT.convertCueToDOMTree(self,this.text)},a}(),ka=function(){function t(){}return t.prototype.decode=function(t,e){if(!t)return"";if("string"!=typeof t)throw new Error("Error - expected string data.");return decodeURIComponent(encodeURIComponent(t))},t}();function Da(t){function e(t,e,r,i){return 3600*(0|t)+60*(0|e)+(0|r)+parseFloat(i||0)}var r=t.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/);return r?parseFloat(r[2])>59?e(r[2],r[3],0,r[4]):e(r[1],r[2],r[3],r[4]):null}var Ia=function(){function t(){this.values=Object.create(null)}var e=t.prototype;return e.set=function(t,e){this.get(t)||""===e||(this.values[t]=e)},e.get=function(t,e,r){return r?this.has(t)?this.values[t]:e[r]:this.has(t)?this.values[t]:e},e.has=function(t){return t in this.values},e.alt=function(t,e,r){for(var i=0;i<r.length;++i)if(e===r[i]){this.set(t,e);break}},e.integer=function(t,e){/^-?\d+$/.test(e)&&this.set(t,parseInt(e,10))},e.percent=function(t,e){if(/^([\d]{1,3})(\.[\d]*)?%$/.test(e)){var r=parseFloat(e);if(r>=0&&r<=100)return this.set(t,r),!0}return!1},t}();function wa(t,e,r,i){var n=i?t.split(i):[t];for(var a in n)if("string"==typeof n[a]){var s=n[a].split(r);2===s.length&&e(s[0],s[1])}}var Ca=new ba(0,0,""),_a="middle"===Ca.align?"middle":"center";function xa(t,e,r){var i=t;function n(){var e=Da(t);if(null===e)throw new Error("Malformed timestamp: "+i);return t=t.replace(/^[^\sa-zA-Z-]+/,""),e}function a(){t=t.replace(/^\s+/,"")}if(a(),e.startTime=n(),a(),"--\x3e"!==t.slice(0,3))throw new Error("Malformed time stamp (time stamps must be separated by '--\x3e'): "+i);t=t.slice(3),a(),e.endTime=n(),a(),function(t,e){var i=new Ia;wa(t,(function(t,e){var n;switch(t){case"region":for(var a=r.length-1;a>=0;a--)if(r[a].id===e){i.set(t,r[a].region);break}break;case"vertical":i.alt(t,e,["rl","lr"]);break;case"line":n=e.split(","),i.integer(t,n[0]),i.percent(t,n[0])&&i.set("snapToLines",!1),i.alt(t,n[0],["auto"]),2===n.length&&i.alt("lineAlign",n[1],["start",_a,"end"]);break;case"position":n=e.split(","),i.percent(t,n[0]),2===n.length&&i.alt("positionAlign",n[1],["start",_a,"end","line-left","line-right","auto"]);break;case"size":i.percent(t,e);break;case"align":i.alt(t,e,["start",_a,"end","left","right"])}}),/:/,/\s/),e.region=i.get("region",null),e.vertical=i.get("vertical","");var n=i.get("line","auto");"auto"===n&&-1===Ca.line&&(n=-1),e.line=n,e.lineAlign=i.get("lineAlign","start"),e.snapToLines=i.get("snapToLines",!0),e.size=i.get("size",100),e.align=i.get("align",_a);var a=i.get("position","auto");"auto"===a&&50===Ca.position&&(a="start"===e.align||"left"===e.align?0:"end"===e.align||"right"===e.align?100:50),e.position=a}(t,e)}function Pa(t){return t.replace(/<br(?: \/)?>/gi,"\n")}var Fa=function(){function t(){this.state="INITIAL",this.buffer="",this.decoder=new ka,this.regionList=[],this.cue=null,this.oncue=void 0,this.onparsingerror=void 0,this.onflush=void 0}var e=t.prototype;return e.parse=function(t){var e=this;function r(){var t=e.buffer,r=0;for(t=Pa(t);r<t.length&&"\r"!==t[r]&&"\n"!==t[r];)++r;var i=t.slice(0,r);return"\r"===t[r]&&++r,"\n"===t[r]&&++r,e.buffer=t.slice(r),i}t&&(e.buffer+=e.decoder.decode(t,{stream:!0}));try{var i="";if("INITIAL"===e.state){if(!/\r\n|\n/.test(e.buffer))return this;var n=(i=r()).match(/^()?WEBVTT([ \t].*)?$/);if(null==n||!n[0])throw new Error("Malformed WebVTT signature.");e.state="HEADER"}for(var a=!1;e.buffer;){if(!/\r\n|\n/.test(e.buffer))return this;switch(a?a=!1:i=r(),e.state){case"HEADER":/:/.test(i)?wa(i,(function(t,e){}),/:/):i||(e.state="ID");continue;case"NOTE":i||(e.state="ID");continue;case"ID":if(/^NOTE($|[ \t])/.test(i)){e.state="NOTE";break}if(!i)continue;if(e.cue=new ba(0,0,""),e.state="CUE",-1===i.indexOf("--\x3e")){e.cue.id=i;continue}case"CUE":if(!e.cue){e.state="BADCUE";continue}try{xa(i,e.cue,e.regionList)}catch(t){e.cue=null,e.state="BADCUE";continue}e.state="CUETEXT";continue;case"CUETEXT":var s=-1!==i.indexOf("--\x3e");if(!i||s&&(a=!0)){e.oncue&&e.cue&&e.oncue(e.cue),e.cue=null,e.state="ID";continue}if(null===e.cue)continue;e.cue.text&&(e.cue.text+="\n"),e.cue.text+=i;continue;case"BADCUE":i||(e.state="ID")}}}catch(t){"CUETEXT"===e.state&&e.cue&&e.oncue&&e.oncue(e.cue),e.cue=null,e.state="INITIAL"===e.state?"BADWEBVTT":"BADCUE"}return this},e.flush=function(){var t=this;try{if((t.cue||"HEADER"===t.state)&&(t.buffer+="\n\n",t.parse()),"INITIAL"===t.state||"BADWEBVTT"===t.state)throw new Error("Malformed WebVTT signature.")}catch(e){t.onparsingerror&&t.onparsingerror(e)}return t.onflush&&t.onflush(),this},t}(),Ma=/\r\n|\n\r|\n|\r/g,Oa=function(t,e,r){return void 0===r&&(r=0),t.slice(r,r+e.length)===e},Na=function(t){for(var e=5381,r=t.length;r;)e=33*e^t.charCodeAt(--r);return(e>>>0).toString()};function Ua(t,e,r){return Na(t.toString())+Na(e.toString())+Na(r)}function Ba(t,e,r,i,n,a,s){var o,l,u,h=new Fa,d=Rt(new Uint8Array(t)).trim().replace(Ma,"\n").split("\n"),c=[],f=e?(o=e.baseTime,void 0===(l=e.timescale)&&(l=1),Rn(o,An,1/l)):0,g="00:00.000",v=0,m=0,p=!0;h.oncue=function(t){var a=r[i],s=r.ccOffset,o=(v-f)/9e4;if(null!=a&&a.new&&(void 0!==m?s=r.ccOffset=a.start:function(t,e,r){var i=t[e],n=t[i.prevCC];if(!n||!n.new&&i.new)return t.ccOffset=t.presentationOffset=i.start,void(i.new=!1);for(;null!=(a=n)&&a.new;){var a;t.ccOffset+=i.start-n.start,i.new=!1,n=t[(i=n).prevCC]}t.presentationOffset=r}(r,i,o)),o){if(!e)return void(u=new Error("Missing initPTS for VTT MPEGTS"));s=o-r.presentationOffset}var l=t.endTime-t.startTime,h=wn(9e4*(t.startTime+s-m),9e4*n)/9e4;t.startTime=Math.max(h,0),t.endTime=Math.max(h+l,0);var d=t.text.trim();t.text=decodeURIComponent(encodeURIComponent(d)),t.id||(t.id=Ua(t.startTime,t.endTime,d)),t.endTime>0&&c.push(t)},h.onparsingerror=function(t){u=t},h.onflush=function(){u?s(u):a(c)},d.forEach((function(t){if(p){if(Oa(t,"X-TIMESTAMP-MAP=")){p=!1,t.slice(16).split(",").forEach((function(t){Oa(t,"LOCAL:")?g=t.slice(6):Oa(t,"MPEGTS:")&&(v=parseInt(t.slice(7)))}));try{m=function(t){var e=parseInt(t.slice(-3)),r=parseInt(t.slice(-6,-4)),i=parseInt(t.slice(-9,-7)),n=t.length>9?parseInt(t.substring(0,t.indexOf(":"))):0;if(!(y(e)&&y(r)&&y(i)&&y(n)))throw Error("Malformed X-TIMESTAMP-MAP: Local:"+t);return e+=1e3*r,(e+=6e4*i)+36e5*n}(g)/1e3}catch(t){u=t}return}""===t&&(p=!1)}h.parse(t+"\n")})),h.flush()}var Ga="stpp.ttml.im1t",Ka=/^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/,Ha=/^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/,Va={left:"start",center:"center",right:"end",start:"start",end:"end"};function Ya(t,e,r,i){var n=Ot(new Uint8Array(t),["mdat"]);if(0!==n.length){var a,s,l,u,h=n.map((function(t){return Rt(t)})),d=(a=e.baseTime,s=1,void 0===(l=e.timescale)&&(l=1),void 0===u&&(u=!1),Rn(a,s,1/l,u));try{h.forEach((function(t){return r(function(t,e){var r=(new DOMParser).parseFromString(t,"text/xml"),i=r.getElementsByTagName("tt")[0];if(!i)throw new Error("Invalid ttml");var n={frameRate:30,subFrameRate:1,frameRateMultiplier:0,tickRate:0},a=Object.keys(n).reduce((function(t,e){return t[e]=i.getAttribute("ttp:"+e)||n[e],t}),{}),s="preserve"!==i.getAttribute("xml:space"),l=ja(Wa(i,"styling","style")),u=ja(Wa(i,"layout","region")),h=Wa(i,"body","[begin]");return[].map.call(h,(function(t){var r=qa(t,s);if(!r||!t.hasAttribute("begin"))return null;var i=Qa(t.getAttribute("begin"),a),n=Qa(t.getAttribute("dur"),a),h=Qa(t.getAttribute("end"),a);if(null===i)throw za(t);if(null===h){if(null===n)throw za(t);h=i+n}var d=new ba(i-e,h-e,r);d.id=Ua(d.startTime,d.endTime,d.text);var c=function(t,e,r){var i="http://www.w3.org/ns/ttml#styling",n=null,a=["displayAlign","textAlign","color","backgroundColor","fontSize","fontFamily"],s=null!=t&&t.hasAttribute("style")?t.getAttribute("style"):null;return s&&r.hasOwnProperty(s)&&(n=r[s]),a.reduce((function(r,a){var s=Xa(e,i,a)||Xa(t,i,a)||Xa(n,i,a);return s&&(r[a]=s),r}),{})}(u[t.getAttribute("region")],l[t.getAttribute("style")],l),f=c.textAlign;if(f){var g=Va[f];g&&(d.lineAlign=g),d.align=f}return o(d,c),d})).filter((function(t){return null!==t}))}(t,d))}))}catch(t){i(t)}}else i(new Error("Could not parse IMSC1 mdat"))}function Wa(t,e,r){var i=t.getElementsByTagName(e)[0];return i?[].slice.call(i.querySelectorAll(r)):[]}function ja(t){return t.reduce((function(t,e){var r=e.getAttribute("xml:id");return r&&(t[r]=e),t}),{})}function qa(t,e){return[].slice.call(t.childNodes).reduce((function(t,r,i){var n;return"br"===r.nodeName&&i?t+"\n":null!=(n=r.childNodes)&&n.length?qa(r,e):e?t+r.textContent.trim().replace(/\s+/g," "):t+r.textContent}),"")}function Xa(t,e,r){return t&&t.hasAttributeNS(e,r)?t.getAttributeNS(e,r):null}function za(t){return new Error("Could not parse ttml timestamp "+t)}function Qa(t,e){if(!t)return null;var r=Da(t);return null===r&&(Ka.test(t)?r=function(t,e){var r=Ka.exec(t),i=(0|r[4])+(0|r[5])/e.subFrameRate;return 3600*(0|r[1])+60*(0|r[2])+(0|r[3])+i/e.frameRate}(t,e):Ha.test(t)&&(r=function(t,e){var r=Ha.exec(t),i=Number(r[1]);switch(r[2]){case"h":return 3600*i;case"m":return 60*i;case"ms":return 1e3*i;case"f":return i/e.frameRate;case"t":return i/e.tickRate}return i}(t,e))),r}var Ja=function(){function t(t){this.hls=void 0,this.media=null,this.config=void 0,this.enabled=!0,this.Cues=void 0,this.textTracks=[],this.tracks=[],this.initPTS=[],this.unparsedVttFrags=[],this.captionsTracks={},this.nonNativeCaptionsTracks={},this.cea608Parser1=void 0,this.cea608Parser2=void 0,this.lastCc=-1,this.lastSn=-1,this.lastPartIndex=-1,this.prevCC=-1,this.vttCCs={ccOffset:0,presentationOffset:0,0:{start:0,prevCC:-1,new:!0}},this.captionsProperties=void 0,this.hls=t,this.config=t.config,this.Cues=t.config.cueHandler,this.captionsProperties={textTrack1:{label:this.config.captionsTextTrack1Label,languageCode:this.config.captionsTextTrack1LanguageCode},textTrack2:{label:this.config.captionsTextTrack2Label,languageCode:this.config.captionsTextTrack2LanguageCode},textTrack3:{label:this.config.captionsTextTrack3Label,languageCode:this.config.captionsTextTrack3LanguageCode},textTrack4:{label:this.config.captionsTextTrack4Label,languageCode:this.config.captionsTextTrack4LanguageCode}},t.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.on(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.on(S.FRAG_LOADING,this.onFragLoading,this),t.on(S.FRAG_LOADED,this.onFragLoaded,this),t.on(S.FRAG_PARSING_USERDATA,this.onFragParsingUserdata,this),t.on(S.FRAG_DECRYPTED,this.onFragDecrypted,this),t.on(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.on(S.SUBTITLE_TRACKS_CLEARED,this.onSubtitleTracksCleared,this),t.on(S.BUFFER_FLUSHING,this.onBufferFlushing,this)}var e=t.prototype;return e.destroy=function(){var t=this.hls;t.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.off(S.SUBTITLE_TRACKS_UPDATED,this.onSubtitleTracksUpdated,this),t.off(S.FRAG_LOADING,this.onFragLoading,this),t.off(S.FRAG_LOADED,this.onFragLoaded,this),t.off(S.FRAG_PARSING_USERDATA,this.onFragParsingUserdata,this),t.off(S.FRAG_DECRYPTED,this.onFragDecrypted,this),t.off(S.INIT_PTS_FOUND,this.onInitPtsFound,this),t.off(S.SUBTITLE_TRACKS_CLEARED,this.onSubtitleTracksCleared,this),t.off(S.BUFFER_FLUSHING,this.onBufferFlushing,this),this.hls=this.config=null,this.cea608Parser1=this.cea608Parser2=void 0},e.initCea608Parsers=function(){if(this.config.enableCEA708Captions&&(!this.cea608Parser1||!this.cea608Parser2)){var t=new Ra(this,"textTrack1"),e=new Ra(this,"textTrack2"),r=new Ra(this,"textTrack3"),i=new Ra(this,"textTrack4");this.cea608Parser1=new La(1,t,e),this.cea608Parser2=new La(3,r,i)}},e.addCues=function(t,e,r,i,n){for(var a,s,o,l,u=!1,h=n.length;h--;){var d=n[h],c=(a=d[0],s=d[1],o=e,l=r,Math.min(s,l)-Math.max(a,o));if(c>=0&&(d[0]=Math.min(d[0],e),d[1]=Math.max(d[1],r),u=!0,c/(r-e)>.5))return}if(u||n.push([e,r]),this.config.renderTextTracksNatively){var f=this.captionsTracks[t];this.Cues.newCue(f,e,r,i)}else{var g=this.Cues.newCue(null,e,r,i);this.hls.trigger(S.CUES_PARSED,{type:"captions",cues:g,track:t})}},e.onInitPtsFound=function(t,e){var r=this,i=e.frag,n=e.id,a=e.initPTS,s=e.timescale,o=this.unparsedVttFrags;"main"===n&&(this.initPTS[i.cc]={baseTime:a,timescale:s}),o.length&&(this.unparsedVttFrags=[],o.forEach((function(t){r.onFragLoaded(S.FRAG_LOADED,t)})))},e.getExistingTrack=function(t,e){var r=this.media;if(r)for(var i=0;i<r.textTracks.length;i++){var n=r.textTracks[i];if(Za(n,{name:t,lang:e,attrs:{}}))return n}return null},e.createCaptionsTrack=function(t){this.config.renderTextTracksNatively?this.createNativeTrack(t):this.createNonNativeTrack(t)},e.createNativeTrack=function(t){if(!this.captionsTracks[t]){var e=this.captionsProperties,r=this.captionsTracks,i=this.media,n=e[t],a=n.label,s=n.languageCode,o=this.getExistingTrack(a,s);if(o)r[t]=o,He(r[t]),Ge(r[t],i);else{var l=this.createTextTrack("captions",a,s);l&&(l[t]=!0,r[t]=l)}}},e.createNonNativeTrack=function(t){if(!this.nonNativeCaptionsTracks[t]){var e=this.captionsProperties[t];if(e){var r={_id:t,label:e.label,kind:"captions",default:!!e.media&&!!e.media.default,closedCaptions:e.media};this.nonNativeCaptionsTracks[t]=r,this.hls.trigger(S.NON_NATIVE_TEXT_TRACKS_FOUND,{tracks:[r]})}}},e.createTextTrack=function(t,e,r){var i=this.media;if(i)return i.addTextTrack(t,e,r)},e.onMediaAttaching=function(t,e){this.media=e.media,this._cleanTracks()},e.onMediaDetaching=function(){var t=this.captionsTracks;Object.keys(t).forEach((function(e){He(t[e]),delete t[e]})),this.nonNativeCaptionsTracks={}},e.onManifestLoading=function(){this.lastCc=-1,this.lastSn=-1,this.lastPartIndex=-1,this.prevCC=-1,this.vttCCs={ccOffset:0,presentationOffset:0,0:{start:0,prevCC:-1,new:!0}},this._cleanTracks(),this.tracks=[],this.captionsTracks={},this.nonNativeCaptionsTracks={},this.textTracks=[],this.unparsedVttFrags=[],this.initPTS=[],this.cea608Parser1&&this.cea608Parser2&&(this.cea608Parser1.reset(),this.cea608Parser2.reset())},e._cleanTracks=function(){var t=this.media;if(t){var e=t.textTracks;if(e)for(var r=0;r<e.length;r++)He(e[r])}},e.onSubtitleTracksUpdated=function(t,e){var r=this,i=e.subtitleTracks||[],n=i.some((function(t){return t.textCodec===Ga}));if(this.config.enableWebVTT||n&&this.config.enableIMSC1){if(Xn(this.tracks,i))return void(this.tracks=i);if(this.textTracks=[],this.tracks=i,this.config.renderTextTracksNatively){var a=this.media,s=a?Ye(a.textTracks):null;if(this.tracks.forEach((function(t,e){var i;if(s){for(var n=null,a=0;a<s.length;a++)if(s[a]&&Za(s[a],t)){n=s[a],s[a]=null;break}n&&(i=n)}if(i)He(i);else{var o=$a(t);(i=r.createTextTrack(o,t.name,t.lang))&&(i.mode="disabled")}i&&r.textTracks.push(i)})),null!=s&&s.length){var o=s.filter((function(t){return null!==t})).map((function(t){return t.label}));o.length&&w.warn("Media element contains unused subtitle tracks: "+o.join(", ")+". Replace media element for each source to clear TextTracks and captions menu.")}}else if(this.tracks.length){var l=this.tracks.map((function(t){return{label:t.name,kind:t.type.toLowerCase(),default:t.default,subtitleTrack:t}}));this.hls.trigger(S.NON_NATIVE_TEXT_TRACKS_FOUND,{tracks:l})}}},e.onManifestLoaded=function(t,e){var r=this;this.config.enableCEA708Captions&&e.captions&&e.captions.forEach((function(t){var e=/(?:CC|SERVICE)([1-4])/.exec(t.instreamId);if(e){var i="textTrack"+e[1],n=r.captionsProperties[i];n&&(n.label=t.name,t.lang&&(n.languageCode=t.lang),n.media=t)}}))},e.closedCaptionsForLevel=function(t){var e=this.hls.levels[t.level];return null==e?void 0:e.attrs["CLOSED-CAPTIONS"]},e.onFragLoading=function(t,e){if(this.enabled&&e.frag.type===Fe){var r,i,n=this.cea608Parser1,a=this.cea608Parser2,s=this.lastSn,o=e.frag,l=o.cc,u=o.sn,h=null!=(r=null==(i=e.part)?void 0:i.index)?r:-1;n&&a&&(u!==s+1||u===s&&h!==this.lastPartIndex+1||l!==this.lastCc)&&(n.reset(),a.reset()),this.lastCc=l,this.lastSn=u,this.lastPartIndex=h}},e.onFragLoaded=function(t,e){var r=e.frag,i=e.payload;if(r.type===Oe)if(i.byteLength){var n=r.decryptdata,a="stats"in e;if(null==n||!n.encrypted||a){var s=this.tracks[r.level],o=this.vttCCs;o[r.cc]||(o[r.cc]={start:r.start,prevCC:this.prevCC,new:!0},this.prevCC=r.cc),s&&s.textCodec===Ga?this._parseIMSC1(r,i):this._parseVTTs(e)}}else this.hls.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!1,frag:r,error:new Error("Empty subtitle payload")})},e._parseIMSC1=function(t,e){var r=this,i=this.hls;Ya(e,this.initPTS[t.cc],(function(e){r._appendCues(e,t.level),i.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!0,frag:t})}),(function(e){w.log("Failed to parse IMSC1: "+e),i.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!1,frag:t,error:e})}))},e._parseVTTs=function(t){var e,r=this,i=t.frag,n=t.payload,a=this.initPTS,s=this.unparsedVttFrags,o=a.length-1;if(a[i.cc]||-1!==o){var l=this.hls;Ba(null!=(e=i.initSegment)&&e.data?Wt(i.initSegment.data,new Uint8Array(n)):n,this.initPTS[i.cc],this.vttCCs,i.cc,i.start,(function(t){r._appendCues(t,i.level),l.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!0,frag:i})}),(function(e){var a="Missing initPTS for VTT MPEGTS"===e.message;a?s.push(t):r._fallbackToIMSC1(i,n),w.log("Failed to parse VTT cue: "+e),a&&o>i.cc||l.trigger(S.SUBTITLE_FRAG_PROCESSED,{success:!1,frag:i,error:e})}))}else s.push(t)},e._fallbackToIMSC1=function(t,e){var r=this,i=this.tracks[t.level];i.textCodec||Ya(e,this.initPTS[t.cc],(function(){i.textCodec=Ga,r._parseIMSC1(t,e)}),(function(){i.textCodec="wvtt"}))},e._appendCues=function(t,e){var r=this.hls;if(this.config.renderTextTracksNatively){var i=this.textTracks[e];if(!i||"disabled"===i.mode)return;t.forEach((function(t){return Ke(i,t)}))}else{var n=this.tracks[e];if(!n)return;var a=n.default?"default":"subtitles"+e;r.trigger(S.CUES_PARSED,{type:"subtitles",cues:t,track:a})}},e.onFragDecrypted=function(t,e){e.frag.type===Oe&&this.onFragLoaded(S.FRAG_LOADED,e)},e.onSubtitleTracksCleared=function(){this.tracks=[],this.captionsTracks={}},e.onFragParsingUserdata=function(t,e){this.initCea608Parsers();var r=this.cea608Parser1,i=this.cea608Parser2;if(this.enabled&&r&&i){var n=e.frag,a=e.samples;if(n.type!==Fe||"NONE"!==this.closedCaptionsForLevel(n))for(var s=0;s<a.length;s++){var o=a[s].bytes;if(o){var l=this.extractCea608Data(o);r.addData(a[s].pts,l[0]),i.addData(a[s].pts,l[1])}}}},e.onBufferFlushing=function(t,e){var r=e.startOffset,i=e.endOffset,n=e.endOffsetSubtitles,a=e.type,s=this.media;if(s&&!(s.currentTime<i)){if(!a||"video"===a){var o=this.captionsTracks;Object.keys(o).forEach((function(t){return Ve(o[t],r,i)}))}if(this.config.renderTextTracksNatively&&0===r&&void 0!==n){var l=this.textTracks;Object.keys(l).forEach((function(t){return Ve(l[t],r,n)}))}}},e.extractCea608Data=function(t){for(var e=[[],[]],r=31&t[0],i=2,n=0;n<r;n++){var a=t[i++],s=127&t[i++],o=127&t[i++];if((0!==s||0!==o)&&0!=(4&a)){var l=3&a;0!==l&&1!==l||(e[l].push(s),e[l].push(o))}}return e},t}();function $a(t){return t.characteristics&&/transcribes-spoken-dialog/gi.test(t.characteristics)&&/describes-music-and-sound/gi.test(t.characteristics)?"captions":"subtitles"}function Za(t,e){return!!t&&t.kind===$a(e)&&Qn(e,t)}var ts=function(){function t(t){this.hls=void 0,this.autoLevelCapping=void 0,this.firstLevel=void 0,this.media=void 0,this.restrictedLevels=void 0,this.timer=void 0,this.clientRect=void 0,this.streamController=void 0,this.hls=t,this.autoLevelCapping=Number.POSITIVE_INFINITY,this.firstLevel=-1,this.media=null,this.restrictedLevels=[],this.timer=void 0,this.clientRect=null,this.registerListeners()}var e=t.prototype;return e.setStreamController=function(t){this.streamController=t},e.destroy=function(){this.hls&&this.unregisterListener(),this.timer&&this.stopCapping(),this.media=null,this.clientRect=null,this.hls=this.streamController=null},e.registerListeners=function(){var t=this.hls;t.on(S.FPS_DROP_LEVEL_CAPPING,this.onFpsDropLevelCapping,this),t.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.BUFFER_CODECS,this.onBufferCodecs,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this)},e.unregisterListener=function(){var t=this.hls;t.off(S.FPS_DROP_LEVEL_CAPPING,this.onFpsDropLevelCapping,this),t.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.BUFFER_CODECS,this.onBufferCodecs,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this)},e.onFpsDropLevelCapping=function(t,e){var r=this.hls.levels[e.droppedLevel];this.isLevelAllowed(r)&&this.restrictedLevels.push({bitrate:r.bitrate,height:r.height,width:r.width})},e.onMediaAttaching=function(t,e){this.media=e.media instanceof HTMLVideoElement?e.media:null,this.clientRect=null,this.timer&&this.hls.levels.length&&this.detectPlayerSize()},e.onManifestParsed=function(t,e){var r=this.hls;this.restrictedLevels=[],this.firstLevel=e.firstLevel,r.config.capLevelToPlayerSize&&e.video&&this.startCapping()},e.onLevelsUpdated=function(t,e){this.timer&&y(this.autoLevelCapping)&&this.detectPlayerSize()},e.onBufferCodecs=function(t,e){this.hls.config.capLevelToPlayerSize&&e.video&&this.startCapping()},e.onMediaDetaching=function(){this.stopCapping()},e.detectPlayerSize=function(){if(this.media){if(this.mediaHeight<=0||this.mediaWidth<=0)return void(this.clientRect=null);var t=this.hls.levels;if(t.length){var e=this.hls,r=this.getMaxLevel(t.length-1);r!==this.autoLevelCapping&&w.log("Setting autoLevelCapping to "+r+": "+t[r].height+"p@"+t[r].bitrate+" for media "+this.mediaWidth+"x"+this.mediaHeight),e.autoLevelCapping=r,e.autoLevelCapping>this.autoLevelCapping&&this.streamController&&this.streamController.nextLevelSwitch(),this.autoLevelCapping=e.autoLevelCapping}}},e.getMaxLevel=function(e){var r=this,i=this.hls.levels;if(!i.length)return-1;var n=i.filter((function(t,i){return r.isLevelAllowed(t)&&i<=e}));return this.clientRect=null,t.getMaxLevelByMediaSize(n,this.mediaWidth,this.mediaHeight)},e.startCapping=function(){this.timer||(this.autoLevelCapping=Number.POSITIVE_INFINITY,self.clearInterval(this.timer),this.timer=self.setInterval(this.detectPlayerSize.bind(this),1e3),this.detectPlayerSize())},e.stopCapping=function(){this.restrictedLevels=[],this.firstLevel=-1,this.autoLevelCapping=Number.POSITIVE_INFINITY,this.timer&&(self.clearInterval(this.timer),this.timer=void 0)},e.getDimensions=function(){if(this.clientRect)return this.clientRect;var t=this.media,e={width:0,height:0};if(t){var r=t.getBoundingClientRect();e.width=r.width,e.height=r.height,e.width||e.height||(e.width=r.right-r.left||t.width||0,e.height=r.bottom-r.top||t.height||0)}return this.clientRect=e,e},e.isLevelAllowed=function(t){return!this.restrictedLevels.some((function(e){return t.bitrate===e.bitrate&&t.width===e.width&&t.height===e.height}))},t.getMaxLevelByMediaSize=function(t,e,r){if(null==t||!t.length)return-1;for(var i,n,a=t.length-1,s=Math.max(e,r),o=0;o<t.length;o+=1){var l=t[o];if((l.width>=s||l.height>=s)&&(i=l,!(n=t[o+1])||i.width!==n.width||i.height!==n.height)){a=o;break}}return a},s(t,[{key:"mediaWidth",get:function(){return this.getDimensions().width*this.contentScaleFactor}},{key:"mediaHeight",get:function(){return this.getDimensions().height*this.contentScaleFactor}},{key:"contentScaleFactor",get:function(){var t=1;if(!this.hls.config.ignoreDevicePixelRatio)try{t=self.devicePixelRatio}catch(t){}return t}}]),t}(),es=function(){function t(t){this.hls=void 0,this.isVideoPlaybackQualityAvailable=!1,this.timer=void 0,this.media=null,this.lastTime=void 0,this.lastDroppedFrames=0,this.lastDecodedFrames=0,this.streamController=void 0,this.hls=t,this.registerListeners()}var e=t.prototype;return e.setStreamController=function(t){this.streamController=t},e.registerListeners=function(){this.hls.on(S.MEDIA_ATTACHING,this.onMediaAttaching,this)},e.unregisterListeners=function(){this.hls.off(S.MEDIA_ATTACHING,this.onMediaAttaching,this)},e.destroy=function(){this.timer&&clearInterval(this.timer),this.unregisterListeners(),this.isVideoPlaybackQualityAvailable=!1,this.media=null},e.onMediaAttaching=function(t,e){var r=this.hls.config;if(r.capLevelOnFPSDrop){var i=e.media instanceof self.HTMLVideoElement?e.media:null;this.media=i,i&&"function"==typeof i.getVideoPlaybackQuality&&(this.isVideoPlaybackQualityAvailable=!0),self.clearInterval(this.timer),this.timer=self.setInterval(this.checkFPSInterval.bind(this),r.fpsDroppedMonitoringPeriod)}},e.checkFPS=function(t,e,r){var i=performance.now();if(e){if(this.lastTime){var n=i-this.lastTime,a=r-this.lastDroppedFrames,s=e-this.lastDecodedFrames,o=1e3*a/n,l=this.hls;if(l.trigger(S.FPS_DROP,{currentDropped:a,currentDecoded:s,totalDroppedFrames:r}),o>0&&a>l.config.fpsDroppedMonitoringThreshold*s){var u=l.currentLevel;w.warn("drop FPS ratio greater than max allowed value for currentLevel: "+u),u>0&&(-1===l.autoLevelCapping||l.autoLevelCapping>=u)&&(u-=1,l.trigger(S.FPS_DROP_LEVEL_CAPPING,{level:u,droppedLevel:l.currentLevel}),l.autoLevelCapping=u,this.streamController.nextLevelSwitch())}}this.lastTime=i,this.lastDroppedFrames=r,this.lastDecodedFrames=e}},e.checkFPSInterval=function(){var t=this.media;if(t)if(this.isVideoPlaybackQualityAvailable){var e=t.getVideoPlaybackQuality();this.checkFPS(t,e.totalVideoFrames,e.droppedVideoFrames)}else this.checkFPS(t,t.webkitDecodedFrameCount,t.webkitDroppedFrameCount)},t}(),rs="[eme]",is=function(){function t(e){this.hls=void 0,this.config=void 0,this.media=null,this.keyFormatPromise=null,this.keySystemAccessPromises={},this._requestLicenseFailureCount=0,this.mediaKeySessions=[],this.keyIdToKeySessionPromise={},this.setMediaKeysQueue=t.CDMCleanupPromise?[t.CDMCleanupPromise]:[],this.onMediaEncrypted=this._onMediaEncrypted.bind(this),this.onWaitingForKey=this._onWaitingForKey.bind(this),this.debug=w.debug.bind(w,rs),this.log=w.log.bind(w,rs),this.warn=w.warn.bind(w,rs),this.error=w.error.bind(w,rs),this.hls=e,this.config=e.config,this.registerListeners()}var e=t.prototype;return e.destroy=function(){this.unregisterListeners(),this.onMediaDetached();var t=this.config;t.requestMediaKeySystemAccessFunc=null,t.licenseXhrSetup=t.licenseResponseCallback=void 0,t.drmSystems=t.drmSystemOptions={},this.hls=this.onMediaEncrypted=this.onWaitingForKey=this.keyIdToKeySessionPromise=null,this.config=null},e.registerListeners=function(){this.hls.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.on(S.MEDIA_DETACHED,this.onMediaDetached,this),this.hls.on(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.on(S.MANIFEST_LOADED,this.onManifestLoaded,this)},e.unregisterListeners=function(){this.hls.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),this.hls.off(S.MEDIA_DETACHED,this.onMediaDetached,this),this.hls.off(S.MANIFEST_LOADING,this.onManifestLoading,this),this.hls.off(S.MANIFEST_LOADED,this.onManifestLoaded,this)},e.getLicenseServerUrl=function(t){var e=this.config,r=e.drmSystems,i=e.widevineLicenseUrl,n=r[t];if(n)return n.licenseUrl;if(t===q.WIDEVINE&&i)return i;throw new Error('no license server URL configured for key-system "'+t+'"')},e.getServerCertificateUrl=function(t){var e=this.config.drmSystems[t];if(e)return e.serverCertificateUrl;this.log('No Server Certificate in config.drmSystems["'+t+'"]')},e.attemptKeySystemAccess=function(t){var e=this,r=this.hls.levels,i=function(t,e,r){return!!t&&r.indexOf(t)===e},n=r.map((function(t){return t.audioCodec})).filter(i),a=r.map((function(t){return t.videoCodec})).filter(i);return n.length+a.length===0&&a.push("avc1.42e01e"),new Promise((function(r,i){!function t(s){var o=s.shift();e.getMediaKeysPromise(o,n,a).then((function(t){return r({keySystem:o,mediaKeys:t})})).catch((function(e){s.length?t(s):i(e instanceof ls?e:new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_ACCESS,error:e,fatal:!0},e.message))}))}(t)}))},e.requestMediaKeySystemAccess=function(t,e){var r=this.config.requestMediaKeySystemAccessFunc;if("function"!=typeof r){var i="Configured requestMediaKeySystemAccess is not a function "+r;return null===ot&&"http:"===self.location.protocol&&(i="navigator.requestMediaKeySystemAccess is not available over insecure protocol "+location.protocol),Promise.reject(new Error(i))}return r(t,e)},e.getMediaKeysPromise=function(t,e,r){var i=this,n=function(t,e,r,i){var n;switch(t){case q.FAIRPLAY:n=["cenc","sinf"];break;case q.WIDEVINE:case q.PLAYREADY:n=["cenc"];break;case q.CLEARKEY:n=["cenc","keyids"];break;default:throw new Error("Unknown key-system: "+t)}return function(t,e,r,i){return[{initDataTypes:t,persistentState:i.persistentState||"optional",distinctiveIdentifier:i.distinctiveIdentifier||"optional",sessionTypes:i.sessionTypes||[i.sessionType||"temporary"],audioCapabilities:e.map((function(t){return{contentType:'audio/mp4; codecs="'+t+'"',robustness:i.audioRobustness||"",encryptionScheme:i.audioEncryptionScheme||null}})),videoCapabilities:r.map((function(t){return{contentType:'video/mp4; codecs="'+t+'"',robustness:i.videoRobustness||"",encryptionScheme:i.videoEncryptionScheme||null}}))}]}(n,e,r,i)}(t,e,r,this.config.drmSystemOptions),a=this.keySystemAccessPromises[t],s=null==a?void 0:a.keySystemAccess;if(!s){this.log('Requesting encrypted media "'+t+'" key-system access with config: '+JSON.stringify(n)),s=this.requestMediaKeySystemAccess(t,n);var o=this.keySystemAccessPromises[t]={keySystemAccess:s};return s.catch((function(e){i.log('Failed to obtain access to key-system "'+t+'": '+e)})),s.then((function(e){i.log('Access for key-system "'+e.keySystem+'" obtained');var r=i.fetchServerCertificate(t);return i.log('Create media-keys for "'+t+'"'),o.mediaKeys=e.createMediaKeys().then((function(e){return i.log('Media-keys created for "'+t+'"'),r.then((function(r){return r?i.setMediaKeysServerCertificate(e,t,r):e}))})),o.mediaKeys.catch((function(e){i.error('Failed to create media-keys for "'+t+'"}: '+e)})),o.mediaKeys}))}return s.then((function(){return a.mediaKeys}))},e.createMediaKeySessionContext=function(t){var e=t.decryptdata,r=t.keySystem,i=t.mediaKeys;this.log('Creating key-system session "'+r+'" keyId: '+kt.hexDump(e.keyId||[]));var n=i.createSession(),a={decryptdata:e,keySystem:r,mediaKeys:i,mediaKeysSession:n,keyStatus:"status-pending"};return this.mediaKeySessions.push(a),a},e.renewKeySession=function(t){var e=t.decryptdata;if(e.pssh){var r=this.createMediaKeySessionContext(t),i=this.getKeyIdString(e);this.keyIdToKeySessionPromise[i]=this.generateRequestWithPreferredKeySession(r,"cenc",e.pssh,"expired")}else this.warn("Could not renew expired session. Missing pssh initData.");this.removeSession(t)},e.getKeyIdString=function(t){if(!t)throw new Error("Could not read keyId of undefined decryptdata");if(null===t.keyId)throw new Error("keyId is null");return kt.hexDump(t.keyId)},e.updateKeySession=function(t,e){var r,i=t.mediaKeysSession;return this.log('Updating key-session "'+i.sessionId+'" for keyID '+kt.hexDump((null==(r=t.decryptdata)?void 0:r.keyId)||[])+"\n } (data length: "+(e?e.byteLength:e)+")"),i.update(e)},e.selectKeySystemFormat=function(t){var e=Object.keys(t.levelkeys||{});return this.keyFormatPromise||(this.log("Selecting key-system from fragment (sn: "+t.sn+" "+t.type+": "+t.level+") key formats "+e.join(", ")),this.keyFormatPromise=this.getKeyFormatPromise(e)),this.keyFormatPromise},e.getKeyFormatPromise=function(t){var e=this;return new Promise((function(r,i){var n=at(e.config),a=t.map($).filter((function(t){return!!t&&-1!==n.indexOf(t)}));return e.getKeySystemSelectionPromise(a).then((function(t){var e=t.keySystem,n=nt(e);n?r(n):i(new Error('Unable to find format for key-system "'+e+'"'))})).catch(i)}))},e.loadKey=function(t){var e=this,r=t.keyInfo.decryptdata,i=this.getKeyIdString(r),n="(keyId: "+i+' format: "'+r.keyFormat+'" method: '+r.method+" uri: "+r.uri+")";this.log("Starting session for key "+n);var a=this.keyIdToKeySessionPromise[i];return a||(a=this.keyIdToKeySessionPromise[i]=this.getKeySystemForKeyPromise(r).then((function(i){var a=i.keySystem,s=i.mediaKeys;return e.throwIfDestroyed(),e.log("Handle encrypted media sn: "+t.frag.sn+" "+t.frag.type+": "+t.frag.level+" using key "+n),e.attemptSetMediaKeys(a,s).then((function(){e.throwIfDestroyed();var t=e.createMediaKeySessionContext({keySystem:a,mediaKeys:s,decryptdata:r});return e.generateRequestWithPreferredKeySession(t,"cenc",r.pssh,"playlist-key")}))}))).catch((function(t){return e.handleError(t)})),a},e.throwIfDestroyed=function(t){if(!this.hls)throw new Error("invalid state")},e.handleError=function(t){this.hls&&(this.error(t.message),t instanceof ls?this.hls.trigger(S.ERROR,t.data):this.hls.trigger(S.ERROR,{type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_KEYS,error:t,fatal:!0}))},e.getKeySystemForKeyPromise=function(t){var e=this.getKeyIdString(t),r=this.keyIdToKeySessionPromise[e];if(!r){var i=$(t.keyFormat),n=i?[i]:at(this.config);return this.attemptKeySystemAccess(n)}return r},e.getKeySystemSelectionPromise=function(t){if(t.length||(t=at(this.config)),0===t.length)throw new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_CONFIGURED_LICENSE,fatal:!0},"Missing key-system license configuration options "+JSON.stringify({drmSystems:this.config.drmSystems}));return this.attemptKeySystemAccess(t)},e._onMediaEncrypted=function(t){var e=this,r=t.initDataType,i=t.initData,n='"'+t.type+'" event: init data type: "'+r+'"';if(this.debug(n),null!==i){var a,s;if("sinf"===r&&this.config.drmSystems[q.FAIRPLAY]){var o=Ct(new Uint8Array(i));try{var l=V(JSON.parse(o).sinf),u=Vt(new Uint8Array(l));if(!u)throw new Error("'schm' box missing or not cbcs/cenc with schi > tenc");a=u.subarray(8,24),s=q.FAIRPLAY}catch(t){return void this.warn(n+" Failed to parse sinf: "+t)}}else{var h=function(t){var e=[];if(t instanceof ArrayBuffer)for(var r=t.byteLength,i=0;i+32<r;){var n=Jt(new DataView(t,i));e.push(n),i+=n.size}return e}(i),d=h.filter((function(t){return t.systemId===rt}))[0];if(!d)return void(0===h.length||h.some((function(t){return!t.systemId}))?this.warn(n+" contains incomplete or invalid pssh data"):this.log("ignoring "+n+" for "+h.map((function(t){return it(t.systemId)})).join(",")+" pssh data in favor of playlist keys"));if(s=it(d.systemId),0===d.version&&d.data){var c=d.data.length-22;a=d.data.subarray(c,c+16)}}if(s&&a){for(var f,g=kt.hexDump(a),v=this.keyIdToKeySessionPromise,m=this.mediaKeySessions,p=v[g],y=function(){var t=m[E],n=t.decryptdata;if(!n.keyId)return 0;var s=kt.hexDump(n.keyId);return g===s||-1!==n.uri.replace(/-/g,"").indexOf(g)?(p=v[s],n.pssh||(delete v[s],n.pssh=new Uint8Array(i),n.keyId=a,p=v[g]=p.then((function(){return e.generateRequestWithPreferredKeySession(t,r,i,"encrypted-event-key-match")}))),1):void 0},E=0;E<m.length&&(0===(f=y())||1!==f);E++);p||(p=v[g]=this.getKeySystemSelectionPromise([s]).then((function(t){var n,s=t.keySystem,o=t.mediaKeys;e.throwIfDestroyed();var l=new Zt("ISO-23001-7",g,null!=(n=nt(s))?n:"");return l.pssh=new Uint8Array(i),l.keyId=a,e.attemptSetMediaKeys(s,o).then((function(){e.throwIfDestroyed();var t=e.createMediaKeySessionContext({decryptdata:l,keySystem:s,mediaKeys:o});return e.generateRequestWithPreferredKeySession(t,r,i,"encrypted-event-no-match")}))}))),p.catch((function(t){return e.handleError(t)}))}}},e._onWaitingForKey=function(t){this.log('"'+t.type+'" event')},e.attemptSetMediaKeys=function(t,e){var r=this,i=this.setMediaKeysQueue.slice();this.log('Setting media-keys for "'+t+'"');var n=Promise.all(i).then((function(){if(!r.media)throw new Error("Attempted to set mediaKeys without media element attached");return r.media.setMediaKeys(e)}));return this.setMediaKeysQueue.push(n),n.then((function(){r.log('Media-keys set for "'+t+'"'),i.push(n),r.setMediaKeysQueue=r.setMediaKeysQueue.filter((function(t){return-1===i.indexOf(t)}))}))},e.generateRequestWithPreferredKeySession=function(t,e,r,i){var n,a,s=this,o=null==(n=this.config.drmSystems)||null==(a=n[t.keySystem])?void 0:a.generateRequest;if(o)try{var l=o.call(this.hls,e,r,t);if(!l)throw new Error("Invalid response from configured generateRequest filter");e=l.initDataType,r=t.decryptdata.pssh=l.initData?new Uint8Array(l.initData):null}catch(t){var u;if(this.warn(t.message),null!=(u=this.hls)&&u.config.debug)throw t}if(null===r)return this.log('Skipping key-session request for "'+i+'" (no initData)'),Promise.resolve(t);var h=this.getKeyIdString(t.decryptdata);this.log('Generating key-session request for "'+i+'": '+h+" (init data type: "+e+" length: "+(r?r.byteLength:null)+")");var d=new Vn,c=t._onmessage=function(e){var r=t.mediaKeysSession;if(r){var i=e.messageType,n=e.message;s.log('"'+i+'" message event for session "'+r.sessionId+'" message size: '+n.byteLength),"license-request"===i||"license-renewal"===i?s.renewLicense(t,n).catch((function(t){s.handleError(t),d.emit("error",t)})):"license-release"===i?t.keySystem===q.FAIRPLAY&&(s.updateKeySession(t,W("acknowledged")),s.removeSession(t)):s.warn('unhandled media key message type "'+i+'"')}else d.emit("error",new Error("invalid state"))},f=t._onkeystatuseschange=function(e){if(t.mediaKeysSession){s.onKeyStatusChange(t);var r=t.keyStatus;d.emit("keyStatus",r),"expired"===r&&(s.warn(t.keySystem+" expired for key "+h),s.renewKeySession(t))}else d.emit("error",new Error("invalid state"))};t.mediaKeysSession.addEventListener("message",c),t.mediaKeysSession.addEventListener("keystatuseschange",f);var g=new Promise((function(t,e){d.on("error",e),d.on("keyStatus",(function(r){r.startsWith("usable")?t():"output-restricted"===r?e(new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED,fatal:!1},"HDCP level output restricted")):"internal-error"===r?e(new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_STATUS_INTERNAL_ERROR,fatal:!0},'key status changed to "'+r+'"')):"expired"===r?e(new Error("key expired while generating request")):s.warn('unhandled key status change "'+r+'"')}))}));return t.mediaKeysSession.generateRequest(e,r).then((function(){var e;s.log('Request generated for key-session "'+(null==(e=t.mediaKeysSession)?void 0:e.sessionId)+'" keyId: '+h)})).catch((function(t){throw new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_NO_SESSION,error:t,fatal:!1},"Error generating key-session request: "+t)})).then((function(){return g})).catch((function(e){throw d.removeAllListeners(),s.removeSession(t),e})).then((function(){return d.removeAllListeners(),t}))},e.onKeyStatusChange=function(t){var e=this;t.mediaKeysSession.keyStatuses.forEach((function(r,i){e.log('key status change "'+r+'" for keyStatuses keyId: '+kt.hexDump("buffer"in i?new Uint8Array(i.buffer,i.byteOffset,i.byteLength):new Uint8Array(i))+" session keyId: "+kt.hexDump(new Uint8Array(t.decryptdata.keyId||[]))+" uri: "+t.decryptdata.uri),t.keyStatus=r}))},e.fetchServerCertificate=function(t){var e=this.config,r=new(0,e.loader)(e),n=this.getServerCertificateUrl(t);return n?(this.log('Fetching server certificate for "'+t+'"'),new Promise((function(a,s){var o={responseType:"arraybuffer",url:n},l=e.certLoadPolicy.default,u={loadPolicy:l,timeout:l.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0},h={onSuccess:function(t,e,r,i){a(t.data)},onError:function(e,r,a,l){s(new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,fatal:!0,networkDetails:a,response:i({url:o.url,data:void 0},e)},'"'+t+'" certificate request failed ('+n+"). Status: "+e.code+" ("+e.text+")"))},onTimeout:function(e,r,i){s(new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED,fatal:!0,networkDetails:i,response:{url:o.url,data:void 0}},'"'+t+'" certificate request timed out ('+n+")"))},onAbort:function(t,e,r){s(new Error("aborted"))}};r.load(o,u,h)}))):Promise.resolve()},e.setMediaKeysServerCertificate=function(t,e,r){var i=this;return new Promise((function(n,a){t.setServerCertificate(r).then((function(a){i.log("setServerCertificate "+(a?"success":"not supported by CDM")+" ("+(null==r?void 0:r.byteLength)+') on "'+e+'"'),n(t)})).catch((function(t){a(new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED,error:t,fatal:!0},t.message))}))}))},e.renewLicense=function(t,e){var r=this;return this.requestLicense(t,new Uint8Array(e)).then((function(e){return r.updateKeySession(t,new Uint8Array(e)).catch((function(t){throw new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_SESSION_UPDATE_FAILED,error:t,fatal:!0},t.message)}))}))},e.unpackPlayReadyKeyMessage=function(t,e){var r=String.fromCharCode.apply(null,new Uint16Array(e.buffer));if(!r.includes("PlayReadyKeyMessage"))return t.setRequestHeader("Content-Type","text/xml; charset=utf-8"),e;var i=(new DOMParser).parseFromString(r,"application/xml"),n=i.querySelectorAll("HttpHeader");if(n.length>0)for(var a,s=0,o=n.length;s<o;s++){var l,u,h=null==(l=(a=n[s]).querySelector("name"))?void 0:l.textContent,d=null==(u=a.querySelector("value"))?void 0:u.textContent;h&&d&&t.setRequestHeader(h,d)}var c=i.querySelector("Challenge"),f=null==c?void 0:c.textContent;if(!f)throw new Error("Cannot find <Challenge> in key message");return W(atob(f))},e.setupLicenseXHR=function(t,e,r,i){var n=this,a=this.config.licenseXhrSetup;return a?Promise.resolve().then((function(){if(!r.decryptdata)throw new Error("Key removed");return a.call(n.hls,t,e,r,i)})).catch((function(s){if(!r.decryptdata)throw s;return t.open("POST",e,!0),a.call(n.hls,t,e,r,i)})).then((function(r){return t.readyState||t.open("POST",e,!0),{xhr:t,licenseChallenge:r||i}})):(t.open("POST",e,!0),Promise.resolve({xhr:t,licenseChallenge:i}))},e.requestLicense=function(t,e){var r=this,i=this.config.keyLoadPolicy.default;return new Promise((function(n,a){var s=r.getLicenseServerUrl(t.keySystem);r.log("Sending license request to URL: "+s);var o=new XMLHttpRequest;o.responseType="arraybuffer",o.onreadystatechange=function(){if(!r.hls||!t.mediaKeysSession)return a(new Error("invalid state"));if(4===o.readyState)if(200===o.status){r._requestLicenseFailureCount=0;var l=o.response;r.log("License received "+(l instanceof ArrayBuffer?l.byteLength:l));var u=r.config.licenseResponseCallback;if(u)try{l=u.call(r.hls,o,s,t)}catch(t){r.error(t)}n(l)}else{var h=i.errorRetry,d=h?h.maxNumRetry:0;if(r._requestLicenseFailureCount++,r._requestLicenseFailureCount>d||o.status>=400&&o.status<500)a(new ls({type:L.KEY_SYSTEM_ERROR,details:A.KEY_SYSTEM_LICENSE_REQUEST_FAILED,fatal:!0,networkDetails:o,response:{url:s,data:void 0,code:o.status,text:o.statusText}},"License Request XHR failed ("+s+"). Status: "+o.status+" ("+o.statusText+")"));else{var c=d-r._requestLicenseFailureCount+1;r.warn("Retrying license request, "+c+" attempts left"),r.requestLicense(t,e).then(n,a)}}},t.licenseXhr&&t.licenseXhr.readyState!==XMLHttpRequest.DONE&&t.licenseXhr.abort(),t.licenseXhr=o,r.setupLicenseXHR(o,s,t,e).then((function(e){var i=e.xhr,n=e.licenseChallenge;t.keySystem==q.PLAYREADY&&(n=r.unpackPlayReadyKeyMessage(i,n)),i.send(n)}))}))},e.onMediaAttached=function(t,e){if(this.config.emeEnabled){var r=e.media;this.media=r,r.addEventListener("encrypted",this.onMediaEncrypted),r.addEventListener("waitingforkey",this.onWaitingForKey)}},e.onMediaDetached=function(){var e=this,r=this.media,i=this.mediaKeySessions;r&&(r.removeEventListener("encrypted",this.onMediaEncrypted),r.removeEventListener("waitingforkey",this.onWaitingForKey),this.media=null),this._requestLicenseFailureCount=0,this.setMediaKeysQueue=[],this.mediaKeySessions=[],this.keyIdToKeySessionPromise={},Zt.clearKeyUriToKeyIdMap();var n=i.length;t.CDMCleanupPromise=Promise.all(i.map((function(t){return e.removeSession(t)})).concat(null==r?void 0:r.setMediaKeys(null).catch((function(t){e.log("Could not clear media keys: "+t)})))).then((function(){n&&(e.log("finished closing key sessions and clearing media keys"),i.length=0)})).catch((function(t){e.log("Could not close sessions and clear media keys: "+t)}))},e.onManifestLoading=function(){this.keyFormatPromise=null},e.onManifestLoaded=function(t,e){var r=e.sessionKeys;if(r&&this.config.emeEnabled&&!this.keyFormatPromise){var i=r.reduce((function(t,e){return-1===t.indexOf(e.keyFormat)&&t.push(e.keyFormat),t}),[]);this.log("Selecting key-system from session-keys "+i.join(", ")),this.keyFormatPromise=this.getKeyFormatPromise(i)}},e.removeSession=function(t){var e=this,r=t.mediaKeysSession,i=t.licenseXhr;if(r){this.log("Remove licenses and keys and close session "+r.sessionId),t._onmessage&&(r.removeEventListener("message",t._onmessage),t._onmessage=void 0),t._onkeystatuseschange&&(r.removeEventListener("keystatuseschange",t._onkeystatuseschange),t._onkeystatuseschange=void 0),i&&i.readyState!==XMLHttpRequest.DONE&&i.abort(),t.mediaKeysSession=t.decryptdata=t.licenseXhr=void 0;var n=this.mediaKeySessions.indexOf(t);return n>-1&&this.mediaKeySessions.splice(n,1),r.remove().catch((function(t){e.log("Could not remove session: "+t)})).then((function(){return r.close()})).catch((function(t){e.log("Could not close session: "+t)}))}},t}();is.CDMCleanupPromise=void 0;var ns,as,ss,os,ls=function(t){function e(e,r){var i;return(i=t.call(this,r)||this).data=void 0,e.error||(e.error=new Error(r)),i.data=e,e.err=e.error,i}return l(e,t),e}(c(Error));!function(t){t.MANIFEST="m",t.AUDIO="a",t.VIDEO="v",t.MUXED="av",t.INIT="i",t.CAPTION="c",t.TIMED_TEXT="tt",t.KEY="k",t.OTHER="o"}(ns||(ns={})),function(t){t.DASH="d",t.HLS="h",t.SMOOTH="s",t.OTHER="o"}(as||(as={})),function(t){t.OBJECT="CMCD-Object",t.REQUEST="CMCD-Request",t.SESSION="CMCD-Session",t.STATUS="CMCD-Status"}(ss||(ss={}));var us=((os={})[ss.OBJECT]=["br","d","ot","tb"],os[ss.REQUEST]=["bl","dl","mtp","nor","nrr","su"],os[ss.SESSION]=["cid","pr","sf","sid","st","v"],os[ss.STATUS]=["bs","rtp"],os),hs=function t(e,r){this.value=void 0,this.params=void 0,Array.isArray(e)&&(e=e.map((function(e){return e instanceof t?e:new t(e)}))),this.value=e,this.params=r},ds=function(t){this.description=void 0,this.description=t},cs="Dict";function fs(t,e,r,i){return new Error("failed to "+t+' "'+(n=e,(Array.isArray(n)?JSON.stringify(n):n instanceof Map?"Map{}":n instanceof Set?"Set{}":"object"==typeof n?JSON.stringify(n):String(n))+'" as ')+r,{cause:i});var n}var gs="Bare Item",vs="Boolean",ms="Byte Sequence",ps="Decimal",ys="Integer",Es=/[\x00-\x1f\x7f]+/,Ts="Token",Ss="Key";function Ls(t,e,r){return fs("serialize",t,e,r)}function As(t){if(!1===ArrayBuffer.isView(t))throw Ls(t,ms);return":"+(e=t,btoa(String.fromCharCode.apply(String,e))+":");var e}function Rs(t){if(function(t){return t<-999999999999999||999999999999999<t}(t))throw Ls(t,ys);return t.toString()}function bs(t,e){if(t<0)return-bs(-t,e);var r=Math.pow(10,e);if(Math.abs(t*r%1-.5)<Number.EPSILON){var i=Math.floor(t*r);return(i%2==0?i:i+1)/r}return Math.round(t*r)/r}function ks(t){var e=bs(t,3);if(Math.floor(Math.abs(e)).toString().length>12)throw Ls(t,ps);var r=e.toString();return r.includes(".")?r:r+".0"}var Ds="String";function Is(t){var e,r=(e=t).description||e.toString().slice(7,-1);if(!1===/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(r))throw Ls(r,Ts);return r}function ws(t){switch(typeof t){case"number":if(!y(t))throw Ls(t,gs);return Number.isInteger(t)?Rs(t):ks(t);case"string":return function(t){if(Es.test(t))throw Ls(t,Ds);return'"'+t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'}(t);case"symbol":return Is(t);case"boolean":return function(t){if("boolean"!=typeof t)throw Ls(t,vs);return t?"?1":"?0"}(t);case"object":if(t instanceof Date)return function(t){return"@"+Rs(t.getTime()/1e3)}(t);if(t instanceof Uint8Array)return As(t);if(t instanceof ds)return Is(t);default:throw Ls(t,gs)}}function Cs(t){if(!1===/^[a-z*][a-z0-9\-_.*]*$/.test(t))throw Ls(t,Ss);return t}function _s(t){return null==t?"":Object.entries(t).map((function(t){var e=t[0],r=t[1];return!0===r?";"+Cs(e):";"+Cs(e)+"="+ws(r)})).join("")}function xs(t){return t instanceof hs?""+ws(t.value)+_s(t.params):ws(t)}function Ps(t,e){var r;if(void 0===e&&(e={whitespace:!0}),"object"!=typeof t)throw Ls(t,cs);var i=t instanceof Map?t.entries():Object.entries(t),n=null!=(r=e)&&r.whitespace?" ":"";return Array.from(i).map((function(t){var e=t[0],r=t[1];r instanceof hs==0&&(r=new hs(r));var i,n=Cs(e);return!0===r.value?n+=_s(r.params):(n+="=",Array.isArray(r.value)?n+="("+(i=r).value.map(xs).join(" ")+")"+_s(i.params):n+=xs(r)),n})).join(","+n)}var Fs=function(t){return"ot"===t||"sf"===t||"st"===t},Ms=function(t){return"number"==typeof t?y(t):null!=t&&""!==t&&!1!==t},Os=function(t){return Math.round(t)},Ns=function(t){return 100*Os(t/100)},Us={br:Os,d:Os,bl:Ns,dl:Ns,mtp:Ns,nor:function(t,e){return null!=e&&e.baseUrl&&(t=function(t,e){var r=new URL(t),i=new URL(e);if(r.origin!==i.origin)return t;for(var n=r.pathname.split("/").slice(1),a=i.pathname.split("/").slice(1,-1);n[0]===a[0];)n.shift(),a.shift();for(;a.length;)a.shift(),n.unshift("..");return n.join("/")}(t,e.baseUrl)),encodeURIComponent(t)},rtp:Ns,tb:Os};function Bs(t,e){return void 0===e&&(e={}),t?function(t,e){return Ps(t,e)}(function(t,e){var r={};if(null==t||"object"!=typeof t)return r;var i=Object.keys(t).sort(),n=o({},Us,null==e?void 0:e.formatters),a=null==e?void 0:e.filter;return i.forEach((function(i){if(null==a||!a(i)){var s=t[i],o=n[i];o&&(s=o(s,e)),"v"===i&&1===s||"pr"==i&&1===s||Ms(s)&&(Fs(i)&&"string"==typeof s&&(s=new ds(s)),r[i]=s)}})),r}(t,e),o({whitespace:!1},e)):""}function Gs(t,e,r){return o(t,function(t,e){var r;if(void 0===e&&(e={}),!t)return{};var i=Object.entries(t),n=Object.entries(us).concat(Object.entries((null==(r=e)?void 0:r.customHeaderMap)||{})),a=i.reduce((function(t,e){var r,i=e[0],a=e[1],s=(null==(r=n.find((function(t){return t[1].includes(i)})))?void 0:r[0])||ss.REQUEST;return null!=t[s]||(t[s]={}),t[s][i]=a,t}),{});return Object.entries(a).reduce((function(t,r){var i=r[0],n=r[1];return t[i]=Bs(n,e),t}),{})}(e,r))}var Ks="CMCD",Hs=/CMCD=[^&#]+/;function Vs(t,e,r){var i=function(t,e){if(void 0===e&&(e={}),!t)return"";var r=Bs(t,e);return Ks+"="+encodeURIComponent(r)}(e,r);if(!i)return t;if(Hs.test(t))return t.replace(Hs,i);var n=t.includes("?")?"&":"?";return""+t+n+i}var Ys=function(){function t(t){var e=this;this.hls=void 0,this.config=void 0,this.media=void 0,this.sid=void 0,this.cid=void 0,this.useHeaders=!1,this.includeKeys=void 0,this.initialized=!1,this.starved=!1,this.buffering=!0,this.audioBuffer=void 0,this.videoBuffer=void 0,this.onWaiting=function(){e.initialized&&(e.starved=!0),e.buffering=!0},this.onPlaying=function(){e.initialized||(e.initialized=!0),e.buffering=!1},this.applyPlaylistData=function(t){try{e.apply(t,{ot:ns.MANIFEST,su:!e.initialized})}catch(t){w.warn("Could not generate manifest CMCD data.",t)}},this.applyFragmentData=function(t){try{var r=t.frag,i=e.hls.levels[r.level],n=e.getObjectType(r),a={d:1e3*r.duration,ot:n};n!==ns.VIDEO&&n!==ns.AUDIO&&n!=ns.MUXED||(a.br=i.bitrate/1e3,a.tb=e.getTopBandwidth(n)/1e3,a.bl=e.getBufferLength(n)),e.apply(t,a)}catch(t){w.warn("Could not generate segment CMCD data.",t)}},this.hls=t;var r=this.config=t.config,i=r.cmcd;null!=i&&(r.pLoader=this.createPlaylistLoader(),r.fLoader=this.createFragmentLoader(),this.sid=i.sessionId||function(){try{return crypto.randomUUID()}catch(i){try{var t=URL.createObjectURL(new Blob),e=t.toString();return URL.revokeObjectURL(t),e.slice(e.lastIndexOf("/")+1)}catch(t){var r=(new Date).getTime();return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(function(t){var e=(r+16*Math.random())%16|0;return r=Math.floor(r/16),("x"==t?e:3&e|8).toString(16)}))}}}(),this.cid=i.contentId,this.useHeaders=!0===i.useHeaders,this.includeKeys=i.includeKeys,this.registerListeners())}var e=t.prototype;return e.registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHED,this.onMediaDetached,this),t.on(S.BUFFER_CREATED,this.onBufferCreated,this)},e.unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHED,this.onMediaDetached,this),t.off(S.BUFFER_CREATED,this.onBufferCreated,this)},e.destroy=function(){this.unregisterListeners(),this.onMediaDetached(),this.hls=this.config=this.audioBuffer=this.videoBuffer=null,this.onWaiting=this.onPlaying=null},e.onMediaAttached=function(t,e){this.media=e.media,this.media.addEventListener("waiting",this.onWaiting),this.media.addEventListener("playing",this.onPlaying)},e.onMediaDetached=function(){this.media&&(this.media.removeEventListener("waiting",this.onWaiting),this.media.removeEventListener("playing",this.onPlaying),this.media=null)},e.onBufferCreated=function(t,e){var r,i;this.audioBuffer=null==(r=e.tracks.audio)?void 0:r.buffer,this.videoBuffer=null==(i=e.tracks.video)?void 0:i.buffer},e.createData=function(){var t;return{v:1,sf:as.HLS,sid:this.sid,cid:this.cid,pr:null==(t=this.media)?void 0:t.playbackRate,mtp:this.hls.bandwidthEstimate/1e3}},e.apply=function(t,e){void 0===e&&(e={}),o(e,this.createData());var r=e.ot===ns.INIT||e.ot===ns.VIDEO||e.ot===ns.MUXED;this.starved&&r&&(e.bs=!0,e.su=!0,this.starved=!1),null==e.su&&(e.su=this.buffering);var i=this.includeKeys;i&&(e=Object.keys(e).reduce((function(t,r){return i.includes(r)&&(t[r]=e[r]),t}),{})),this.useHeaders?(t.headers||(t.headers={}),Gs(t.headers,e)):t.url=Vs(t.url,e)},e.getObjectType=function(t){var e=t.type;return"subtitle"===e?ns.TIMED_TEXT:"initSegment"===t.sn?ns.INIT:"audio"===e?ns.AUDIO:"main"===e?this.hls.audioTracks.length?ns.VIDEO:ns.MUXED:void 0},e.getTopBandwidth=function(t){var e,r=0,i=this.hls;if(t===ns.AUDIO)e=i.audioTracks;else{var n=i.maxAutoLevel,a=n>-1?n+1:i.levels.length;e=i.levels.slice(0,a)}for(var s,o=g(e);!(s=o()).done;){var l=s.value;l.bitrate>r&&(r=l.bitrate)}return r>0?r:NaN},e.getBufferLength=function(t){var e=this.hls.media,r=t===ns.AUDIO?this.audioBuffer:this.videoBuffer;return r&&e?1e3*ri.bufferInfo(r,e.currentTime,this.config.maxBufferHole).len:NaN},e.createPlaylistLoader=function(){var t=this.config.pLoader,e=this.applyPlaylistData,r=t||this.config.loader;return function(){function t(t){this.loader=void 0,this.loader=new r(t)}var i=t.prototype;return i.destroy=function(){this.loader.destroy()},i.abort=function(){this.loader.abort()},i.load=function(t,r,i){e(t),this.loader.load(t,r,i)},s(t,[{key:"stats",get:function(){return this.loader.stats}},{key:"context",get:function(){return this.loader.context}}]),t}()},e.createFragmentLoader=function(){var t=this.config.fLoader,e=this.applyFragmentData,r=t||this.config.loader;return function(){function t(t){this.loader=void 0,this.loader=new r(t)}var i=t.prototype;return i.destroy=function(){this.loader.destroy()},i.abort=function(){this.loader.abort()},i.load=function(t,r,i){e(t),this.loader.load(t,r,i)},s(t,[{key:"stats",get:function(){return this.loader.stats}},{key:"context",get:function(){return this.loader.context}}]),t}()},t}(),Ws=function(){function t(t){this.hls=void 0,this.log=void 0,this.loader=null,this.uri=null,this.pathwayId=".",this.pathwayPriority=null,this.timeToLoad=300,this.reloadTimer=-1,this.updated=0,this.started=!1,this.enabled=!0,this.levels=null,this.audioTracks=null,this.subtitleTracks=null,this.penalizedPathways={},this.hls=t,this.log=w.log.bind(w,"[content-steering]:"),this.registerListeners()}var e=t.prototype;return e.registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.ERROR,this.onError,this)},e.unregisterListeners=function(){var t=this.hls;t&&(t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.ERROR,this.onError,this))},e.startLoad=function(){if(this.started=!0,this.clearTimeout(),this.enabled&&this.uri){if(this.updated){var t=1e3*this.timeToLoad-(performance.now()-this.updated);if(t>0)return void this.scheduleRefresh(this.uri,t)}this.loadSteeringManifest(this.uri)}},e.stopLoad=function(){this.started=!1,this.loader&&(this.loader.destroy(),this.loader=null),this.clearTimeout()},e.clearTimeout=function(){-1!==this.reloadTimer&&(self.clearTimeout(this.reloadTimer),this.reloadTimer=-1)},e.destroy=function(){this.unregisterListeners(),this.stopLoad(),this.hls=null,this.levels=this.audioTracks=this.subtitleTracks=null},e.removeLevel=function(t){var e=this.levels;e&&(this.levels=e.filter((function(e){return e!==t})))},e.onManifestLoading=function(){this.stopLoad(),this.enabled=!0,this.timeToLoad=300,this.updated=0,this.uri=null,this.pathwayId=".",this.levels=this.audioTracks=this.subtitleTracks=null},e.onManifestLoaded=function(t,e){var r=e.contentSteering;null!==r&&(this.pathwayId=r.pathwayId,this.uri=r.uri,this.started&&this.startLoad())},e.onManifestParsed=function(t,e){this.audioTracks=e.audioTracks,this.subtitleTracks=e.subtitleTracks},e.onError=function(t,e){var r=e.errorAction;if((null==r?void 0:r.action)===Dr&&r.flags===_r){var i=this.levels,n=this.pathwayPriority,a=this.pathwayId;if(e.context){var s=e.context,o=s.groupId,l=s.pathwayId,u=s.type;o&&i?a=this.getPathwayForGroupId(o,u,a):l&&(a=l)}a in this.penalizedPathways||(this.penalizedPathways[a]=performance.now()),!n&&i&&(n=i.reduce((function(t,e){return-1===t.indexOf(e.pathwayId)&&t.push(e.pathwayId),t}),[])),n&&n.length>1&&(this.updatePathwayPriority(n),r.resolved=this.pathwayId!==a),r.resolved||w.warn("Could not resolve "+e.details+' ("'+e.error.message+'") with content-steering for Pathway: '+a+" levels: "+(i?i.length:i)+" priorities: "+JSON.stringify(n)+" penalized: "+JSON.stringify(this.penalizedPathways))}},e.filterParsedLevels=function(t){this.levels=t;var e=this.getLevelsForPathway(this.pathwayId);if(0===e.length){var r=t[0].pathwayId;this.log("No levels found in Pathway "+this.pathwayId+'. Setting initial Pathway to "'+r+'"'),e=this.getLevelsForPathway(r),this.pathwayId=r}return e.length!==t.length?(this.log("Found "+e.length+"/"+t.length+' levels in Pathway "'+this.pathwayId+'"'),e):t},e.getLevelsForPathway=function(t){return null===this.levels?[]:this.levels.filter((function(e){return t===e.pathwayId}))},e.updatePathwayPriority=function(t){var e;this.pathwayPriority=t;var r=this.penalizedPathways,i=performance.now();Object.keys(r).forEach((function(t){i-r[t]>3e5&&delete r[t]}));for(var n=0;n<t.length;n++){var a=t[n];if(!(a in r)){if(a===this.pathwayId)return;var s=this.hls.nextLoadLevel,o=this.hls.levels[s];if((e=this.getLevelsForPathway(a)).length>0){this.log('Setting Pathway to "'+a+'"'),this.pathwayId=a,mr(e),this.hls.trigger(S.LEVELS_UPDATED,{levels:e});var l=this.hls.levels[s];o&&l&&this.levels&&(l.attrs["STABLE-VARIANT-ID"]!==o.attrs["STABLE-VARIANT-ID"]&&l.bitrate!==o.bitrate&&this.log("Unstable Pathways change from bitrate "+o.bitrate+" to "+l.bitrate),this.hls.nextLoadLevel=s);break}}}},e.getPathwayForGroupId=function(t,e,r){for(var i=this.getLevelsForPathway(r).concat(this.levels||[]),n=0;n<i.length;n++)if(e===xe&&i[n].hasAudioGroup(t)||e===Pe&&i[n].hasSubtitleGroup(t))return i[n].pathwayId;return r},e.clonePathways=function(t){var e=this,r=this.levels;if(r){var i={},n={};t.forEach((function(t){var a=t.ID,s=t["BASE-ID"],o=t["URI-REPLACEMENT"];if(!r.some((function(t){return t.pathwayId===a}))){var l=e.getLevelsForPathway(s).map((function(t){var e=new x(t.attrs);e["PATHWAY-ID"]=a;var r=e.AUDIO&&e.AUDIO+"_clone_"+a,s=e.SUBTITLES&&e.SUBTITLES+"_clone_"+a;r&&(i[e.AUDIO]=r,e.AUDIO=r),s&&(n[e.SUBTITLES]=s,e.SUBTITLES=s);var l=qs(t.uri,e["STABLE-VARIANT-ID"],"PER-VARIANT-URIS",o),u=new or({attrs:e,audioCodec:t.audioCodec,bitrate:t.bitrate,height:t.height,name:t.name,url:l,videoCodec:t.videoCodec,width:t.width});if(t.audioGroups)for(var h=1;h<t.audioGroups.length;h++)u.addGroupId("audio",t.audioGroups[h]+"_clone_"+a);if(t.subtitleGroups)for(var d=1;d<t.subtitleGroups.length;d++)u.addGroupId("text",t.subtitleGroups[d]+"_clone_"+a);return u}));r.push.apply(r,l),js(e.audioTracks,i,o,a),js(e.subtitleTracks,n,o,a)}}))}},e.loadSteeringManifest=function(t){var e,r=this,i=this.hls.config,n=i.loader;this.loader&&this.loader.destroy(),this.loader=new n(i);try{e=new self.URL(t)}catch(e){return this.enabled=!1,void this.log("Failed to parse Steering Manifest URI: "+t)}if("data:"!==e.protocol){var a=0|(this.hls.bandwidthEstimate||i.abrEwmaDefaultEstimate);e.searchParams.set("_HLS_pathway",this.pathwayId),e.searchParams.set("_HLS_throughput",""+a)}var s={responseType:"json",url:e.href},o=i.steeringManifestLoadPolicy.default,l=o.errorRetry||o.timeoutRetry||{},u={loadPolicy:o,timeout:o.maxLoadTimeMs,maxRetry:l.maxNumRetry||0,retryDelay:l.retryDelayMs||0,maxRetryDelay:l.maxRetryDelayMs||0},h={onSuccess:function(t,i,n,a){r.log('Loaded steering manifest: "'+e+'"');var s=t.data;if(1===s.VERSION){r.updated=performance.now(),r.timeToLoad=s.TTL;var o=s["RELOAD-URI"],l=s["PATHWAY-CLONES"],u=s["PATHWAY-PRIORITY"];if(o)try{r.uri=new self.URL(o,e).href}catch(t){return r.enabled=!1,void r.log("Failed to parse Steering Manifest RELOAD-URI: "+o)}r.scheduleRefresh(r.uri||n.url),l&&r.clonePathways(l);var h={steeringManifest:s,url:e.toString()};r.hls.trigger(S.STEERING_MANIFEST_LOADED,h),u&&r.updatePathwayPriority(u)}else r.log("Steering VERSION "+s.VERSION+" not supported!")},onError:function(t,e,i,n){if(r.log("Error loading steering manifest: "+t.code+" "+t.text+" ("+e.url+")"),r.stopLoad(),410===t.code)return r.enabled=!1,void r.log("Steering manifest "+e.url+" no longer available");var a=1e3*r.timeToLoad;if(429!==t.code)r.scheduleRefresh(r.uri||e.url,a);else{var s=r.loader;if("function"==typeof(null==s?void 0:s.getResponseHeader)){var o=s.getResponseHeader("Retry-After");o&&(a=1e3*parseFloat(o))}r.log("Steering manifest "+e.url+" rate limited")}},onTimeout:function(t,e,i){r.log("Timeout loading steering manifest ("+e.url+")"),r.scheduleRefresh(r.uri||e.url)}};this.log("Requesting steering manifest: "+e),this.loader.load(s,u,h)},e.scheduleRefresh=function(t,e){var r=this;void 0===e&&(e=1e3*this.timeToLoad),this.clearTimeout(),this.reloadTimer=self.setTimeout((function(){var e,i=null==(e=r.hls)?void 0:e.media;!i||i.ended?r.scheduleRefresh(t,1e3*r.timeToLoad):r.loadSteeringManifest(t)}),e)},t}();function js(t,e,r,i){t&&Object.keys(e).forEach((function(n){var a=t.filter((function(t){return t.groupId===n})).map((function(t){var a=o({},t);return a.details=void 0,a.attrs=new x(a.attrs),a.url=a.attrs.URI=qs(t.url,t.attrs["STABLE-RENDITION-ID"],"PER-RENDITION-URIS",r),a.groupId=a.attrs["GROUP-ID"]=e[n],a.attrs["PATHWAY-ID"]=i,a}));t.push.apply(t,a)}))}function qs(t,e,r,i){var n,a=i.HOST,s=i.PARAMS,o=i[r];e&&(n=null==o?void 0:o[e])&&(t=n);var l=new self.URL(t);return a&&!n&&(l.host=a),s&&Object.keys(s).sort().forEach((function(t){t&&l.searchParams.set(t,s[t])})),l.href}var Xs=/^age:\s*[\d.]+\s*$/im,zs=function(){function t(t){this.xhrSetup=void 0,this.requestTimeout=void 0,this.retryTimeout=void 0,this.retryDelay=void 0,this.config=null,this.callbacks=null,this.context=null,this.loader=null,this.stats=void 0,this.xhrSetup=t&&t.xhrSetup||null,this.stats=new M,this.retryDelay=0}var e=t.prototype;return e.destroy=function(){this.callbacks=null,this.abortInternal(),this.loader=null,this.config=null,this.context=null,this.xhrSetup=null},e.abortInternal=function(){var t=this.loader;self.clearTimeout(this.requestTimeout),self.clearTimeout(this.retryTimeout),t&&(t.onreadystatechange=null,t.onprogress=null,4!==t.readyState&&(this.stats.aborted=!0,t.abort()))},e.abort=function(){var t;this.abortInternal(),null!=(t=this.callbacks)&&t.onAbort&&this.callbacks.onAbort(this.stats,this.context,this.loader)},e.load=function(t,e,r){if(this.stats.loading.start)throw new Error("Loader can only be used once.");this.stats.loading.start=self.performance.now(),this.context=t,this.config=e,this.callbacks=r,this.loadInternal()},e.loadInternal=function(){var t=this,e=this.config,r=this.context;if(e&&r){var i=this.loader=new self.XMLHttpRequest,n=this.stats;n.loading.first=0,n.loaded=0,n.aborted=!1;var a=this.xhrSetup;a?Promise.resolve().then((function(){if(t.loader===i&&!t.stats.aborted)return a(i,r.url)})).catch((function(e){if(t.loader===i&&!t.stats.aborted)return i.open("GET",r.url,!0),a(i,r.url)})).then((function(){t.loader!==i||t.stats.aborted||t.openAndSendXhr(i,r,e)})).catch((function(e){t.callbacks.onError({code:i.status,text:e.message},r,i,n)})):this.openAndSendXhr(i,r,e)}},e.openAndSendXhr=function(t,e,r){t.readyState||t.open("GET",e.url,!0);var i=e.headers,n=r.loadPolicy,a=n.maxTimeToFirstByteMs,s=n.maxLoadTimeMs;if(i)for(var o in i)t.setRequestHeader(o,i[o]);e.rangeEnd&&t.setRequestHeader("Range","bytes="+e.rangeStart+"-"+(e.rangeEnd-1)),t.onreadystatechange=this.readystatechange.bind(this),t.onprogress=this.loadprogress.bind(this),t.responseType=e.responseType,self.clearTimeout(this.requestTimeout),r.timeout=a&&y(a)?a:s,this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this),r.timeout),t.send()},e.readystatechange=function(){var t=this.context,e=this.loader,r=this.stats;if(t&&e){var i=e.readyState,n=this.config;if(!r.aborted&&i>=2&&(0===r.loading.first&&(r.loading.first=Math.max(self.performance.now(),r.loading.start),n.timeout!==n.loadPolicy.maxLoadTimeMs&&(self.clearTimeout(this.requestTimeout),n.timeout=n.loadPolicy.maxLoadTimeMs,this.requestTimeout=self.setTimeout(this.loadtimeout.bind(this),n.loadPolicy.maxLoadTimeMs-(r.loading.first-r.loading.start)))),4===i)){self.clearTimeout(this.requestTimeout),e.onreadystatechange=null,e.onprogress=null;var a=e.status,s="text"!==e.responseType;if(a>=200&&a<300&&(s&&e.response||null!==e.responseText)){r.loading.end=Math.max(self.performance.now(),r.loading.first);var o=s?e.response:e.responseText,l="arraybuffer"===e.responseType?o.byteLength:o.length;if(r.loaded=r.total=l,r.bwEstimate=8e3*r.total/(r.loading.end-r.loading.first),!this.callbacks)return;var u=this.callbacks.onProgress;if(u&&u(r,t,o,e),!this.callbacks)return;var h={url:e.responseURL,data:o,code:a};this.callbacks.onSuccess(h,r,t,e)}else{var d=n.loadPolicy.errorRetry;Sr(d,r.retry,!1,{url:t.url,data:void 0,code:a})?this.retry(d):(w.error(a+" while loading "+t.url),this.callbacks.onError({code:a,text:e.statusText},t,e,r))}}}},e.loadtimeout=function(){if(this.config){var t=this.config.loadPolicy.timeoutRetry;if(Sr(t,this.stats.retry,!0))this.retry(t);else{var e;w.warn("timeout while loading "+(null==(e=this.context)?void 0:e.url));var r=this.callbacks;r&&(this.abortInternal(),r.onTimeout(this.stats,this.context,this.loader))}}},e.retry=function(t){var e=this.context,r=this.stats;this.retryDelay=Er(t,r.retry),r.retry++,w.warn((status?"HTTP Status "+status:"Timeout")+" while loading "+(null==e?void 0:e.url)+", retrying "+r.retry+"/"+t.maxNumRetry+" in "+this.retryDelay+"ms"),this.abortInternal(),this.loader=null,self.clearTimeout(this.retryTimeout),this.retryTimeout=self.setTimeout(this.loadInternal.bind(this),this.retryDelay)},e.loadprogress=function(t){var e=this.stats;e.loaded=t.loaded,t.lengthComputable&&(e.total=t.total)},e.getCacheAge=function(){var t=null;if(this.loader&&Xs.test(this.loader.getAllResponseHeaders())){var e=this.loader.getResponseHeader("age");t=e?parseFloat(e):null}return t},e.getResponseHeader=function(t){return this.loader&&new RegExp("^"+t+":\\s*[\\d.]+\\s*$","im").test(this.loader.getAllResponseHeaders())?this.loader.getResponseHeader(t):null},t}(),Qs=/(\d+)-(\d+)\/(\d+)/,Js=function(){function t(t){this.fetchSetup=void 0,this.requestTimeout=void 0,this.request=null,this.response=null,this.controller=void 0,this.context=null,this.config=null,this.callbacks=null,this.stats=void 0,this.loader=null,this.fetchSetup=t.fetchSetup||$s,this.controller=new self.AbortController,this.stats=new M}var e=t.prototype;return e.destroy=function(){this.loader=this.callbacks=this.context=this.config=this.request=null,this.abortInternal(),this.response=null,this.fetchSetup=this.controller=this.stats=null},e.abortInternal=function(){this.controller&&!this.stats.loading.end&&(this.stats.aborted=!0,this.controller.abort())},e.abort=function(){var t;this.abortInternal(),null!=(t=this.callbacks)&&t.onAbort&&this.callbacks.onAbort(this.stats,this.context,this.response)},e.load=function(t,e,r){var i=this,n=this.stats;if(n.loading.start)throw new Error("Loader can only be used once.");n.loading.start=self.performance.now();var a=function(t,e){var r={method:"GET",mode:"cors",credentials:"same-origin",signal:e,headers:new self.Headers(o({},t.headers))};return t.rangeEnd&&r.headers.set("Range","bytes="+t.rangeStart+"-"+String(t.rangeEnd-1)),r}(t,this.controller.signal),s=r.onProgress,l="arraybuffer"===t.responseType,u=l?"byteLength":"length",h=e.loadPolicy,d=h.maxTimeToFirstByteMs,c=h.maxLoadTimeMs;this.context=t,this.config=e,this.callbacks=r,this.request=this.fetchSetup(t,a),self.clearTimeout(this.requestTimeout),e.timeout=d&&y(d)?d:c,this.requestTimeout=self.setTimeout((function(){i.abortInternal(),r.onTimeout(n,t,i.response)}),e.timeout),self.fetch(this.request).then((function(a){i.response=i.loader=a;var o=Math.max(self.performance.now(),n.loading.start);if(self.clearTimeout(i.requestTimeout),e.timeout=c,i.requestTimeout=self.setTimeout((function(){i.abortInternal(),r.onTimeout(n,t,i.response)}),c-(o-n.loading.start)),!a.ok){var u=a.status,h=a.statusText;throw new to(h||"fetch, bad network response",u,a)}return n.loading.first=o,n.total=function(t){var e=t.get("Content-Range");if(e){var r=function(t){var e=Qs.exec(t);if(e)return parseInt(e[2])-parseInt(e[1])+1}(e);if(y(r))return r}var i=t.get("Content-Length");if(i)return parseInt(i)}(a.headers)||n.total,s&&y(e.highWaterMark)?i.loadProgressively(a,n,t,e.highWaterMark,s):l?a.arrayBuffer():"json"===t.responseType?a.json():a.text()})).then((function(a){var o=i.response;if(!o)throw new Error("loader destroyed");self.clearTimeout(i.requestTimeout),n.loading.end=Math.max(self.performance.now(),n.loading.first);var l=a[u];l&&(n.loaded=n.total=l);var h={url:o.url,data:a,code:o.status};s&&!y(e.highWaterMark)&&s(n,t,a,o),r.onSuccess(h,n,t,o)})).catch((function(e){if(self.clearTimeout(i.requestTimeout),!n.aborted){var a=e&&e.code||0,s=e?e.message:null;r.onError({code:a,text:s},t,e?e.details:null,n)}}))},e.getCacheAge=function(){var t=null;if(this.response){var e=this.response.headers.get("age");t=e?parseFloat(e):null}return t},e.getResponseHeader=function(t){return this.response?this.response.headers.get(t):null},e.loadProgressively=function(t,e,r,i,n){void 0===i&&(i=0);var a=new xi,s=t.body.getReader();return function o(){return s.read().then((function(s){if(s.done)return a.dataLength&&n(e,r,a.flush(),t),Promise.resolve(new ArrayBuffer(0));var l=s.value,u=l.length;return e.loaded+=u,u<i||a.dataLength?(a.push(l),a.dataLength>=i&&n(e,r,a.flush(),t)):n(e,r,l,t),o()})).catch((function(){return Promise.reject()}))}()},t}();function $s(t,e){return new self.Request(t.url,e)}var Zs,to=function(t){function e(e,r,i){var n;return(n=t.call(this,e)||this).code=void 0,n.details=void 0,n.code=r,n.details=i,n}return l(e,t),e}(c(Error)),eo=/\s/,ro=i(i({autoStartLoad:!0,startPosition:-1,defaultAudioCodec:void 0,debug:!1,capLevelOnFPSDrop:!1,capLevelToPlayerSize:!1,ignoreDevicePixelRatio:!1,preferManagedMediaSource:!0,initialLiveManifestSize:1,maxBufferLength:30,backBufferLength:1/0,frontBufferFlushThreshold:1/0,maxBufferSize:6e7,maxBufferHole:.1,highBufferWatchdogPeriod:2,nudgeOffset:.1,nudgeMaxRetry:3,maxFragLookUpTolerance:.25,liveSyncDurationCount:3,liveMaxLatencyDurationCount:1/0,liveSyncDuration:void 0,liveMaxLatencyDuration:void 0,maxLiveSyncPlaybackRate:1,liveDurationInfinity:!1,liveBackBufferLength:null,maxMaxBufferLength:600,enableWorker:!0,workerPath:null,enableSoftwareAES:!0,startLevel:void 0,startFragPrefetch:!1,fpsDroppedMonitoringPeriod:5e3,fpsDroppedMonitoringThreshold:.2,appendErrorMaxRetry:3,loader:zs,fLoader:void 0,pLoader:void 0,xhrSetup:void 0,licenseXhrSetup:void 0,licenseResponseCallback:void 0,abrController:jr,bufferController:na,capLevelController:ts,errorController:Pr,fpsController:es,stretchShortVideoTrack:!1,maxAudioFramesDrift:1,forceKeyFrameOnDiscontinuity:!0,abrEwmaFastLive:3,abrEwmaSlowLive:9,abrEwmaFastVoD:3,abrEwmaSlowVoD:9,abrEwmaDefaultEstimate:5e5,abrEwmaDefaultEstimateMax:5e6,abrBandWidthFactor:.95,abrBandWidthUpFactor:.7,abrMaxWithRealBitrate:!1,maxStarvationDelay:4,maxLoadingDelay:4,minAutoBitrate:0,emeEnabled:!1,widevineLicenseUrl:void 0,drmSystems:{},drmSystemOptions:{},requestMediaKeySystemAccessFunc:ot,testBandwidth:!0,progressive:!1,lowLatencyMode:!0,cmcd:void 0,enableDateRangeMetadataCues:!0,enableEmsgMetadataCues:!0,enableID3MetadataCues:!0,useMediaCapabilities:!0,certLoadPolicy:{default:{maxTimeToFirstByteMs:8e3,maxLoadTimeMs:2e4,timeoutRetry:null,errorRetry:null}},keyLoadPolicy:{default:{maxTimeToFirstByteMs:8e3,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:1,retryDelayMs:1e3,maxRetryDelayMs:2e4,backoff:"linear"},errorRetry:{maxNumRetry:8,retryDelayMs:1e3,maxRetryDelayMs:2e4,backoff:"linear"}}},manifestLoadPolicy:{default:{maxTimeToFirstByteMs:1/0,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:2,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:1,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},playlistLoadPolicy:{default:{maxTimeToFirstByteMs:1e4,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:2,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:2,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},fragLoadPolicy:{default:{maxTimeToFirstByteMs:1e4,maxLoadTimeMs:12e4,timeoutRetry:{maxNumRetry:4,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:6,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},steeringManifestLoadPolicy:{default:{maxTimeToFirstByteMs:1e4,maxLoadTimeMs:2e4,timeoutRetry:{maxNumRetry:2,retryDelayMs:0,maxRetryDelayMs:0},errorRetry:{maxNumRetry:1,retryDelayMs:1e3,maxRetryDelayMs:8e3}}},manifestLoadingTimeOut:1e4,manifestLoadingMaxRetry:1,manifestLoadingRetryDelay:1e3,manifestLoadingMaxRetryTimeout:64e3,levelLoadingTimeOut:1e4,levelLoadingMaxRetry:4,levelLoadingRetryDelay:1e3,levelLoadingMaxRetryTimeout:64e3,fragLoadingTimeOut:2e4,fragLoadingMaxRetry:6,fragLoadingRetryDelay:1e3,fragLoadingMaxRetryTimeout:64e3},{cueHandler:{newCue:function(t,e,r,i){for(var n,a,s,o,l,u=[],h=self.VTTCue||self.TextTrackCue,d=0;d<i.rows.length;d++)if(s=!0,o=0,l="",!(n=i.rows[d]).isEmpty()){for(var c,f=0;f<n.chars.length;f++)eo.test(n.chars[f].uchar)&&s?o++:(l+=n.chars[f].uchar,s=!1);n.cueStartTime=e,e===r&&(r+=1e-4),o>=16?o--:o++;var g=Pa(l.trim()),v=Ua(e,r,g);null!=t&&null!=(c=t.cues)&&c.getCueById(v)||((a=new h(e,r,g)).id=v,a.line=d+1,a.align="left",a.position=10+Math.min(80,10*Math.floor(8*o/32)),u.push(a))}return t&&u.length&&(u.sort((function(t,e){return"auto"===t.line||"auto"===e.line?0:t.line>8&&e.line>8?e.line-t.line:t.line-e.line})),u.forEach((function(e){return Ke(t,e)}))),u}},enableWebVTT:!0,enableIMSC1:!0,enableCEA708Captions:!0,captionsTextTrack1Label:"English",captionsTextTrack1LanguageCode:"en",captionsTextTrack2Label:"Spanish",captionsTextTrack2LanguageCode:"es",captionsTextTrack3Label:"Unknown CC",captionsTextTrack3LanguageCode:"",captionsTextTrack4Label:"Unknown CC",captionsTextTrack4LanguageCode:"",renderTextTracksNatively:!0}),{},{subtitleStreamController:Zn,subtitleTrackController:ea,timelineController:Ja,audioStreamController:Jn,audioTrackController:$n,emeController:is,cmcdController:Ys,contentSteeringController:Ws});function io(t){return t&&"object"==typeof t?Array.isArray(t)?t.map(io):Object.keys(t).reduce((function(e,r){return e[r]=io(t[r]),e}),{}):t}function no(t){var e=t.loader;e!==Js&&e!==zs?(w.log("[config]: Custom loader detected, cannot enable progressive streaming"),t.progressive=!1):function(){if(self.fetch&&self.AbortController&&self.ReadableStream&&self.Request)try{return new self.ReadableStream({}),!0}catch(t){}return!1}()&&(t.loader=Js,t.progressive=!0,t.enableSoftwareAES=!0,w.log("[config]: Progressive streaming enabled, using FetchLoader"))}var ao=function(t){function e(e,r){var i;return(i=t.call(this,e,"[level-controller]")||this)._levels=[],i._firstLevel=-1,i._maxAutoLevel=-1,i._startLevel=void 0,i.currentLevel=null,i.currentLevelIndex=-1,i.manualLevelIndex=-1,i.steering=void 0,i.onParsedComplete=void 0,i.steering=r,i._registerListeners(),i}l(e,t);var r=e.prototype;return r._registerListeners=function(){var t=this.hls;t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this),t.on(S.ERROR,this.onError,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_LOADED,this.onManifestLoaded,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this),t.off(S.ERROR,this.onError,this)},r.destroy=function(){this._unregisterListeners(),this.steering=null,this.resetLevels(),t.prototype.destroy.call(this)},r.stopLoad=function(){this._levels.forEach((function(t){t.loadError=0,t.fragmentError=0})),t.prototype.stopLoad.call(this)},r.resetLevels=function(){this._startLevel=void 0,this.manualLevelIndex=-1,this.currentLevelIndex=-1,this.currentLevel=null,this._levels=[],this._maxAutoLevel=-1},r.onManifestLoading=function(t,e){this.resetLevels()},r.onManifestLoaded=function(t,e){var r=this.hls.config.preferManagedMediaSource,i=[],n={},a={},s=!1,o=!1,l=!1;e.levels.forEach((function(t){var e,u,h=t.attrs,d=t.audioCodec,c=t.videoCodec;-1!==(null==(e=d)?void 0:e.indexOf("mp4a.40.34"))&&(Zs||(Zs=/chrome|firefox/i.test(navigator.userAgent)),Zs&&(t.audioCodec=d=void 0)),d&&(t.audioCodec=d=ve(d,r)),0===(null==(u=c)?void 0:u.indexOf("avc1"))&&(c=t.videoCodec=function(t){for(var e=t.split(","),r=0;r<e.length;r++){var i=e[r].split(".");if(i.length>2){var n=i.shift()+".";n+=parseInt(i.shift()).toString(16),n+=("000"+parseInt(i.shift()).toString(16)).slice(-4),e[r]=n}}return e.join(",")}(c));var f=t.width,g=t.height,v=t.unknownCodecs;if(s||(s=!(!f||!g)),o||(o=!!c),l||(l=!!d),!(null!=v&&v.length||d&&!le(d,"audio",r)||c&&!le(c,"video",r))){var m=h.CODECS,p=h["FRAME-RATE"],y=h["HDCP-LEVEL"],E=h["PATHWAY-ID"],T=h.RESOLUTION,S=h["VIDEO-RANGE"],L=(E||".")+"-"+t.bitrate+"-"+T+"-"+p+"-"+m+"-"+S+"-"+y;if(n[L])if(n[L].uri===t.url||t.attrs["PATHWAY-ID"])n[L].addGroupId("audio",h.AUDIO),n[L].addGroupId("text",h.SUBTITLES);else{var A=a[L]+=1;t.attrs["PATHWAY-ID"]=new Array(A+1).join(".");var R=new or(t);n[L]=R,i.push(R)}else{var b=new or(t);n[L]=b,a[L]=1,i.push(b)}}})),this.filterAndSortMediaOptions(i,e,s,o,l)},r.filterAndSortMediaOptions=function(t,e,r,i,n){var a=this,s=[],o=[],l=t;if((r||i)&&n&&(l=l.filter((function(t){var e,r=t.videoCodec,i=t.videoRange,n=t.width,a=t.height;return(!!r||!(!n||!a))&&!!(e=i)&&er.indexOf(e)>-1}))),0!==l.length){if(e.audioTracks){var u=this.hls.config.preferManagedMediaSource;so(s=e.audioTracks.filter((function(t){return!t.audioCodec||le(t.audioCodec,"audio",u)})))}e.subtitles&&so(o=e.subtitles);var h=l.slice(0);l.sort((function(t,e){if(t.attrs["HDCP-LEVEL"]!==e.attrs["HDCP-LEVEL"])return(t.attrs["HDCP-LEVEL"]||"")>(e.attrs["HDCP-LEVEL"]||"")?1:-1;if(r&&t.height!==e.height)return t.height-e.height;if(t.frameRate!==e.frameRate)return t.frameRate-e.frameRate;if(t.videoRange!==e.videoRange)return er.indexOf(t.videoRange)-er.indexOf(e.videoRange);if(t.videoCodec!==e.videoCodec){var i=de(t.videoCodec),n=de(e.videoCodec);if(i!==n)return n-i}if(t.uri===e.uri&&t.codecSet!==e.codecSet){var a=ce(t.codecSet),s=ce(e.codecSet);if(a!==s)return s-a}return t.averageBitrate!==e.averageBitrate?t.averageBitrate-e.averageBitrate:0}));var d=h[0];if(this.steering&&(l=this.steering.filterParsedLevels(l)).length!==h.length)for(var c=0;c<h.length;c++)if(h[c].pathwayId===l[0].pathwayId){d=h[c];break}this._levels=l;for(var f=0;f<l.length;f++)if(l[f]===d){var g;this._firstLevel=f;var v=d.bitrate,m=this.hls.bandwidthEstimate;if(this.log("manifest loaded, "+l.length+" level(s) found, first bitrate: "+v),void 0===(null==(g=this.hls.userConfig)?void 0:g.abrEwmaDefaultEstimate)){var p=Math.min(v,this.hls.config.abrEwmaDefaultEstimateMax);p>m&&m===ro.abrEwmaDefaultEstimate&&(this.hls.bandwidthEstimate=p)}break}var y=n&&!i,E={levels:l,audioTracks:s,subtitleTracks:o,sessionData:e.sessionData,sessionKeys:e.sessionKeys,firstLevel:this._firstLevel,stats:e.stats,audio:n,video:i,altAudio:!y&&s.some((function(t){return!!t.url}))};this.hls.trigger(S.MANIFEST_PARSED,E),(this.hls.config.autoStartLoad||this.hls.forceStartLoad)&&this.hls.startLoad(this.hls.config.startPosition)}else Promise.resolve().then((function(){if(a.hls){e.levels.length&&a.warn("One or more CODECS in variant not supported: "+JSON.stringify(e.levels[0].attrs));var t=new Error("no level with compatible codecs found in manifest");a.hls.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.MANIFEST_INCOMPATIBLE_CODECS_ERROR,fatal:!0,url:e.url,error:t,reason:t.message})}}))},r.onError=function(t,e){!e.fatal&&e.context&&e.context.type===_e&&e.context.level===this.level&&this.checkRetry(e)},r.onFragBuffered=function(t,e){var r=e.frag;if(void 0!==r&&r.type===Fe){var i=r.elementaryStreams;if(!Object.keys(i).some((function(t){return!!i[t]})))return;var n=this._levels[r.level];null!=n&&n.loadError&&(this.log("Resetting level error count of "+n.loadError+" on frag buffered"),n.loadError=0)}},r.onLevelLoaded=function(t,e){var r,i,n=e.level,a=e.details,s=this._levels[n];if(!s)return this.warn("Invalid level index "+n),void(null!=(i=e.deliveryDirectives)&&i.skip&&(a.deltaUpdateFailed=!0));n===this.currentLevelIndex?(0===s.fragmentError&&(s.loadError=0),this.playlistLoaded(n,e,s.details)):null!=(r=e.deliveryDirectives)&&r.skip&&(a.deltaUpdateFailed=!0)},r.loadPlaylist=function(e){t.prototype.loadPlaylist.call(this);var r=this.currentLevelIndex,i=this.currentLevel;if(i&&this.shouldLoadPlaylist(i)){var n=i.uri;if(e)try{n=e.addDirectives(n)}catch(t){this.warn("Could not construct new URL with HLS Delivery Directives: "+t)}var a=i.attrs["PATHWAY-ID"];this.log("Loading level index "+r+(void 0!==(null==e?void 0:e.msn)?" at sn "+e.msn+" part "+e.part:"")+" with"+(a?" Pathway "+a:"")+" "+n),this.clearTimer(),this.hls.trigger(S.LEVEL_LOADING,{url:n,level:r,pathwayId:i.attrs["PATHWAY-ID"],id:0,deliveryDirectives:e||null})}},r.removeLevel=function(t){var e,r=this,i=this._levels.filter((function(e,i){return i!==t||(r.steering&&r.steering.removeLevel(e),e===r.currentLevel&&(r.currentLevel=null,r.currentLevelIndex=-1,e.details&&e.details.fragments.forEach((function(t){return t.level=-1}))),!1)}));mr(i),this._levels=i,this.currentLevelIndex>-1&&null!=(e=this.currentLevel)&&e.details&&(this.currentLevelIndex=this.currentLevel.details.fragments[0].level),this.hls.trigger(S.LEVELS_UPDATED,{levels:i})},r.onLevelsUpdated=function(t,e){var r=e.levels;this._levels=r},r.checkMaxAutoUpdated=function(){var t=this.hls,e=t.autoLevelCapping,r=t.maxAutoLevel,i=t.maxHdcpLevel;this._maxAutoLevel!==r&&(this._maxAutoLevel=r,this.hls.trigger(S.MAX_AUTO_LEVEL_UPDATED,{autoLevelCapping:e,levels:this.levels,maxAutoLevel:r,minAutoLevel:this.hls.minAutoLevel,maxHdcpLevel:i}))},s(e,[{key:"levels",get:function(){return 0===this._levels.length?null:this._levels}},{key:"level",get:function(){return this.currentLevelIndex},set:function(t){var e=this._levels;if(0!==e.length){if(t<0||t>=e.length){var r=new Error("invalid level idx"),i=t<0;if(this.hls.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.LEVEL_SWITCH_ERROR,level:t,fatal:i,error:r,reason:r.message}),i)return;t=Math.min(t,e.length-1)}var n=this.currentLevelIndex,a=this.currentLevel,s=a?a.attrs["PATHWAY-ID"]:void 0,o=e[t],l=o.attrs["PATHWAY-ID"];if(this.currentLevelIndex=t,this.currentLevel=o,n!==t||!o.details||!a||s!==l){this.log("Switching to level "+t+" ("+(o.height?o.height+"p ":"")+(o.videoRange?o.videoRange+" ":"")+(o.codecSet?o.codecSet+" ":"")+"@"+o.bitrate+")"+(l?" with Pathway "+l:"")+" from level "+n+(s?" with Pathway "+s:""));var u={level:t,attrs:o.attrs,details:o.details,bitrate:o.bitrate,averageBitrate:o.averageBitrate,maxBitrate:o.maxBitrate,realBitrate:o.realBitrate,width:o.width,height:o.height,codecSet:o.codecSet,audioCodec:o.audioCodec,videoCodec:o.videoCodec,audioGroups:o.audioGroups,subtitleGroups:o.subtitleGroups,loaded:o.loaded,loadError:o.loadError,fragmentError:o.fragmentError,name:o.name,id:o.id,uri:o.uri,url:o.url,urlId:0,audioGroupIds:o.audioGroupIds,textGroupIds:o.textGroupIds};this.hls.trigger(S.LEVEL_SWITCHING,u);var h=o.details;if(!h||h.live){var d=this.switchParams(o.uri,null==a?void 0:a.details,h);this.loadPlaylist(d)}}}}},{key:"manualLevel",get:function(){return this.manualLevelIndex},set:function(t){this.manualLevelIndex=t,void 0===this._startLevel&&(this._startLevel=t),-1!==t&&(this.level=t)}},{key:"firstLevel",get:function(){return this._firstLevel},set:function(t){this._firstLevel=t}},{key:"startLevel",get:function(){if(void 0===this._startLevel){var t=this.hls.config.startLevel;return void 0!==t?t:this.hls.firstAutoLevel}return this._startLevel},set:function(t){this._startLevel=t}},{key:"nextLoadLevel",get:function(){return-1!==this.manualLevelIndex?this.manualLevelIndex:this.hls.nextAutoLevel},set:function(t){this.level=t,-1===this.manualLevelIndex&&(this.hls.nextAutoLevel=t)}}]),e}(Fr);function so(t){var e={};t.forEach((function(t){var r=t.groupId||"";t.id=e[r]=e[r]||0,e[r]++}))}var oo=function(){function t(t){this.config=void 0,this.keyUriToKeyInfo={},this.emeController=null,this.config=t}var e=t.prototype;return e.abort=function(t){for(var e in this.keyUriToKeyInfo){var r=this.keyUriToKeyInfo[e].loader;if(r){var i;if(t&&t!==(null==(i=r.context)?void 0:i.frag.type))return;r.abort()}}},e.detach=function(){for(var t in this.keyUriToKeyInfo){var e=this.keyUriToKeyInfo[t];(e.mediaKeySessionContext||e.decryptdata.isCommonEncryption)&&delete this.keyUriToKeyInfo[t]}},e.destroy=function(){for(var t in this.detach(),this.keyUriToKeyInfo){var e=this.keyUriToKeyInfo[t].loader;e&&e.destroy()}this.keyUriToKeyInfo={}},e.createKeyLoadError=function(t,e,r,i,n){return void 0===e&&(e=A.KEY_LOAD_ERROR),new fi({type:L.NETWORK_ERROR,details:e,fatal:!1,frag:t,response:n,error:r,networkDetails:i})},e.loadClear=function(t,e){var r=this;if(this.emeController&&this.config.emeEnabled)for(var i=t.sn,n=t.cc,a=function(){var t=e[s];if(n<=t.cc&&("initSegment"===i||"initSegment"===t.sn||i<t.sn))return r.emeController.selectKeySystemFormat(t).then((function(e){t.setKeyFormat(e)})),1},s=0;s<e.length&&!a();s++);},e.load=function(t){var e=this;return!t.decryptdata&&t.encrypted&&this.emeController?this.emeController.selectKeySystemFormat(t).then((function(r){return e.loadInternal(t,r)})):this.loadInternal(t)},e.loadInternal=function(t,e){var r,i;e&&t.setKeyFormat(e);var n=t.decryptdata;if(!n){var a=new Error(e?"Expected frag.decryptdata to be defined after setting format "+e:"Missing decryption data on fragment in onKeyLoading");return Promise.reject(this.createKeyLoadError(t,A.KEY_LOAD_ERROR,a))}var s=n.uri;if(!s)return Promise.reject(this.createKeyLoadError(t,A.KEY_LOAD_ERROR,new Error('Invalid key URI: "'+s+'"')));var o,l=this.keyUriToKeyInfo[s];if(null!=(r=l)&&r.decryptdata.key)return n.key=l.decryptdata.key,Promise.resolve({frag:t,keyInfo:l});if(null!=(i=l)&&i.keyLoadPromise)switch(null==(o=l.mediaKeySessionContext)?void 0:o.keyStatus){case void 0:case"status-pending":case"usable":case"usable-in-future":return l.keyLoadPromise.then((function(e){return n.key=e.keyInfo.decryptdata.key,{frag:t,keyInfo:l}}))}switch(l=this.keyUriToKeyInfo[s]={decryptdata:n,keyLoadPromise:null,loader:null,mediaKeySessionContext:null},n.method){case"ISO-23001-7":case"SAMPLE-AES":case"SAMPLE-AES-CENC":case"SAMPLE-AES-CTR":return"identity"===n.keyFormat?this.loadKeyHTTP(l,t):this.loadKeyEME(l,t);case"AES-128":return this.loadKeyHTTP(l,t);default:return Promise.reject(this.createKeyLoadError(t,A.KEY_LOAD_ERROR,new Error('Key supplied with unsupported METHOD: "'+n.method+'"')))}},e.loadKeyEME=function(t,e){var r={frag:e,keyInfo:t};if(this.emeController&&this.config.emeEnabled){var i=this.emeController.loadKey(r);if(i)return(t.keyLoadPromise=i.then((function(e){return t.mediaKeySessionContext=e,r}))).catch((function(e){throw t.keyLoadPromise=null,e}))}return Promise.resolve(r)},e.loadKeyHTTP=function(t,e){var r=this,n=this.config,a=new(0,n.loader)(n);return e.keyLoader=t.loader=a,t.keyLoadPromise=new Promise((function(s,o){var l={keyInfo:t,frag:e,responseType:"arraybuffer",url:t.decryptdata.uri},u=n.keyLoadPolicy.default,h={loadPolicy:u,timeout:u.maxLoadTimeMs,maxRetry:0,retryDelay:0,maxRetryDelay:0},d={onSuccess:function(t,e,i,n){var a=i.frag,l=i.keyInfo,u=i.url;if(!a.decryptdata||l!==r.keyUriToKeyInfo[u])return o(r.createKeyLoadError(a,A.KEY_LOAD_ERROR,new Error("after key load, decryptdata unset or changed"),n));l.decryptdata.key=a.decryptdata.key=new Uint8Array(t.data),a.keyLoader=null,l.loader=null,s({frag:a,keyInfo:l})},onError:function(t,n,a,s){r.resetLoader(n),o(r.createKeyLoadError(e,A.KEY_LOAD_ERROR,new Error("HTTP Error "+t.code+" loading key "+t.text),a,i({url:l.url,data:void 0},t)))},onTimeout:function(t,i,n){r.resetLoader(i),o(r.createKeyLoadError(e,A.KEY_LOAD_TIMEOUT,new Error("key loading timed out"),n))},onAbort:function(t,i,n){r.resetLoader(i),o(r.createKeyLoadError(e,A.INTERNAL_ABORTED,new Error("key loading aborted"),n))}};a.load(l,h,d)}))},e.resetLoader=function(t){var e=t.frag,r=t.keyInfo,i=t.url,n=r.loader;e.keyLoader===n&&(e.keyLoader=null,r.loader=null),delete this.keyUriToKeyInfo[i],n&&n.destroy()},t}();function lo(){return self.SourceBuffer||self.WebKitSourceBuffer}function uo(){if(!se())return!1;var t=lo();return!t||t.prototype&&"function"==typeof t.prototype.appendBuffer&&"function"==typeof t.prototype.remove}var ho=function(){function t(t,e,r,i){this.config=void 0,this.media=null,this.fragmentTracker=void 0,this.hls=void 0,this.nudgeRetry=0,this.stallReported=!1,this.stalled=null,this.moved=!1,this.seeking=!1,this.config=t,this.media=e,this.fragmentTracker=r,this.hls=i}var e=t.prototype;return e.destroy=function(){this.media=null,this.hls=this.fragmentTracker=null},e.poll=function(t,e){var r=this.config,i=this.media,n=this.stalled;if(null!==i){var a=i.currentTime,s=i.seeking,o=this.seeking&&!s,l=!this.seeking&&s;if(this.seeking=s,a===t)if(l||o)this.stalled=null;else if(i.paused&&!s||i.ended||0===i.playbackRate||!ri.getBuffered(i).length)this.nudgeRetry=0;else{var u=ri.bufferInfo(i,a,0),h=u.nextStart||0;if(s){var d=u.len>2,c=!h||e&&e.start<=a||h-a>2&&!this.fragmentTracker.getPartialFragment(a);if(d||c)return;this.moved=!1}if(!this.moved&&null!==this.stalled){var f;if(!(u.len>0||h))return;var g=Math.max(h,u.start||0)-a,v=this.hls.levels?this.hls.levels[this.hls.currentLevel]:null,m=(null==v||null==(f=v.details)?void 0:f.live)?2*v.details.targetduration:2,p=this.fragmentTracker.getPartialFragment(a);if(g>0&&(g<=m||p))return void(i.paused||this._trySkipBufferHole(p))}var y=self.performance.now();if(null!==n){var E=y-n;if(s||!(E>=250)||(this._reportStall(u),this.media)){var T=ri.bufferInfo(i,a,r.maxBufferHole);this._tryFixBufferStall(T,E)}}else this.stalled=y}else if(this.moved=!0,s||(this.nudgeRetry=0),null!==n){if(this.stallReported){var S=self.performance.now()-n;w.warn("playback not stuck anymore @"+a+", after "+Math.round(S)+"ms"),this.stallReported=!1}this.stalled=null}}},e._tryFixBufferStall=function(t,e){var r=this.config,i=this.fragmentTracker,n=this.media;if(null!==n){var a=n.currentTime,s=i.getPartialFragment(a);if(s&&(this._trySkipBufferHole(s)||!this.media))return;(t.len>r.maxBufferHole||t.nextStart&&t.nextStart-a<r.maxBufferHole)&&e>1e3*r.highBufferWatchdogPeriod&&(w.warn("Trying to nudge playhead over buffer-hole"),this.stalled=null,this._tryNudgeBuffer())}},e._reportStall=function(t){var e=this.hls,r=this.media;if(!this.stallReported&&r){this.stallReported=!0;var i=new Error("Playback stalling at @"+r.currentTime+" due to low buffer ("+JSON.stringify(t)+")");w.warn(i.message),e.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_STALLED_ERROR,fatal:!1,error:i,buffer:t.len})}},e._trySkipBufferHole=function(t){var e=this.config,r=this.hls,i=this.media;if(null===i)return 0;var n=i.currentTime,a=ri.bufferInfo(i,n,0),s=n<a.start?a.start:a.nextStart;if(s){var o=a.len<=e.maxBufferHole,l=a.len>0&&a.len<1&&i.readyState<3,u=s-n;if(u>0&&(o||l)){if(u>e.maxBufferHole){var h=this.fragmentTracker,d=!1;if(0===n){var c=h.getAppendedFrag(0,Fe);c&&s<c.end&&(d=!0)}if(!d){var f=t||h.getAppendedFrag(n,Fe);if(f){for(var g=!1,v=f.end;v<s;){var m=h.getPartialFragment(v);if(!m){g=!0;break}v+=m.duration}if(g)return 0}}}var p=Math.max(s+.05,n+.1);if(w.warn("skipping hole, adjusting currentTime from "+n+" to "+p),this.moved=!0,this.stalled=null,i.currentTime=p,t&&!t.gap){var y=new Error("fragment loaded with buffer holes, seeking from "+n+" to "+p);r.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_SEEK_OVER_HOLE,fatal:!1,error:y,reason:y.message,frag:t})}return p}}return 0},e._tryNudgeBuffer=function(){var t=this.config,e=this.hls,r=this.media,i=this.nudgeRetry;if(null!==r){var n=r.currentTime;if(this.nudgeRetry++,i<t.nudgeMaxRetry){var a=n+(i+1)*t.nudgeOffset,s=new Error("Nudging 'currentTime' from "+n+" to "+a);w.warn(s.message),r.currentTime=a,e.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_NUDGE_ON_STALL,error:s,fatal:!1})}else{var o=new Error("Playhead still not moving while enough data buffered @"+n+" after "+t.nudgeMaxRetry+" nudges");w.error(o.message),e.trigger(S.ERROR,{type:L.MEDIA_ERROR,details:A.BUFFER_STALLED_ERROR,error:o,fatal:!0})}}},t}(),co=function(t){function e(e,r,i){var n;return(n=t.call(this,e,r,i,"[stream-controller]",Fe)||this).audioCodecSwap=!1,n.gapController=null,n.level=-1,n._forceStartLoad=!1,n.altAudio=!1,n.audioOnly=!1,n.fragPlaying=null,n.onvplaying=null,n.onvseeked=null,n.fragLastKbps=0,n.couldBacktrack=!1,n.backtrackFragment=null,n.audioCodecSwitch=!1,n.videoBuffer=null,n._registerListeners(),n}l(e,t);var r=e.prototype;return r._registerListeners=function(){var t=this.hls;t.on(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.on(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.on(S.MANIFEST_LOADING,this.onManifestLoading,this),t.on(S.MANIFEST_PARSED,this.onManifestParsed,this),t.on(S.LEVEL_LOADING,this.onLevelLoading,this),t.on(S.LEVEL_LOADED,this.onLevelLoaded,this),t.on(S.FRAG_LOAD_EMERGENCY_ABORTED,this.onFragLoadEmergencyAborted,this),t.on(S.ERROR,this.onError,this),t.on(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.on(S.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),t.on(S.BUFFER_CREATED,this.onBufferCreated,this),t.on(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.on(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.on(S.FRAG_BUFFERED,this.onFragBuffered,this)},r._unregisterListeners=function(){var t=this.hls;t.off(S.MEDIA_ATTACHED,this.onMediaAttached,this),t.off(S.MEDIA_DETACHING,this.onMediaDetaching,this),t.off(S.MANIFEST_LOADING,this.onManifestLoading,this),t.off(S.MANIFEST_PARSED,this.onManifestParsed,this),t.off(S.LEVEL_LOADED,this.onLevelLoaded,this),t.off(S.FRAG_LOAD_EMERGENCY_ABORTED,this.onFragLoadEmergencyAborted,this),t.off(S.ERROR,this.onError,this),t.off(S.AUDIO_TRACK_SWITCHING,this.onAudioTrackSwitching,this),t.off(S.AUDIO_TRACK_SWITCHED,this.onAudioTrackSwitched,this),t.off(S.BUFFER_CREATED,this.onBufferCreated,this),t.off(S.BUFFER_FLUSHED,this.onBufferFlushed,this),t.off(S.LEVELS_UPDATED,this.onLevelsUpdated,this),t.off(S.FRAG_BUFFERED,this.onFragBuffered,this)},r.onHandlerDestroying=function(){this._unregisterListeners(),t.prototype.onHandlerDestroying.call(this)},r.startLoad=function(t){if(this.levels){var e=this.lastCurrentTime,r=this.hls;if(this.stopLoad(),this.setInterval(100),this.level=-1,!this.startFragRequested){var i=r.startLevel;-1===i&&(r.config.testBandwidth&&this.levels.length>1?(i=0,this.bitrateTest=!0):i=r.firstAutoLevel),r.nextLoadLevel=i,this.level=r.loadLevel,this.loadedmetadata=!1}e>0&&-1===t&&(this.log("Override startPosition with lastCurrentTime @"+e.toFixed(3)),t=e),this.state=Ti,this.nextLoadPosition=this.startPosition=this.lastCurrentTime=t,this.tick()}else this._forceStartLoad=!0,this.state=Ei},r.stopLoad=function(){this._forceStartLoad=!1,t.prototype.stopLoad.call(this)},r.doTick=function(){switch(this.state){case Ci:var t=this.levels,e=this.level,r=null==t?void 0:t[e],i=null==r?void 0:r.details;if(i&&(!i.live||this.levelLastLoaded===r)){if(this.waitForCdnTuneIn(i))break;this.state=Ti;break}if(this.hls.nextLoadLevel!==this.level){this.state=Ti;break}break;case Ai:var n,a=self.performance.now(),s=this.retryDate;if(!s||a>=s||null!=(n=this.media)&&n.seeking){var o=this.levels,l=this.level,u=null==o?void 0:o[l];this.resetStartWhenNotLoaded(u||null),this.state=Ti}}this.state===Ti&&this.doTickIdle(),this.onTickEnd()},r.onTickEnd=function(){t.prototype.onTickEnd.call(this),this.checkBuffer(),this.checkFragmentChanged()},r.doTickIdle=function(){var t=this.hls,e=this.levelLastLoaded,r=this.levels,i=this.media;if(null!==e&&(i||!this.startFragRequested&&t.config.startFragPrefetch)&&(!this.altAudio||!this.audioOnly)){var n=t.nextLoadLevel;if(null!=r&&r[n]){var a=r[n],s=this.getMainFwdBufferInfo();if(null!==s){var o=this.getLevelDetails();if(o&&this._streamEnded(s,o)){var l={};return this.altAudio&&(l.type="video"),this.hls.trigger(S.BUFFER_EOS,l),void(this.state=Di)}t.loadLevel!==n&&-1===t.manualLevel&&this.log("Adapting to level "+n+" from level "+this.level),this.level=t.nextLoadLevel=n;var u=a.details;if(!u||this.state===Ci||u.live&&this.levelLastLoaded!==a)return this.level=n,void(this.state=Ci);var h=s.len,d=this.getMaxBufferLength(a.maxBitrate);if(!(h>=d)){this.backtrackFragment&&this.backtrackFragment.start>s.end&&(this.backtrackFragment=null);var c=this.backtrackFragment?this.backtrackFragment.start:s.end,f=this.getNextFragment(c,u);if(this.couldBacktrack&&!this.fragPrevious&&f&&"initSegment"!==f.sn&&this.fragmentTracker.getState(f)!==Jr){var g,v=(null!=(g=this.backtrackFragment)?g:f).sn-u.startSN,m=u.fragments[v-1];m&&f.cc===m.cc&&(f=m,this.fragmentTracker.removeFragment(m))}else this.backtrackFragment&&s.len&&(this.backtrackFragment=null);if(f&&this.isLoopLoading(f,c)){if(!f.gap){var p=this.audioOnly&&!this.altAudio?O:N,y=(p===N?this.videoBuffer:this.mediaBuffer)||this.media;y&&this.afterBufferFlushed(y,p,Fe)}f=this.getNextFragmentLoopLoading(f,u,s,Fe,d)}f&&(!f.initSegment||f.initSegment.data||this.bitrateTest||(f=f.initSegment),this.loadFragment(f,a,c))}}}}},r.loadFragment=function(e,r,i){var n=this.fragmentTracker.getState(e);this.fragCurrent=e,n===Xr||n===Qr?"initSegment"===e.sn?this._loadInitSegment(e,r):this.bitrateTest?(this.log("Fragment "+e.sn+" of level "+e.level+" is being downloaded to test bitrate and will not be buffered"),this._loadBitrateTestFrag(e,r)):(this.startFragRequested=!0,t.prototype.loadFragment.call(this,e,r,i)):this.clearTrackerIfNeeded(e)},r.getBufferedFrag=function(t){return this.fragmentTracker.getBufferedFrag(t,Fe)},r.followingBufferedFrag=function(t){return t?this.getBufferedFrag(t.end+.5):null},r.immediateLevelSwitch=function(){this.abortCurrentFrag(),this.flushMainBuffer(0,Number.POSITIVE_INFINITY)},r.nextLevelSwitch=function(){var t=this.levels,e=this.media;if(null!=e&&e.readyState){var r,i=this.getAppendedFrag(e.currentTime);i&&i.start>1&&this.flushMainBuffer(0,i.start-1);var n=this.getLevelDetails();if(null!=n&&n.live){var a=this.getMainFwdBufferInfo();if(!a||a.len<2*n.targetduration)return}if(!e.paused&&t){var s=t[this.hls.nextLoadLevel],o=this.fragLastKbps;r=o&&this.fragCurrent?this.fragCurrent.duration*s.maxBitrate/(1e3*o)+1:0}else r=0;var l=this.getBufferedFrag(e.currentTime+r);if(l){var u=this.followingBufferedFrag(l);if(u){this.abortCurrentFrag();var h=u.maxStartPTS?u.maxStartPTS:u.start,d=u.duration,c=Math.max(l.end,h+Math.min(Math.max(d-this.config.maxFragLookUpTolerance,d*(this.couldBacktrack?.5:.125)),d*(this.couldBacktrack?.75:.25)));this.flushMainBuffer(c,Number.POSITIVE_INFINITY)}}}},r.abortCurrentFrag=function(){var t=this.fragCurrent;switch(this.fragCurrent=null,this.backtrackFragment=null,t&&(t.abortRequests(),this.fragmentTracker.removeFragment(t)),this.state){case Si:case Li:case Ai:case bi:case ki:this.state=Ti}this.nextLoadPosition=this.getLoadPosition()},r.flushMainBuffer=function(e,r){t.prototype.flushMainBuffer.call(this,e,r,this.altAudio?"video":null)},r.onMediaAttached=function(e,r){t.prototype.onMediaAttached.call(this,e,r);var i=r.media;this.onvplaying=this.onMediaPlaying.bind(this),this.onvseeked=this.onMediaSeeked.bind(this),i.addEventListener("playing",this.onvplaying),i.addEventListener("seeked",this.onvseeked),this.gapController=new ho(this.config,i,this.fragmentTracker,this.hls)},r.onMediaDetaching=function(){var e=this.media;e&&this.onvplaying&&this.onvseeked&&(e.removeEventListener("playing",this.onvplaying),e.removeEventListener("seeked",this.onvseeked),this.onvplaying=this.onvseeked=null,this.videoBuffer=null),this.fragPlaying=null,this.gapController&&(this.gapController.destroy(),this.gapController=null),t.prototype.onMediaDetaching.call(this)},r.onMediaPlaying=function(){this.tick()},r.onMediaSeeked=function(){var t=this.media,e=t?t.currentTime:null;y(e)&&this.log("Media seeked to "+e.toFixed(3));var r=this.getMainFwdBufferInfo();null!==r&&0!==r.len?this.tick():this.warn('Main forward buffer length on "seeked" event '+(r?r.len:"empty")+")")},r.onManifestLoading=function(){this.log("Trigger BUFFER_RESET"),this.hls.trigger(S.BUFFER_RESET,void 0),this.fragmentTracker.removeAllFragments(),this.couldBacktrack=!1,this.startPosition=this.lastCurrentTime=this.fragLastKbps=0,this.levels=this.fragPlaying=this.backtrackFragment=this.levelLastLoaded=null,this.altAudio=this.audioOnly=this.startFragRequested=!1},r.onManifestParsed=function(t,e){var r,i,n=!1,a=!1;e.levels.forEach((function(t){var e=t.audioCodec;e&&(n=n||-1!==e.indexOf("mp4a.40.2"),a=a||-1!==e.indexOf("mp4a.40.5"))})),this.audioCodecSwitch=n&&a&&!("function"==typeof(null==(i=lo())||null==(r=i.prototype)?void 0:r.changeType)),this.audioCodecSwitch&&this.log("Both AAC/HE-AAC audio found in levels; declaring level codec as HE-AAC"),this.levels=e.levels,this.startFragRequested=!1},r.onLevelLoading=function(t,e){var r=this.levels;if(r&&this.state===Ti){var i=r[e.level];(!i.details||i.details.live&&this.levelLastLoaded!==i||this.waitForCdnTuneIn(i.details))&&(this.state=Ci)}},r.onLevelLoaded=function(t,e){var r,i=this.levels,n=e.level,a=e.details,s=a.totalduration;if(i){this.log("Level "+n+" loaded ["+a.startSN+","+a.endSN+"]"+(a.lastPartSn?"[part-"+a.lastPartSn+"-"+a.lastPartIndex+"]":"")+", cc ["+a.startCC+", "+a.endCC+"] duration:"+s);var o=i[n],l=this.fragCurrent;!l||this.state!==Li&&this.state!==Ai||l.level!==e.level&&l.loader&&this.abortCurrentFrag();var u=0;if(a.live||null!=(r=o.details)&&r.live){var h;if(this.checkLiveUpdate(a),a.deltaUpdateFailed)return;u=this.alignPlaylists(a,o.details,null==(h=this.levelLastLoaded)?void 0:h.details)}if(o.details=a,this.levelLastLoaded=o,this.hls.trigger(S.LEVEL_UPDATED,{details:a,level:n}),this.state===Ci){if(this.waitForCdnTuneIn(a))return;this.state=Ti}this.startFragRequested?a.live&&this.synchronizeToLiveEdge(a):this.setStartPosition(a,u),this.tick()}else this.warn("Levels were reset while loading level "+n)},r._handleFragmentLoadProgress=function(t){var e,r=t.frag,i=t.part,n=t.payload,a=this.levels;if(a){var s=a[r.level],o=s.details;if(!o)return this.warn("Dropping fragment "+r.sn+" of level "+r.level+" after level details were reset"),void this.fragmentTracker.removeFragment(r);var l=s.videoCodec,u=o.PTSKnown||!o.live,h=null==(e=r.initSegment)?void 0:e.data,d=this._getAudioCodec(s),c=this.transmuxer=this.transmuxer||new qn(this.hls,Fe,this._handleTransmuxComplete.bind(this),this._handleTransmuxerFlush.bind(this)),f=i?i.index:-1,g=-1!==f,v=new ii(r.level,r.sn,r.stats.chunkCount,n.byteLength,f,g),m=this.initPTS[r.cc];c.push(n,h,d,l,r,i,o.totalduration,u,v,m)}else this.warn("Levels were reset while fragment load was in progress. Fragment "+r.sn+" of level "+r.level+" will not be buffered")},r.onAudioTrackSwitching=function(t,e){var r=this.altAudio;if(!e.url){if(this.mediaBuffer!==this.media){this.log("Switching on main audio, use media.buffered to schedule main fragment loading"),this.mediaBuffer=this.media;var i=this.fragCurrent;i&&(this.log("Switching to main audio track, cancel main fragment load"),i.abortRequests(),this.fragmentTracker.removeFragment(i)),this.resetTransmuxer(),this.resetLoadingState()}else this.audioOnly&&this.resetTransmuxer();var n=this.hls;r&&(n.trigger(S.BUFFER_FLUSHING,{startOffset:0,endOffset:Number.POSITIVE_INFINITY,type:null}),this.fragmentTracker.removeAllFragments()),n.trigger(S.AUDIO_TRACK_SWITCHED,e)}},r.onAudioTrackSwitched=function(t,e){var r=e.id,i=!!this.hls.audioTracks[r].url;if(i){var n=this.videoBuffer;n&&this.mediaBuffer!==n&&(this.log("Switching on alternate audio, use video.buffered to schedule main fragment loading"),this.mediaBuffer=n)}this.altAudio=i,this.tick()},r.onBufferCreated=function(t,e){var r,i,n=e.tracks,a=!1;for(var s in n){var o=n[s];if("main"===o.id){if(i=s,r=o,"video"===s){var l=n[s];l&&(this.videoBuffer=l.buffer)}}else a=!0}a&&r?(this.log("Alternate track found, use "+i+".buffered to schedule main fragment loading"),this.mediaBuffer=r.buffer):this.mediaBuffer=this.media},r.onFragBuffered=function(t,e){var r=e.frag,i=e.part;if(!r||r.type===Fe){if(this.fragContextChanged(r))return this.warn("Fragment "+r.sn+(i?" p: "+i.index:"")+" of level "+r.level+" finished buffering, but was aborted. state: "+this.state),void(this.state===ki&&(this.state=Ti));var n=i?i.stats:r.stats;this.fragLastKbps=Math.round(8*n.total/(n.buffering.end-n.loading.first)),"initSegment"!==r.sn&&(this.fragPrevious=r),this.fragBufferedComplete(r,i)}},r.onError=function(t,e){var r;if(e.fatal)this.state=Ii;else switch(e.details){case A.FRAG_GAP:case A.FRAG_PARSING_ERROR:case A.FRAG_DECRYPT_ERROR:case A.FRAG_LOAD_ERROR:case A.FRAG_LOAD_TIMEOUT:case A.KEY_LOAD_ERROR:case A.KEY_LOAD_TIMEOUT:this.onFragmentOrKeyLoadError(Fe,e);break;case A.LEVEL_LOAD_ERROR:case A.LEVEL_LOAD_TIMEOUT:case A.LEVEL_PARSING_ERROR:e.levelRetry||this.state!==Ci||(null==(r=e.context)?void 0:r.type)!==_e||(this.state=Ti);break;case A.BUFFER_APPEND_ERROR:case A.BUFFER_FULL_ERROR:if(!e.parent||"main"!==e.parent)return;if(e.details===A.BUFFER_APPEND_ERROR)return void this.resetLoadingState();this.reduceLengthAndFlushBuffer(e)&&this.flushMainBuffer(0,Number.POSITIVE_INFINITY);break;case A.INTERNAL_EXCEPTION:this.recoverWorkerError(e)}},r.checkBuffer=function(){var t=this.media,e=this.gapController;if(t&&e&&t.readyState){if(this.loadedmetadata||!ri.getBuffered(t).length){var r=this.state!==Ti?this.fragCurrent:null;e.poll(this.lastCurrentTime,r)}this.lastCurrentTime=t.currentTime}},r.onFragLoadEmergencyAborted=function(){this.state=Ti,this.loadedmetadata||(this.startFragRequested=!1,this.nextLoadPosition=this.startPosition),this.tickImmediate()},r.onBufferFlushed=function(t,e){var r=e.type;if(r!==O||this.audioOnly&&!this.altAudio){var i=(r===N?this.videoBuffer:this.mediaBuffer)||this.media;this.afterBufferFlushed(i,r,Fe),this.tick()}},r.onLevelsUpdated=function(t,e){this.level>-1&&this.fragCurrent&&(this.level=this.fragCurrent.level),this.levels=e.levels},r.swapAudioCodec=function(){this.audioCodecSwap=!this.audioCodecSwap},r.seekToStartPos=function(){var t=this.media;if(t){var e=t.currentTime,r=this.startPosition;if(r>=0&&e<r){if(t.seeking)return void this.log("could not seek to "+r+", already seeking at "+e);var i=ri.getBuffered(t),n=(i.length?i.start(0):0)-r;n>0&&(n<this.config.maxBufferHole||n<this.config.maxFragLookUpTolerance)&&(this.log("adjusting start position by "+n+" to match buffer start"),r+=n,this.startPosition=r),this.log("seek to target start position "+r+" from current time "+e),t.currentTime=r}}},r._getAudioCodec=function(t){var e=this.config.defaultAudioCodec||t.audioCodec;return this.audioCodecSwap&&e&&(this.log("Swapping audio codec"),e=-1!==e.indexOf("mp4a.40.5")?"mp4a.40.2":"mp4a.40.5"),e},r._loadBitrateTestFrag=function(t,e){var r=this;t.bitrateTest=!0,this._doFragLoad(t,e).then((function(i){var n=r.hls;if(i&&!r.fragContextChanged(t)){e.fragmentError=0,r.state=Ti,r.startFragRequested=!1,r.bitrateTest=!1;var a=t.stats;a.parsing.start=a.parsing.end=a.buffering.start=a.buffering.end=self.performance.now(),n.trigger(S.FRAG_LOADED,i),t.bitrateTest=!1}}))},r._handleTransmuxComplete=function(t){var e,r="main",i=this.hls,n=t.remuxResult,a=t.chunkMeta,s=this.getCurrentContext(a);if(s){var o=s.frag,l=s.part,u=s.level,h=n.video,d=n.text,c=n.id3,f=n.initSegment,g=u.details,v=this.altAudio?void 0:n.audio;if(this.fragContextChanged(o))this.fragmentTracker.removeFragment(o);else{if(this.state=bi,f){if(null!=f&&f.tracks){var m=o.initSegment||o;this._bufferInitSegment(u,f.tracks,m,a),i.trigger(S.FRAG_PARSING_INIT_SEGMENT,{frag:m,id:r,tracks:f.tracks})}var p=f.initPTS,E=f.timescale;y(p)&&(this.initPTS[o.cc]={baseTime:p,timescale:E},i.trigger(S.INIT_PTS_FOUND,{frag:o,id:r,initPTS:p,timescale:E}))}if(h&&g&&"initSegment"!==o.sn){var T=g.fragments[o.sn-1-g.startSN],L=o.sn===g.startSN,A=!T||o.cc>T.cc;if(!1!==n.independent){var R=h.startPTS,b=h.endPTS,k=h.startDTS,D=h.endDTS;if(l)l.elementaryStreams[h.type]={startPTS:R,endPTS:b,startDTS:k,endDTS:D};else if(h.firstKeyFrame&&h.independent&&1===a.id&&!A&&(this.couldBacktrack=!0),h.dropped&&h.independent){var I=this.getMainFwdBufferInfo(),w=(I?I.end:this.getLoadPosition())+this.config.maxBufferHole,C=h.firstKeyFramePTS?h.firstKeyFramePTS:R;if(!L&&w<C-this.config.maxBufferHole&&!A)return void this.backtrack(o);A&&(o.gap=!0),o.setElementaryStreamInfo(h.type,o.start,b,o.start,D,!0)}else L&&R>2&&(o.gap=!0);o.setElementaryStreamInfo(h.type,R,b,k,D),this.backtrackFragment&&(this.backtrackFragment=o),this.bufferFragmentData(h,o,l,a,L||A)}else{if(!L&&!A)return void this.backtrack(o);o.gap=!0}}if(v){var _=v.startPTS,x=v.endPTS,P=v.startDTS,F=v.endDTS;l&&(l.elementaryStreams[O]={startPTS:_,endPTS:x,startDTS:P,endDTS:F}),o.setElementaryStreamInfo(O,_,x,P,F),this.bufferFragmentData(v,o,l,a)}if(g&&null!=c&&null!=(e=c.samples)&&e.length){var M={id:r,frag:o,details:g,samples:c.samples};i.trigger(S.FRAG_PARSING_METADATA,M)}if(g&&d){var N={id:r,frag:o,details:g,samples:d.samples};i.trigger(S.FRAG_PARSING_USERDATA,N)}}}else this.resetWhenMissingContext(a)},r._bufferInitSegment=function(t,e,r,i){var n=this;if(this.state===bi){this.audioOnly=!!e.audio&&!e.video,this.altAudio&&!this.audioOnly&&delete e.audio;var a=e.audio,s=e.video,o=e.audiovideo;if(a){var l=t.audioCodec,u=navigator.userAgent.toLowerCase();if(this.audioCodecSwitch){l&&(l=-1!==l.indexOf("mp4a.40.5")?"mp4a.40.2":"mp4a.40.5");var h=a.metadata;h&&"channelCount"in h&&1!==(h.channelCount||1)&&-1===u.indexOf("firefox")&&(l="mp4a.40.5")}l&&-1!==l.indexOf("mp4a.40.5")&&-1!==u.indexOf("android")&&"audio/mpeg"!==a.container&&(l="mp4a.40.2",this.log("Android: force audio codec to "+l)),t.audioCodec&&t.audioCodec!==l&&this.log('Swapping manifest audio codec "'+t.audioCodec+'" for "'+l+'"'),a.levelCodec=l,a.id="main",this.log("Init audio buffer, container:"+a.container+", codecs[selected/level/parsed]=["+(l||"")+"/"+(t.audioCodec||"")+"/"+a.codec+"]")}s&&(s.levelCodec=t.videoCodec,s.id="main",this.log("Init video buffer, container:"+s.container+", codecs[level/parsed]=["+(t.videoCodec||"")+"/"+s.codec+"]")),o&&this.log("Init audiovideo buffer, container:"+o.container+", codecs[level/parsed]=["+t.codecs+"/"+o.codec+"]"),this.hls.trigger(S.BUFFER_CODECS,e),Object.keys(e).forEach((function(t){var a=e[t].initSegment;null!=a&&a.byteLength&&n.hls.trigger(S.BUFFER_APPENDING,{type:t,data:a,frag:r,part:null,chunkMeta:i,parent:r.type})})),this.tickImmediate()}},r.getMainFwdBufferInfo=function(){return this.getFwdBufferInfo(this.mediaBuffer?this.mediaBuffer:this.media,Fe)},r.backtrack=function(t){this.couldBacktrack=!0,this.backtrackFragment=t,this.resetTransmuxer(),this.flushBufferGap(t),this.fragmentTracker.removeFragment(t),this.fragPrevious=null,this.nextLoadPosition=t.start,this.state=Ti},r.checkFragmentChanged=function(){var t=this.media,e=null;if(t&&t.readyState>1&&!1===t.seeking){var r=t.currentTime;if(ri.isBuffered(t,r)?e=this.getAppendedFrag(r):ri.isBuffered(t,r+.1)&&(e=this.getAppendedFrag(r+.1)),e){this.backtrackFragment=null;var i=this.fragPlaying,n=e.level;i&&e.sn===i.sn&&i.level===n||(this.fragPlaying=e,this.hls.trigger(S.FRAG_CHANGED,{frag:e}),i&&i.level===n||this.hls.trigger(S.LEVEL_SWITCHED,{level:n}))}}},s(e,[{key:"nextLevel",get:function(){var t=this.nextBufferedFrag;return t?t.level:-1}},{key:"currentFrag",get:function(){var t=this.media;return t?this.fragPlaying||this.getAppendedFrag(t.currentTime):null}},{key:"currentProgramDateTime",get:function(){var t=this.media;if(t){var e=t.currentTime,r=this.currentFrag;if(r&&y(e)&&y(r.programDateTime)){var i=r.programDateTime+1e3*(e-r.start);return new Date(i)}}return null}},{key:"currentLevel",get:function(){var t=this.currentFrag;return t?t.level:-1}},{key:"nextBufferedFrag",get:function(){var t=this.currentFrag;return t?this.followingBufferedFrag(t):null}},{key:"forceStartLoad",get:function(){return this._forceStartLoad}}]),e}(_i),fo=function(){function t(e){void 0===e&&(e={}),this.config=void 0,this.userConfig=void 0,this.coreComponents=void 0,this.networkControllers=void 0,this.started=!1,this._emitter=new Vn,this._autoLevelCapping=-1,this._maxHdcpLevel=null,this.abrController=void 0,this.bufferController=void 0,this.capLevelController=void 0,this.latencyController=void 0,this.levelController=void 0,this.streamController=void 0,this.audioTrackController=void 0,this.subtitleTrackController=void 0,this.emeController=void 0,this.cmcdController=void 0,this._media=null,this.url=null,this.triggeringException=void 0,I(e.debug||!1,"Hls instance");var r=this.config=function(t,e){if((e.liveSyncDurationCount||e.liveMaxLatencyDurationCount)&&(e.liveSyncDuration||e.liveMaxLatencyDuration))throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");if(void 0!==e.liveMaxLatencyDurationCount&&(void 0===e.liveSyncDurationCount||e.liveMaxLatencyDurationCount<=e.liveSyncDurationCount))throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"');if(void 0!==e.liveMaxLatencyDuration&&(void 0===e.liveSyncDuration||e.liveMaxLatencyDuration<=e.liveSyncDuration))throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"');var r=io(t),n=["TimeOut","MaxRetry","RetryDelay","MaxRetryTimeout"];return["manifest","level","frag"].forEach((function(t){var i=("level"===t?"playlist":t)+"LoadPolicy",a=void 0===e[i],s=[];n.forEach((function(n){var o=t+"Loading"+n,l=e[o];if(void 0!==l&&a){s.push(o);var u=r[i].default;switch(e[i]={default:u},n){case"TimeOut":u.maxLoadTimeMs=l,u.maxTimeToFirstByteMs=l;break;case"MaxRetry":u.errorRetry.maxNumRetry=l,u.timeoutRetry.maxNumRetry=l;break;case"RetryDelay":u.errorRetry.retryDelayMs=l,u.timeoutRetry.retryDelayMs=l;break;case"MaxRetryTimeout":u.errorRetry.maxRetryDelayMs=l,u.timeoutRetry.maxRetryDelayMs=l}}})),s.length&&w.warn('hls.js config: "'+s.join('", "')+'" setting(s) are deprecated, use "'+i+'": '+JSON.stringify(e[i]))})),i(i({},r),e)}(t.DefaultConfig,e);this.userConfig=e,r.progressive&&no(r);var n=r.abrController,a=r.bufferController,s=r.capLevelController,o=r.errorController,l=r.fpsController,u=new o(this),h=this.abrController=new n(this),d=this.bufferController=new a(this),c=this.capLevelController=new s(this),f=new l(this),g=new Be(this),v=new $e(this),m=r.contentSteeringController,p=m?new m(this):null,y=this.levelController=new ao(this,p),E=new $r(this),T=new oo(this.config),L=this.streamController=new co(this,E,T);c.setStreamController(L),f.setStreamController(L);var A=[g,y,L];p&&A.splice(1,0,p),this.networkControllers=A;var R=[h,d,c,f,v,E];this.audioTrackController=this.createController(r.audioTrackController,A);var b=r.audioStreamController;b&&A.push(new b(this,E,T)),this.subtitleTrackController=this.createController(r.subtitleTrackController,A);var k=r.subtitleStreamController;k&&A.push(new k(this,E,T)),this.createController(r.timelineController,R),T.emeController=this.emeController=this.createController(r.emeController,R),this.cmcdController=this.createController(r.cmcdController,R),this.latencyController=this.createController(Ze,R),this.coreComponents=R,A.push(u);var D=u.onErrorOut;"function"==typeof D&&this.on(S.ERROR,D,u)}t.isMSESupported=function(){return uo()},t.isSupported=function(){return function(){if(!uo())return!1;var t=se();return"function"==typeof(null==t?void 0:t.isTypeSupported)&&(["avc1.42E01E,mp4a.40.2","av01.0.01M.08","vp09.00.50.08"].some((function(e){return t.isTypeSupported(he(e,"video"))}))||["mp4a.40.2","fLaC"].some((function(e){return t.isTypeSupported(he(e,"audio"))})))}()},t.getMediaSource=function(){return se()};var e=t.prototype;return e.createController=function(t,e){if(t){var r=new t(this);return e&&e.push(r),r}return null},e.on=function(t,e,r){void 0===r&&(r=this),this._emitter.on(t,e,r)},e.once=function(t,e,r){void 0===r&&(r=this),this._emitter.once(t,e,r)},e.removeAllListeners=function(t){this._emitter.removeAllListeners(t)},e.off=function(t,e,r,i){void 0===r&&(r=this),this._emitter.off(t,e,r,i)},e.listeners=function(t){return this._emitter.listeners(t)},e.emit=function(t,e,r){return this._emitter.emit(t,e,r)},e.trigger=function(t,e){if(this.config.debug)return this.emit(t,t,e);try{return this.emit(t,t,e)}catch(e){if(w.error("An internal error happened while handling event "+t+'. Error message: "'+e.message+'". Here is a stacktrace:',e),!this.triggeringException){this.triggeringException=!0;var r=t===S.ERROR;this.trigger(S.ERROR,{type:L.OTHER_ERROR,details:A.INTERNAL_EXCEPTION,fatal:r,event:t,error:e}),this.triggeringException=!1}}return!1},e.listenerCount=function(t){return this._emitter.listenerCount(t)},e.destroy=function(){w.log("destroy"),this.trigger(S.DESTROYING,void 0),this.detachMedia(),this.removeAllListeners(),this._autoLevelCapping=-1,this.url=null,this.networkControllers.forEach((function(t){return t.destroy()})),this.networkControllers.length=0,this.coreComponents.forEach((function(t){return t.destroy()})),this.coreComponents.length=0;var t=this.config;t.xhrSetup=t.fetchSetup=void 0,this.userConfig=null},e.attachMedia=function(t){w.log("attachMedia"),this._media=t,this.trigger(S.MEDIA_ATTACHING,{media:t})},e.detachMedia=function(){w.log("detachMedia"),this.trigger(S.MEDIA_DETACHING,void 0),this._media=null},e.loadSource=function(t){this.stopLoad();var e=this.media,r=this.url,i=this.url=p.buildAbsoluteURL(self.location.href,t,{alwaysNormalize:!0});this._autoLevelCapping=-1,this._maxHdcpLevel=null,w.log("loadSource:"+i),e&&r&&(r!==i||this.bufferController.hasSourceTypes())&&(this.detachMedia(),this.attachMedia(e)),this.trigger(S.MANIFEST_LOADING,{url:t})},e.startLoad=function(t){void 0===t&&(t=-1),w.log("startLoad("+t+")"),this.started=!0,this.networkControllers.forEach((function(e){e.startLoad(t)}))},e.stopLoad=function(){w.log("stopLoad"),this.started=!1,this.networkControllers.forEach((function(t){t.stopLoad()}))},e.resumeBuffering=function(){this.started&&this.networkControllers.forEach((function(t){"fragmentLoader"in t&&t.startLoad(-1)}))},e.pauseBuffering=function(){this.networkControllers.forEach((function(t){"fragmentLoader"in t&&t.stopLoad()}))},e.swapAudioCodec=function(){w.log("swapAudioCodec"),this.streamController.swapAudioCodec()},e.recoverMediaError=function(){w.log("recoverMediaError");var t=this._media;this.detachMedia(),t&&this.attachMedia(t)},e.removeLevel=function(t){this.levelController.removeLevel(t)},e.setAudioOption=function(t){var e;return null==(e=this.audioTrackController)?void 0:e.setAudioOption(t)},e.setSubtitleOption=function(t){var e;return null==(e=this.subtitleTrackController)||e.setSubtitleOption(t),null},s(t,[{key:"levels",get:function(){var t=this.levelController.levels;return t||[]}},{key:"currentLevel",get:function(){return this.streamController.currentLevel},set:function(t){w.log("set currentLevel:"+t),this.levelController.manualLevel=t,this.streamController.immediateLevelSwitch()}},{key:"nextLevel",get:function(){return this.streamController.nextLevel},set:function(t){w.log("set nextLevel:"+t),this.levelController.manualLevel=t,this.streamController.nextLevelSwitch()}},{key:"loadLevel",get:function(){return this.levelController.level},set:function(t){w.log("set loadLevel:"+t),this.levelController.manualLevel=t}},{key:"nextLoadLevel",get:function(){return this.levelController.nextLoadLevel},set:function(t){this.levelController.nextLoadLevel=t}},{key:"firstLevel",get:function(){return Math.max(this.levelController.firstLevel,this.minAutoLevel)},set:function(t){w.log("set firstLevel:"+t),this.levelController.firstLevel=t}},{key:"startLevel",get:function(){var t=this.levelController.startLevel;return-1===t&&this.abrController.forcedAutoLevel>-1?this.abrController.forcedAutoLevel:t},set:function(t){w.log("set startLevel:"+t),-1!==t&&(t=Math.max(t,this.minAutoLevel)),this.levelController.startLevel=t}},{key:"capLevelToPlayerSize",get:function(){return this.config.capLevelToPlayerSize},set:function(t){var e=!!t;e!==this.config.capLevelToPlayerSize&&(e?this.capLevelController.startCapping():(this.capLevelController.stopCapping(),this.autoLevelCapping=-1,this.streamController.nextLevelSwitch()),this.config.capLevelToPlayerSize=e)}},{key:"autoLevelCapping",get:function(){return this._autoLevelCapping},set:function(t){this._autoLevelCapping!==t&&(w.log("set autoLevelCapping:"+t),this._autoLevelCapping=t,this.levelController.checkMaxAutoUpdated())}},{key:"bandwidthEstimate",get:function(){var t=this.abrController.bwEstimator;return t?t.getEstimate():NaN},set:function(t){this.abrController.resetEstimator(t)}},{key:"ttfbEstimate",get:function(){var t=this.abrController.bwEstimator;return t?t.getEstimateTTFB():NaN}},{key:"maxHdcpLevel",get:function(){return this._maxHdcpLevel},set:function(t){(function(t){return tr.indexOf(t)>-1})(t)&&this._maxHdcpLevel!==t&&(this._maxHdcpLevel=t,this.levelController.checkMaxAutoUpdated())}},{key:"autoLevelEnabled",get:function(){return-1===this.levelController.manualLevel}},{key:"manualLevel",get:function(){return this.levelController.manualLevel}},{key:"minAutoLevel",get:function(){var t=this.levels,e=this.config.minAutoBitrate;if(!t)return 0;for(var r=t.length,i=0;i<r;i++)if(t[i].maxBitrate>=e)return i;return 0}},{key:"maxAutoLevel",get:function(){var t,e=this.levels,r=this.autoLevelCapping,i=this.maxHdcpLevel;if(t=-1===r&&null!=e&&e.length?e.length-1:r,i)for(var n=t;n--;){var a=e[n].attrs["HDCP-LEVEL"];if(a&&a<=i)return n}return t}},{key:"firstAutoLevel",get:function(){return this.abrController.firstAutoLevel}},{key:"nextAutoLevel",get:function(){return this.abrController.nextAutoLevel},set:function(t){this.abrController.nextAutoLevel=t}},{key:"playingDate",get:function(){return this.streamController.currentProgramDateTime}},{key:"mainForwardBufferInfo",get:function(){return this.streamController.getMainFwdBufferInfo()}},{key:"allAudioTracks",get:function(){var t=this.audioTrackController;return t?t.allAudioTracks:[]}},{key:"audioTracks",get:function(){var t=this.audioTrackController;return t?t.audioTracks:[]}},{key:"audioTrack",get:function(){var t=this.audioTrackController;return t?t.audioTrack:-1},set:function(t){var e=this.audioTrackController;e&&(e.audioTrack=t)}},{key:"allSubtitleTracks",get:function(){var t=this.subtitleTrackController;return t?t.allSubtitleTracks:[]}},{key:"subtitleTracks",get:function(){var t=this.subtitleTrackController;return t?t.subtitleTracks:[]}},{key:"subtitleTrack",get:function(){var t=this.subtitleTrackController;return t?t.subtitleTrack:-1},set:function(t){var e=this.subtitleTrackController;e&&(e.subtitleTrack=t)}},{key:"media",get:function(){return this._media}},{key:"subtitleDisplay",get:function(){var t=this.subtitleTrackController;return!!t&&t.subtitleDisplay},set:function(t){var e=this.subtitleTrackController;e&&(e.subtitleDisplay=t)}},{key:"lowLatencyMode",get:function(){return this.config.lowLatencyMode},set:function(t){this.config.lowLatencyMode=t}},{key:"liveSyncPosition",get:function(){return this.latencyController.liveSyncPosition}},{key:"latency",get:function(){return this.latencyController.latency}},{key:"maxLatency",get:function(){return this.latencyController.maxLatency}},{key:"targetLatency",get:function(){return this.latencyController.targetLatency}},{key:"drift",get:function(){return this.latencyController.drift}},{key:"forceStartLoad",get:function(){return this.streamController.forceStartLoad}}],[{key:"version",get:function(){return"1.5.15"}},{key:"Events",get:function(){return S}},{key:"ErrorTypes",get:function(){return L}},{key:"ErrorDetails",get:function(){return A}},{key:"DefaultConfig",get:function(){return t.defaultConfig?t.defaultConfig:ro},set:function(e){t.defaultConfig=e}}]),t}();return fo.defaultConfig=void 0,fo},"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(r="undefined"!=typeof globalThis?globalThis:r||self).Hls=i()}(!1); +//# sourceMappingURL=hls.min.js.map diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoAVContentNode.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoAVContentNode.swift new file mode 100644 index 0000000000..a47dc9becc --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoAVContentNode.swift @@ -0,0 +1,549 @@ +import Foundation +import SwiftSignalKit +import UniversalMediaPlayer +import Postbox +import TelegramCore +import AsyncDisplayKit +import AccountContext +import TelegramAudio +import RangeSet +import AVFoundation +import Display +import PhotoResources +import TelegramVoip + +final class HLSVideoAVContentNode: ASDisplayNode, UniversalVideoContentNode { + private let postbox: Postbox + private let userLocation: MediaResourceUserLocation + private let fileReference: FileMediaReference + private let approximateDuration: Double + private let intrinsicDimensions: CGSize + + private let audioSessionManager: ManagedAudioSession + private let audioSessionDisposable = MetaDisposable() + private var hasAudioSession = false + + private let playbackCompletedListeners = Bag<() -> Void>() + + private var initializedStatus = false + private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) + private var baseRate: Double = 1.0 + private var isBuffering = false + private var seekId: Int = 0 + private let _status = ValuePromise<MediaPlayerStatus>() + var status: Signal<MediaPlayerStatus, NoError> { + return self._status.get() + } + + private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>() + var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> { + return self._bufferingStatus.get() + } + + var isNativePictureInPictureActive: Signal<Bool, NoError> { + return .single(false) + } + + private let _ready = Promise<Void>() + var ready: Signal<Void, NoError> { + return self._ready.get() + } + + private let _preloadCompleted = ValuePromise<Bool>() + var preloadCompleted: Signal<Bool, NoError> { + return self._preloadCompleted.get() + } + + private var playerSource: HLSServerSource? + private var serverDisposable: Disposable? + + private let imageNode: TransformImageNode + + private var playerItem: AVPlayerItem? + private var player: AVPlayer? + private let playerNode: ASDisplayNode + + private var loadProgressDisposable: Disposable? + private var statusDisposable: Disposable? + + private var didPlayToEndTimeObserver: NSObjectProtocol? + private var didBecomeActiveObserver: NSObjectProtocol? + private var willResignActiveObserver: NSObjectProtocol? + private var failureObserverId: NSObjectProtocol? + private var errorObserverId: NSObjectProtocol? + private var playerItemFailedToPlayToEndTimeObserver: NSObjectProtocol? + + private let fetchDisposable = MetaDisposable() + + private var dimensions: CGSize? + private let dimensionsPromise = ValuePromise<CGSize>(CGSize()) + + private var validLayout: (size: CGSize, actualSize: CGSize)? + + private var statusTimer: Foundation.Timer? + + private var preferredVideoQuality: UniversalVideoContentVideoQuality = .auto + + init(accountId: AccountRecordId, postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) { + self.postbox = postbox + self.fileReference = fileReference + self.approximateDuration = fileReference.media.duration ?? 0.0 + self.audioSessionManager = audioSessionManager + self.userLocation = userLocation + self.baseRate = baseRate + + if var dimensions = fileReference.media.dimensions { + if let thumbnail = fileReference.media.previewRepresentations.first { + let dimensionsVertical = dimensions.width < dimensions.height + let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height + if dimensionsVertical != thumbnailVertical { + dimensions = PixelDimensions(width: dimensions.height, height: dimensions.width) + } + } + self.dimensions = dimensions.cgSize + } else { + self.dimensions = CGSize(width: 128.0, height: 128.0) + } + + self.imageNode = TransformImageNode() + + var player: AVPlayer? + player = AVPlayer(playerItem: nil) + self.player = player + if #available(iOS 16.0, *) { + player?.defaultRate = Float(baseRate) + } + if !enableSound { + player?.volume = 0.0 + } + + self.playerNode = ASDisplayNode() + self.playerNode.setLayerBlock({ + return AVPlayerLayer(player: player) + }) + + self.intrinsicDimensions = fileReference.media.dimensions?.cgSize ?? CGSize(width: 480.0, height: 320.0) + + self.playerNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicDimensions) + + if let qualitySet = HLSQualitySet(baseFile: fileReference) { + self.playerSource = HLSServerSource(accountId: accountId.int64, fileId: fileReference.media.fileId.id, postbox: postbox, userLocation: userLocation, playlistFiles: qualitySet.playlistFiles, qualityFiles: qualitySet.qualityFiles) + } + + super.init() + + self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: self.userLocation, videoReference: fileReference) |> map { [weak self] getSize, getData in + Queue.mainQueue().async { + if let strongSelf = self, strongSelf.dimensions == nil { + if let dimensions = getSize() { + strongSelf.dimensions = dimensions + strongSelf.dimensionsPromise.set(dimensions) + if let validLayout = strongSelf.validLayout { + strongSelf.updateLayout(size: validLayout.size, actualSize: validLayout.actualSize, transition: .immediate) + } + } + } + } + return getData + }) + + self.addSubnode(self.imageNode) + self.addSubnode(self.playerNode) + self.player?.actionAtItemEnd = .pause + + self.imageNode.imageUpdated = { [weak self] _ in + self?._ready.set(.single(Void())) + } + + self.player?.addObserver(self, forKeyPath: "rate", options: [], context: nil) + + self._bufferingStatus.set(.single(nil)) + + if let playerSource = self.playerSource { + self.serverDisposable = SharedHLSServer.shared.registerPlayer(source: playerSource, completion: { [weak self] in + Queue.mainQueue().async { + guard let self else { + return + } + + let playerItem: AVPlayerItem + let assetUrl = "http://127.0.0.1:\(SharedHLSServer.shared.port)/\(playerSource.id)/master.m3u8" + #if DEBUG + print("HLSVideoAVContentNode: playing \(assetUrl)") + #endif + playerItem = AVPlayerItem(url: URL(string: assetUrl)!) + + if #available(iOS 14.0, *) { + playerItem.startsOnFirstEligibleVariant = true + } + + self.setPlayerItem(playerItem) + } + }) + } + + self.didBecomeActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in + guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else { + return + } + layer.player = strongSelf.player + }) + self.willResignActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil, using: { [weak self] _ in + guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else { + return + } + layer.player = nil + }) + } + + deinit { + self.player?.removeObserver(self, forKeyPath: "rate") + + self.setPlayerItem(nil) + + self.audioSessionDisposable.dispose() + + self.loadProgressDisposable?.dispose() + self.statusDisposable?.dispose() + + if let didBecomeActiveObserver = self.didBecomeActiveObserver { + NotificationCenter.default.removeObserver(didBecomeActiveObserver) + } + if let willResignActiveObserver = self.willResignActiveObserver { + NotificationCenter.default.removeObserver(willResignActiveObserver) + } + + if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver { + NotificationCenter.default.removeObserver(didPlayToEndTimeObserver) + } + if let failureObserverId = self.failureObserverId { + NotificationCenter.default.removeObserver(failureObserverId) + } + if let errorObserverId = self.errorObserverId { + NotificationCenter.default.removeObserver(errorObserverId) + } + + self.serverDisposable?.dispose() + + self.statusTimer?.invalidate() + } + + private func setPlayerItem(_ item: AVPlayerItem?) { + if let playerItem = self.playerItem { + playerItem.removeObserver(self, forKeyPath: "playbackBufferEmpty") + playerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") + playerItem.removeObserver(self, forKeyPath: "playbackBufferFull") + playerItem.removeObserver(self, forKeyPath: "status") + playerItem.removeObserver(self, forKeyPath: "presentationSize") + } + + if let playerItemFailedToPlayToEndTimeObserver = self.playerItemFailedToPlayToEndTimeObserver { + self.playerItemFailedToPlayToEndTimeObserver = nil + NotificationCenter.default.removeObserver(playerItemFailedToPlayToEndTimeObserver) + } + + if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver { + self.didPlayToEndTimeObserver = nil + NotificationCenter.default.removeObserver(didPlayToEndTimeObserver) + } + if let failureObserverId = self.failureObserverId { + self.failureObserverId = nil + NotificationCenter.default.removeObserver(failureObserverId) + } + if let errorObserverId = self.errorObserverId { + self.errorObserverId = nil + NotificationCenter.default.removeObserver(errorObserverId) + } + + self.playerItem = item + + if let item { + self.didPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item, queue: nil, using: { [weak self] notification in + self?.performActionAtEnd() + }) + + self.failureObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.failedToPlayToEndTimeNotification, object: item, queue: .main, using: { notification in +#if DEBUG + print("Player Error: \(notification.description)") +#endif + }) + self.errorObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.newErrorLogEntryNotification, object: item, queue: .main, using: { [weak item] notification in + if let item { + let event = item.errorLog()?.events.last + if let event { + let _ = event +#if DEBUG + print("Player Error: \(event.errorComment ?? "<no comment>")") +#endif + } + } + }) + item.addObserver(self, forKeyPath: "presentationSize", options: [], context: nil) + } + + if let playerItem = self.playerItem { + playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil) + playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil) + playerItem.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil) + playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil) + self.playerItemFailedToPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: playerItem, queue: OperationQueue.main, using: { [weak self] _ in + guard let self else { + return + } + let _ = self + }) + } + + self.player?.replaceCurrentItem(with: self.playerItem) + } + + private func updateStatus() { + guard let player = self.player else { + return + } + let isPlaying = !player.rate.isZero + let status: MediaPlayerPlaybackStatus + if self.isBuffering { + status = .buffering(initial: false, whilePlaying: isPlaying, progress: 0.0, display: true) + } else { + status = isPlaying ? .playing : .paused + } + var timestamp = player.currentTime().seconds + if timestamp.isFinite && !timestamp.isNaN { + } else { + timestamp = 0.0 + } + self.statusValue = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: timestamp, baseRate: self.baseRate, seekId: self.seekId, status: status, soundEnabled: true) + self._status.set(self.statusValue) + + if case .playing = status { + if self.statusTimer == nil { + self.statusTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 30.0, repeats: true, block: { [weak self] _ in + guard let self else { + return + } + self.updateStatus() + }) + } + } else if let statusTimer = self.statusTimer { + self.statusTimer = nil + statusTimer.invalidate() + } + } + + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "rate" { + if let player = self.player { + let isPlaying = !player.rate.isZero + if isPlaying { + self.isBuffering = false + } + } + self.updateStatus() + } else if keyPath == "playbackBufferEmpty" { + self.isBuffering = true + self.updateStatus() + } else if keyPath == "playbackLikelyToKeepUp" || keyPath == "playbackBufferFull" { + self.isBuffering = false + self.updateStatus() + } else if keyPath == "presentationSize" { + if let currentItem = self.player?.currentItem { + print("Presentation size: \(Int(currentItem.presentationSize.height))") + } + } + } + + private func performActionAtEnd() { + for listener in self.playbackCompletedListeners.copyItems() { + listener() + } + } + + func updateLayout(size: CGSize, actualSize: CGSize, transition: ContainedViewLayoutTransition) { + transition.updatePosition(node: self.playerNode, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + transition.updateTransformScale(node: self.playerNode, scale: size.width / self.intrinsicDimensions.width) + + transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size)) + + if let dimensions = self.dimensions { + let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) + let makeLayout = self.imageNode.asyncLayout() + let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) + applyLayout() + } + } + + func play() { + assert(Queue.mainQueue().isCurrent()) + if !self.initializedStatus { + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: self.baseRate, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) + } + if !self.hasAudioSession { + if self.player?.volume != 0.0 { + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in + guard let self else { + return + } + self.hasAudioSession = true + self.player?.play() + }, deactivate: { [weak self] _ in + guard let self else { + return .complete() + } + self.hasAudioSession = false + self.player?.pause() + + return .complete() + })) + } else { + self.player?.play() + } + } else { + self.player?.play() + } + } + + func pause() { + assert(Queue.mainQueue().isCurrent()) + self.player?.pause() + } + + func togglePlayPause() { + assert(Queue.mainQueue().isCurrent()) + + guard let player = self.player else { + return + } + + if player.rate.isZero { + self.play() + } else { + self.pause() + } + } + + func setSoundEnabled(_ value: Bool) { + assert(Queue.mainQueue().isCurrent()) + if value { + if !self.hasAudioSession { + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in + self?.hasAudioSession = true + self?.player?.volume = 1.0 + }, deactivate: { [weak self] _ in + self?.hasAudioSession = false + self?.player?.pause() + return .complete() + })) + } + } else { + self.player?.volume = 0.0 + self.hasAudioSession = false + self.audioSessionDisposable.set(nil) + } + } + + func seek(_ timestamp: Double) { + assert(Queue.mainQueue().isCurrent()) + self.seekId += 1 + self.player?.seek(to: CMTime(seconds: timestamp, preferredTimescale: 30)) + } + + func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { + self.player?.volume = 1.0 + self.play() + } + + func setSoundMuted(soundMuted: Bool) { + self.player?.volume = soundMuted ? 0.0 : 1.0 + } + + func continueWithOverridingAmbientMode(isAmbient: Bool) { + } + + func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { + } + + func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { + self.player?.volume = 0.0 + self.hasAudioSession = false + self.audioSessionDisposable.set(nil) + } + + func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool) { + } + + func setBaseRate(_ baseRate: Double) { + guard let player = self.player else { + return + } + self.baseRate = baseRate + if #available(iOS 16.0, *) { + player.defaultRate = Float(baseRate) + } + if player.rate != 0.0 { + player.rate = Float(baseRate) + } + self.updateStatus() + } + + func setVideoQuality(_ videoQuality: UniversalVideoContentVideoQuality) { + self.preferredVideoQuality = videoQuality + + guard let currentItem = self.player?.currentItem else { + return + } + guard let playerSource = self.playerSource else { + return + } + + switch videoQuality { + case .auto: + currentItem.preferredPeakBitRate = 0.0 + case let .quality(qualityValue): + if let file = playerSource.qualityFiles[qualityValue] { + if let size = file.media.size, let duration = file.media.duration, duration != 0.0 { + let bandwidth = Int(Double(size) / duration) * 8 + currentItem.preferredPeakBitRate = Double(bandwidth) + } + } + } + + } + + func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? { + guard let currentItem = self.player?.currentItem else { + return nil + } + guard let playerSource = self.playerSource else { + return nil + } + let current = Int(currentItem.presentationSize.height) + var available: [Int] = Array(playerSource.qualityFiles.keys) + available.sort(by: { $0 > $1 }) + return (current, self.preferredVideoQuality, available) + } + + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { + return self.playbackCompletedListeners.add(f) + } + + func removePlaybackCompleted(_ index: Int) { + self.playbackCompletedListeners.remove(index) + } + + func fetchControl(_ control: UniversalVideoNodeFetchControl) { + } + + func notifyPlaybackControlsHidden(_ hidden: Bool) { + } + + func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { + } + + func enterNativePictureInPicture() -> Bool { + return false + } + + func exitNativePictureInPicture() { + } +} diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift index e8d65a508d..77b46d4398 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoContent.swift @@ -13,7 +13,6 @@ import PhotoResources import RangeSet import TelegramVoip import ManagedFile -import WebKit import AppBundle public final class HLSQualitySet { @@ -65,18 +64,116 @@ public final class HLSQualitySet { } public final class HLSVideoContent: UniversalVideoContent { - public static func minimizedHLSQualityFile(file: FileMediaReference) -> FileMediaReference? { + public static func minimizedHLSQuality(file: FileMediaReference) -> (playlist: FileMediaReference, file: FileMediaReference)? { guard let qualitySet = HLSQualitySet(baseFile: file) else { return nil } for (quality, qualityFile) in qualitySet.qualityFiles.sorted(by: { $0.key < $1.key }) { if quality >= 400 { - return qualityFile + guard let playlistFile = qualitySet.playlistFiles[quality] else { + return nil + } + return (playlistFile, qualityFile) } } return nil } + public static func minimizedHLSQualityPreloadData(postbox: Postbox, file: FileMediaReference, userLocation: MediaResourceUserLocation, prefixSeconds: Int, autofetchPlaylist: Bool) -> Signal<(FileMediaReference, Range<Int64>)?, NoError> { + guard let fileSet = minimizedHLSQuality(file: file) else { + return .single(nil) + } + + let playlistData: Signal<Range<Int64>?, NoError> = Signal { subscriber in + var fetchDisposable: Disposable? + if autofetchPlaylist { + fetchDisposable = freeMediaFileResourceInteractiveFetched(postbox: postbox, userLocation: userLocation, fileReference: fileSet.playlist, resource: fileSet.playlist.media.resource).start() + } + let dataDisposable = postbox.mediaBox.resourceData(fileSet.playlist.media.resource).start(next: { data in + if !data.complete { + return + } + guard let data = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else { + subscriber.putNext(nil) + subscriber.putCompletion() + return + } + guard let playlistString = String(data: data, encoding: .utf8) else { + subscriber.putNext(nil) + subscriber.putCompletion() + return + } + + var durations: [Int] = [] + var byteRanges: [Range<Int>] = [] + + let extinfRegex = try! NSRegularExpression(pattern: "EXTINF:(\\d+)", options: []) + let byteRangeRegex = try! NSRegularExpression(pattern: "EXT-X-BYTERANGE:(\\d+)@(\\d+)", options: []) + + let extinfResults = extinfRegex.matches(in: playlistString, range: NSRange(playlistString.startIndex..., in: playlistString)) + for result in extinfResults { + if let durationRange = Range(result.range(at: 1), in: playlistString) { + if let duration = Int(String(playlistString[durationRange])) { + durations.append(duration) + } + } + } + + let byteRangeResults = byteRangeRegex.matches(in: playlistString, range: NSRange(playlistString.startIndex..., in: playlistString)) + for result in byteRangeResults { + if let lengthRange = Range(result.range(at: 1), in: playlistString), let upperBoundRange = Range(result.range(at: 2), in: playlistString) { + if let length = Int(String(playlistString[lengthRange])), let lowerBound = Int(String(playlistString[upperBoundRange])) { + byteRanges.append(lowerBound ..< (lowerBound + length)) + } + } + } + + if durations.count != byteRanges.count { + subscriber.putNext(nil) + subscriber.putCompletion() + return + } + + var rangeUpperBound: Int64 = 0 + var remainingSeconds = prefixSeconds + + for i in 0 ..< durations.count { + if remainingSeconds <= 0 { + break + } + let duration = durations[i] + let byteRange = byteRanges[i] + + remainingSeconds -= duration + rangeUpperBound = max(rangeUpperBound, Int64(byteRange.upperBound)) + } + + if rangeUpperBound != 0 { + subscriber.putNext(0 ..< rangeUpperBound) + subscriber.putCompletion() + } else { + subscriber.putNext(nil) + subscriber.putCompletion() + } + + return + }) + + return ActionDisposable { + fetchDisposable?.dispose() + dataDisposable.dispose() + } + } + + return playlistData + |> map { range -> (FileMediaReference, Range<Int64>)? in + guard let range else { + return nil + } + return (fileSet.file, range) + } + } + public let id: AnyHashable public let nativeId: PlatformVideoContentId let userLocation: MediaResourceUserLocation @@ -125,7 +222,7 @@ public final class HLSVideoContent: UniversalVideoContent { } } -private final class HLSServerSource: SharedHLSServer.Source { +final class HLSServerSource: SharedHLSServer.Source { let id: String let postbox: Postbox let userLocation: MediaResourceUserLocation @@ -358,1261 +455,3 @@ private final class HLSServerSource: SharedHLSServer.Source { return fetchFromRemote } } - -private final class HLSVideoAVContentNode: ASDisplayNode, UniversalVideoContentNode { - private let postbox: Postbox - private let userLocation: MediaResourceUserLocation - private let fileReference: FileMediaReference - private let approximateDuration: Double - private let intrinsicDimensions: CGSize - - private let audioSessionManager: ManagedAudioSession - private let audioSessionDisposable = MetaDisposable() - private var hasAudioSession = false - - private let playbackCompletedListeners = Bag<() -> Void>() - - private var initializedStatus = false - private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - private var baseRate: Double = 1.0 - private var isBuffering = false - private var seekId: Int = 0 - private let _status = ValuePromise<MediaPlayerStatus>() - var status: Signal<MediaPlayerStatus, NoError> { - return self._status.get() - } - - private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>() - var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> { - return self._bufferingStatus.get() - } - - private let _ready = Promise<Void>() - var ready: Signal<Void, NoError> { - return self._ready.get() - } - - private let _preloadCompleted = ValuePromise<Bool>() - var preloadCompleted: Signal<Bool, NoError> { - return self._preloadCompleted.get() - } - - private var playerSource: HLSServerSource? - private var serverDisposable: Disposable? - - private let imageNode: TransformImageNode - - private var playerItem: AVPlayerItem? - private var player: AVPlayer? - private let playerNode: ASDisplayNode - - private var loadProgressDisposable: Disposable? - private var statusDisposable: Disposable? - - private var didPlayToEndTimeObserver: NSObjectProtocol? - private var didBecomeActiveObserver: NSObjectProtocol? - private var willResignActiveObserver: NSObjectProtocol? - private var failureObserverId: NSObjectProtocol? - private var errorObserverId: NSObjectProtocol? - private var playerItemFailedToPlayToEndTimeObserver: NSObjectProtocol? - - private let fetchDisposable = MetaDisposable() - - private var dimensions: CGSize? - private let dimensionsPromise = ValuePromise<CGSize>(CGSize()) - - private var validLayout: (size: CGSize, actualSize: CGSize)? - - private var statusTimer: Foundation.Timer? - - private var preferredVideoQuality: UniversalVideoContentVideoQuality = .auto - - init(accountId: AccountRecordId, postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) { - self.postbox = postbox - self.fileReference = fileReference - self.approximateDuration = fileReference.media.duration ?? 0.0 - self.audioSessionManager = audioSessionManager - self.userLocation = userLocation - self.baseRate = baseRate - - if var dimensions = fileReference.media.dimensions { - if let thumbnail = fileReference.media.previewRepresentations.first { - let dimensionsVertical = dimensions.width < dimensions.height - let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height - if dimensionsVertical != thumbnailVertical { - dimensions = PixelDimensions(width: dimensions.height, height: dimensions.width) - } - } - self.dimensions = dimensions.cgSize - } else { - self.dimensions = CGSize(width: 128.0, height: 128.0) - } - - self.imageNode = TransformImageNode() - - var player: AVPlayer? - player = AVPlayer(playerItem: nil) - self.player = player - if #available(iOS 16.0, *) { - player?.defaultRate = Float(baseRate) - } - if !enableSound { - player?.volume = 0.0 - } - - self.playerNode = ASDisplayNode() - self.playerNode.setLayerBlock({ - return AVPlayerLayer(player: player) - }) - - self.intrinsicDimensions = fileReference.media.dimensions?.cgSize ?? CGSize(width: 480.0, height: 320.0) - - self.playerNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicDimensions) - - if let qualitySet = HLSQualitySet(baseFile: fileReference) { - self.playerSource = HLSServerSource(accountId: accountId.int64, fileId: fileReference.media.fileId.id, postbox: postbox, userLocation: userLocation, playlistFiles: qualitySet.playlistFiles, qualityFiles: qualitySet.qualityFiles) - } - - super.init() - - self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: self.userLocation, videoReference: fileReference) |> map { [weak self] getSize, getData in - Queue.mainQueue().async { - if let strongSelf = self, strongSelf.dimensions == nil { - if let dimensions = getSize() { - strongSelf.dimensions = dimensions - strongSelf.dimensionsPromise.set(dimensions) - if let validLayout = strongSelf.validLayout { - strongSelf.updateLayout(size: validLayout.size, actualSize: validLayout.actualSize, transition: .immediate) - } - } - } - } - return getData - }) - - self.addSubnode(self.imageNode) - self.addSubnode(self.playerNode) - self.player?.actionAtItemEnd = .pause - - self.imageNode.imageUpdated = { [weak self] _ in - self?._ready.set(.single(Void())) - } - - self.player?.addObserver(self, forKeyPath: "rate", options: [], context: nil) - - self._bufferingStatus.set(.single(nil)) - - if let playerSource = self.playerSource { - self.serverDisposable = SharedHLSServer.shared.registerPlayer(source: playerSource, completion: { [weak self] in - Queue.mainQueue().async { - guard let self else { - return - } - - let playerItem: AVPlayerItem - let assetUrl = "http://127.0.0.1:\(SharedHLSServer.shared.port)/\(playerSource.id)/master.m3u8" - #if DEBUG - print("HLSVideoAVContentNode: playing \(assetUrl)") - #endif - playerItem = AVPlayerItem(url: URL(string: assetUrl)!) - - if #available(iOS 14.0, *) { - playerItem.startsOnFirstEligibleVariant = true - } - - self.setPlayerItem(playerItem) - } - }) - } - - self.didBecomeActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in - guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else { - return - } - layer.player = strongSelf.player - }) - self.willResignActiveObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil, using: { [weak self] _ in - guard let strongSelf = self, let layer = strongSelf.playerNode.layer as? AVPlayerLayer else { - return - } - layer.player = nil - }) - } - - deinit { - self.player?.removeObserver(self, forKeyPath: "rate") - - self.setPlayerItem(nil) - - self.audioSessionDisposable.dispose() - - self.loadProgressDisposable?.dispose() - self.statusDisposable?.dispose() - - if let didBecomeActiveObserver = self.didBecomeActiveObserver { - NotificationCenter.default.removeObserver(didBecomeActiveObserver) - } - if let willResignActiveObserver = self.willResignActiveObserver { - NotificationCenter.default.removeObserver(willResignActiveObserver) - } - - if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver { - NotificationCenter.default.removeObserver(didPlayToEndTimeObserver) - } - if let failureObserverId = self.failureObserverId { - NotificationCenter.default.removeObserver(failureObserverId) - } - if let errorObserverId = self.errorObserverId { - NotificationCenter.default.removeObserver(errorObserverId) - } - - self.serverDisposable?.dispose() - - self.statusTimer?.invalidate() - } - - private func setPlayerItem(_ item: AVPlayerItem?) { - if let playerItem = self.playerItem { - playerItem.removeObserver(self, forKeyPath: "playbackBufferEmpty") - playerItem.removeObserver(self, forKeyPath: "playbackLikelyToKeepUp") - playerItem.removeObserver(self, forKeyPath: "playbackBufferFull") - playerItem.removeObserver(self, forKeyPath: "status") - playerItem.removeObserver(self, forKeyPath: "presentationSize") - } - - if let playerItemFailedToPlayToEndTimeObserver = self.playerItemFailedToPlayToEndTimeObserver { - self.playerItemFailedToPlayToEndTimeObserver = nil - NotificationCenter.default.removeObserver(playerItemFailedToPlayToEndTimeObserver) - } - - if let didPlayToEndTimeObserver = self.didPlayToEndTimeObserver { - self.didPlayToEndTimeObserver = nil - NotificationCenter.default.removeObserver(didPlayToEndTimeObserver) - } - if let failureObserverId = self.failureObserverId { - self.failureObserverId = nil - NotificationCenter.default.removeObserver(failureObserverId) - } - if let errorObserverId = self.errorObserverId { - self.errorObserverId = nil - NotificationCenter.default.removeObserver(errorObserverId) - } - - self.playerItem = item - - if let item { - self.didPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: item, queue: nil, using: { [weak self] notification in - self?.performActionAtEnd() - }) - - self.failureObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.failedToPlayToEndTimeNotification, object: item, queue: .main, using: { notification in -#if DEBUG - print("Player Error: \(notification.description)") -#endif - }) - self.errorObserverId = NotificationCenter.default.addObserver(forName: AVPlayerItem.newErrorLogEntryNotification, object: item, queue: .main, using: { [weak item] notification in - if let item { - let event = item.errorLog()?.events.last - if let event { - let _ = event -#if DEBUG - print("Player Error: \(event.errorComment ?? "<no comment>")") -#endif - } - } - }) - item.addObserver(self, forKeyPath: "presentationSize", options: [], context: nil) - } - - if let playerItem = self.playerItem { - playerItem.addObserver(self, forKeyPath: "playbackBufferEmpty", options: .new, context: nil) - playerItem.addObserver(self, forKeyPath: "playbackLikelyToKeepUp", options: .new, context: nil) - playerItem.addObserver(self, forKeyPath: "playbackBufferFull", options: .new, context: nil) - playerItem.addObserver(self, forKeyPath: "status", options: .new, context: nil) - self.playerItemFailedToPlayToEndTimeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: playerItem, queue: OperationQueue.main, using: { [weak self] _ in - guard let self else { - return - } - let _ = self - }) - } - - self.player?.replaceCurrentItem(with: self.playerItem) - } - - private func updateStatus() { - guard let player = self.player else { - return - } - let isPlaying = !player.rate.isZero - let status: MediaPlayerPlaybackStatus - if self.isBuffering { - status = .buffering(initial: false, whilePlaying: isPlaying, progress: 0.0, display: true) - } else { - status = isPlaying ? .playing : .paused - } - var timestamp = player.currentTime().seconds - if timestamp.isFinite && !timestamp.isNaN { - } else { - timestamp = 0.0 - } - self.statusValue = MediaPlayerStatus(generationTimestamp: CACurrentMediaTime(), duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: timestamp, baseRate: self.baseRate, seekId: self.seekId, status: status, soundEnabled: true) - self._status.set(self.statusValue) - - if case .playing = status { - if self.statusTimer == nil { - self.statusTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 30.0, repeats: true, block: { [weak self] _ in - guard let self else { - return - } - self.updateStatus() - }) - } - } else if let statusTimer = self.statusTimer { - self.statusTimer = nil - statusTimer.invalidate() - } - } - - override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - if keyPath == "rate" { - if let player = self.player { - let isPlaying = !player.rate.isZero - if isPlaying { - self.isBuffering = false - } - } - self.updateStatus() - } else if keyPath == "playbackBufferEmpty" { - self.isBuffering = true - self.updateStatus() - } else if keyPath == "playbackLikelyToKeepUp" || keyPath == "playbackBufferFull" { - self.isBuffering = false - self.updateStatus() - } else if keyPath == "presentationSize" { - if let currentItem = self.player?.currentItem { - print("Presentation size: \(Int(currentItem.presentationSize.height))") - } - } - } - - private func performActionAtEnd() { - for listener in self.playbackCompletedListeners.copyItems() { - listener() - } - } - - func updateLayout(size: CGSize, actualSize: CGSize, transition: ContainedViewLayoutTransition) { - transition.updatePosition(node: self.playerNode, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) - transition.updateTransformScale(node: self.playerNode, scale: size.width / self.intrinsicDimensions.width) - - transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size)) - - if let dimensions = self.dimensions { - let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) - let makeLayout = self.imageNode.asyncLayout() - let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) - applyLayout() - } - } - - func play() { - assert(Queue.mainQueue().isCurrent()) - if !self.initializedStatus { - self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: self.baseRate, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) - } - if !self.hasAudioSession { - if self.player?.volume != 0.0 { - self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in - guard let self else { - return - } - self.hasAudioSession = true - self.player?.play() - }, deactivate: { [weak self] _ in - guard let self else { - return .complete() - } - self.hasAudioSession = false - self.player?.pause() - - return .complete() - })) - } else { - self.player?.play() - } - } else { - self.player?.play() - } - } - - func pause() { - assert(Queue.mainQueue().isCurrent()) - self.player?.pause() - } - - func togglePlayPause() { - assert(Queue.mainQueue().isCurrent()) - - guard let player = self.player else { - return - } - - if player.rate.isZero { - self.play() - } else { - self.pause() - } - } - - func setSoundEnabled(_ value: Bool) { - assert(Queue.mainQueue().isCurrent()) - if value { - if !self.hasAudioSession { - self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in - self?.hasAudioSession = true - self?.player?.volume = 1.0 - }, deactivate: { [weak self] _ in - self?.hasAudioSession = false - self?.player?.pause() - return .complete() - })) - } - } else { - self.player?.volume = 0.0 - self.hasAudioSession = false - self.audioSessionDisposable.set(nil) - } - } - - func seek(_ timestamp: Double) { - assert(Queue.mainQueue().isCurrent()) - self.seekId += 1 - self.player?.seek(to: CMTime(seconds: timestamp, preferredTimescale: 30)) - } - - func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { - self.player?.volume = 1.0 - self.play() - } - - func setSoundMuted(soundMuted: Bool) { - self.player?.volume = soundMuted ? 0.0 : 1.0 - } - - func continueWithOverridingAmbientMode(isAmbient: Bool) { - } - - func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { - } - - func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { - self.player?.volume = 0.0 - self.hasAudioSession = false - self.audioSessionDisposable.set(nil) - } - - func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool) { - } - - func setBaseRate(_ baseRate: Double) { - guard let player = self.player else { - return - } - self.baseRate = baseRate - if #available(iOS 16.0, *) { - player.defaultRate = Float(baseRate) - } - if player.rate != 0.0 { - player.rate = Float(baseRate) - } - self.updateStatus() - } - - func setVideoQuality(_ videoQuality: UniversalVideoContentVideoQuality) { - self.preferredVideoQuality = videoQuality - - guard let currentItem = self.player?.currentItem else { - return - } - guard let playerSource = self.playerSource else { - return - } - - switch videoQuality { - case .auto: - currentItem.preferredPeakBitRate = 0.0 - case let .quality(qualityValue): - if let file = playerSource.qualityFiles[qualityValue] { - if let size = file.media.size, let duration = file.media.duration, duration != 0.0 { - let bandwidth = Int(Double(size) / duration) * 8 - currentItem.preferredPeakBitRate = Double(bandwidth) - } - } - } - - } - - func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? { - guard let currentItem = self.player?.currentItem else { - return nil - } - guard let playerSource = self.playerSource else { - return nil - } - let current = Int(currentItem.presentationSize.height) - var available: [Int] = Array(playerSource.qualityFiles.keys) - available.sort(by: { $0 > $1 }) - return (current, self.preferredVideoQuality, available) - } - - func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { - return self.playbackCompletedListeners.add(f) - } - - func removePlaybackCompleted(_ index: Int) { - self.playbackCompletedListeners.remove(index) - } - - func fetchControl(_ control: UniversalVideoNodeFetchControl) { - } - - func notifyPlaybackControlsHidden(_ hidden: Bool) { - } - - func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { - } -} - -private func parseRange(from rangeString: String) -> Range<Int>? { - guard rangeString.hasPrefix("bytes=") else { - return nil - } - - let rangeValues = rangeString.dropFirst("bytes=".count).split(separator: "-") - - guard rangeValues.count == 2, - let start = Int(rangeValues[0]), - let end = Int(rangeValues[1]) else { - return nil - } - return start..<end + 1 -} - -private final class CustomVideoSchemeHandler: NSObject, WKURLSchemeHandler { - private final class PendingTask { - let sourceTask: any WKURLSchemeTask - let isCompleted = Atomic<Bool>(value: false) - var disposable: Disposable? - - init(source: HLSServerSource, sourceTask: any WKURLSchemeTask) { - self.sourceTask = sourceTask - - var requestRange: Range<Int>? - if let rangeString = sourceTask.request.allHTTPHeaderFields?["Range"] { - requestRange = parseRange(from: rangeString) - } - - guard let url = sourceTask.request.url else { - return - } - let filePath = (url.absoluteString as NSString).lastPathComponent - - if filePath == "master.m3u8" { - self.disposable = source.masterPlaylistData().startStrict(next: { [weak self] data in - guard let self else { - return - } - self.sendResponseAndClose(data: data.data(using: .utf8)!) - }) - } else if filePath.hasPrefix("hls_level_") && filePath.hasSuffix(".m3u8") { - guard let levelIndex = Int(String(filePath[filePath.index(filePath.startIndex, offsetBy: "hls_level_".count) ..< filePath.index(filePath.endIndex, offsetBy: -".m3u8".count)])) else { - self.sendErrorAndClose() - return - } - - self.disposable = source.playlistData(quality: levelIndex).startStrict(next: { [weak self] data in - guard let self else { - return - } - self.sendResponseAndClose(data: data.data(using: .utf8)!) - }) - } else if filePath.hasPrefix("partfile") && filePath.hasSuffix(".mp4") { - let fileId = String(filePath[filePath.index(filePath.startIndex, offsetBy: "partfile".count) ..< filePath.index(filePath.endIndex, offsetBy: -".mp4".count)]) - guard let fileIdValue = Int64(fileId) else { - self.sendErrorAndClose() - return - } - guard let requestRange else { - self.sendErrorAndClose() - return - } - self.disposable = (source.fileData(id: fileIdValue, range: requestRange.lowerBound ..< requestRange.upperBound + 1) - |> take(1)).start(next: { [weak self] result in - guard let self else { - return - } - - if let (file, range, totalSize) = result { - guard let allData = try? Data(contentsOf: URL(fileURLWithPath: file.path), options: .mappedIfSafe) else { - return - } - let data = allData.subdata(in: range) - - self.sendResponseAndClose(data: data, range: requestRange, totalSize: totalSize) - } else { - self.sendErrorAndClose() - } - }) - } else { - self.sendErrorAndClose() - } - } - - deinit { - self.disposable?.dispose() - } - - func cancel() { - } - - func sendErrorAndClose() { - self.sourceTask.didFailWithError(NSError(domain: "LocalVideoError", code: 500, userInfo: nil)) - } - - private func sendResponseAndClose(data: Data, range: Range<Int>? = nil, totalSize: Int? = nil) { - // Create the response with the appropriate content-type and content-length - //let mimeType = "application/octet-stream" - let responseLength = data.count - - // Construct URLResponse with optional range headers (for partial content responses) - var headers: [String: String] = [ - "Content-Length": "\(responseLength)", - "Connection": "close", - "Access-Control-Allow-Origin": "*" - ] - - if let range = range, let totalSize = totalSize { - headers["Content-Range"] = "bytes \(range.lowerBound)-\(range.upperBound)/\(totalSize)" - } - - // Create the URLResponse object - let response = HTTPURLResponse(url: self.sourceTask.request.url!, - statusCode: 200, - httpVersion: "HTTP/1.1", - headerFields: headers) - - // Send the response headers - self.sourceTask.didReceive(response!) - - // Send the response data - self.sourceTask.didReceive(data) - - // Complete the task - self.sourceTask.didFinish() - } - } - - private let source: HLSServerSource - private var pendingTasks: [PendingTask] = [] - - init(source: HLSServerSource) { - self.source = source - } - - func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) { - self.pendingTasks.append(PendingTask(source: self.source, sourceTask: urlSchemeTask)) - } - - func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) { - if let index = self.pendingTasks.firstIndex(where: { $0.sourceTask === urlSchemeTask }) { - let task = self.pendingTasks[index] - self.pendingTasks.remove(at: index) - task.cancel() - } - } -} - -private class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler { - private let f: (WKScriptMessage) -> () - - init(_ f: @escaping (WKScriptMessage) -> ()) { - self.f = f - - super.init() - } - - func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { - self.f(scriptMessage) - } -} - -private final class HLSVideoJSContentNode: ASDisplayNode, UniversalVideoContentNode { - private struct Level { - let bitrate: Int - let width: Int - let height: Int - - init(bitrate: Int, width: Int, height: Int) { - self.bitrate = bitrate - self.width = width - self.height = height - } - } - - private let postbox: Postbox - private let userLocation: MediaResourceUserLocation - private let fileReference: FileMediaReference - private let approximateDuration: Double - private let intrinsicDimensions: CGSize - - private let audioSessionManager: ManagedAudioSession - private let audioSessionDisposable = MetaDisposable() - private var hasAudioSession = false - - private let playerSource: HLSServerSource? - private var serverDisposable: Disposable? - - private let playbackCompletedListeners = Bag<() -> Void>() - - private var initializedStatus = false - private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) - private var isBuffering = false - private var seekId: Int = 0 - private let _status = ValuePromise<MediaPlayerStatus>() - var status: Signal<MediaPlayerStatus, NoError> { - return self._status.get() - } - - private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>() - var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> { - return self._bufferingStatus.get() - } - - private let _ready = Promise<Void>() - var ready: Signal<Void, NoError> { - return self._ready.get() - } - - private let _preloadCompleted = ValuePromise<Bool>() - var preloadCompleted: Signal<Bool, NoError> { - return self._preloadCompleted.get() - } - - private let imageNode: TransformImageNode - private let webView: WKWebView - - private let fetchDisposable = MetaDisposable() - - private var dimensions: CGSize? - private let dimensionsPromise = ValuePromise<CGSize>(CGSize()) - - private var validLayout: (size: CGSize, actualSize: CGSize)? - - private var statusTimer: Foundation.Timer? - - private var preferredVideoQuality: UniversalVideoContentVideoQuality = .auto - - private var playerIsReady: Bool = false - private var playerIsFirstFrameReady: Bool = false - private var playerIsPlaying: Bool = false - private var playerRate: Double = 0.0 - private var playerDefaultRate: Double = 1.0 - private var playerTime: Double = 0.0 - private var playerTimeGenerationTimestamp: Double = 0.0 - private var playerAvailableLevels: [Int: Level] = [:] - private var playerCurrentLevelIndex: Int? - - private var hasRequestedPlayerLoad: Bool = false - - private var requestedPlaying: Bool = false - private var requestedBaseRate: Double = 1.0 - private var requestedLevelIndex: Int? - - init(accountId: AccountRecordId, postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) { - self.postbox = postbox - self.fileReference = fileReference - self.approximateDuration = fileReference.media.duration ?? 0.0 - self.audioSessionManager = audioSessionManager - self.userLocation = userLocation - self.requestedBaseRate = baseRate - - #if DEBUG - if let minimizedQualityFile = HLSVideoContent.minimizedHLSQualityFile(file: self.fileReference) { - let _ = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: .video, reference: minimizedQualityFile.resourceReference(minimizedQualityFile.media.resource), range: (0 ..< 5 * 1024 * 1024, .default)).startStandalone() - } - #endif - - if var dimensions = fileReference.media.dimensions { - if let thumbnail = fileReference.media.previewRepresentations.first { - let dimensionsVertical = dimensions.width < dimensions.height - let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height - if dimensionsVertical != thumbnailVertical { - dimensions = PixelDimensions(width: dimensions.height, height: dimensions.width) - } - } - self.dimensions = dimensions.cgSize - } else { - self.dimensions = CGSize(width: 128.0, height: 128.0) - } - - self.imageNode = TransformImageNode() - - let config = WKWebViewConfiguration() - config.allowsInlineMediaPlayback = true - config.mediaTypesRequiringUserActionForPlayback = [] - config.allowsPictureInPictureMediaPlayback = false - - var playerSource: HLSServerSource? - if let qualitySet = HLSQualitySet(baseFile: fileReference) { - let playerSourceValue = HLSServerSource(accountId: accountId.int64, fileId: fileReference.media.fileId.id, postbox: postbox, userLocation: userLocation, playlistFiles: qualitySet.playlistFiles, qualityFiles: qualitySet.qualityFiles) - playerSource = playerSourceValue - let schemeHandler = CustomVideoSchemeHandler(source: playerSourceValue) - config.setURLSchemeHandler(schemeHandler, forURLScheme: "tghls") - } - self.playerSource = playerSource - - let userController = WKUserContentController() - - var handleScriptMessage: ((WKScriptMessage) -> Void)? - userController.add(WeakScriptMessageHandler { message in - handleScriptMessage?(message) - }, name: "performAction") - - let isDebug: Bool - #if DEBUG - isDebug = true - #else - isDebug = false - #endif - - let mediaDimensions = fileReference.media.dimensions?.cgSize ?? CGSize(width: 480.0, height: 320.0) - self.intrinsicDimensions = mediaDimensions.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0)) - - let userScriptJs = """ - playerInitialize({ - 'debug': \(isDebug), - 'width': \(Int(self.intrinsicDimensions.width)), - 'height': \(Int(self.intrinsicDimensions.height)) - }); - """; - let userScript = WKUserScript(source: userScriptJs, injectionTime: .atDocumentEnd, forMainFrameOnly: true) - userController.addUserScript(userScript) - - config.userContentController = userController - - self.webView = WKWebView(frame: CGRect(origin: CGPoint(), size: self.intrinsicDimensions), configuration: config) - self.webView.scrollView.isScrollEnabled = false - self.webView.allowsLinkPreview = false - self.webView.allowsBackForwardNavigationGestures = false - self.webView.accessibilityIgnoresInvertColors = true - self.webView.scrollView.contentInsetAdjustmentBehavior = .never - self.webView.alpha = 0.0 - - if #available(iOS 16.4, *) { - #if DEBUG - self.webView.isInspectable = true - #endif - } - - super.init() - - self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: self.userLocation, videoReference: fileReference) |> map { [weak self] getSize, getData in - Queue.mainQueue().async { - if let strongSelf = self, strongSelf.dimensions == nil { - if let dimensions = getSize() { - strongSelf.dimensions = dimensions - strongSelf.dimensionsPromise.set(dimensions) - if let validLayout = strongSelf.validLayout { - strongSelf.updateLayout(size: validLayout.size, actualSize: validLayout.actualSize, transition: .immediate) - } - } - } - } - return getData - }) - - self.addSubnode(self.imageNode) - self.view.addSubview(self.webView) - - self.imageNode.imageUpdated = { [weak self] _ in - self?._ready.set(.single(Void())) - } - - self._bufferingStatus.set(.single(nil)) - - handleScriptMessage = { [weak self] message in - Queue.mainQueue().async { - guard let self else { - return - } - guard let body = message.body as? [String: Any] else { - return - } - guard let eventName = body["event"] as? String else { - return - } - - switch eventName { - case "playerStatus": - guard let eventData = body["data"] as? [String: Any] else { - return - } - if let isReady = eventData["isReady"] as? Bool { - self.playerIsReady = isReady - } else { - self.playerIsReady = false - } - if let isFirstFrameReady = eventData["isFirstFrameReady"] as? Bool { - self.playerIsFirstFrameReady = isFirstFrameReady - } else { - self.playerIsFirstFrameReady = false - } - if let isPlaying = eventData["isPlaying"] as? Bool { - self.playerIsPlaying = isPlaying - } else { - self.playerIsPlaying = false - } - if let rate = eventData["rate"] as? Double { - self.playerRate = rate - } else { - self.playerRate = 0.0 - } - if let defaultRate = eventData["defaultRate"] as? Double { - self.playerDefaultRate = defaultRate - } else { - self.playerDefaultRate = 0.0 - } - if let levels = eventData["levels"] as? [[String: Any]] { - self.playerAvailableLevels.removeAll() - - for level in levels { - guard let levelIndex = level["index"] as? Int else { - continue - } - guard let levelBitrate = level["bitrate"] as? Int else { - continue - } - guard let levelWidth = level["width"] as? Int else { - continue - } - guard let levelHeight = level["height"] as? Int else { - continue - } - self.playerAvailableLevels[levelIndex] = Level( - bitrate: levelBitrate, - width: levelWidth, - height: levelHeight - ) - } - } else { - self.playerAvailableLevels.removeAll() - } - - if let currentLevel = eventData["currentLevel"] as? Int { - if self.playerAvailableLevels[currentLevel] != nil { - self.playerCurrentLevelIndex = currentLevel - } else { - self.playerCurrentLevelIndex = nil - } - } else { - self.playerCurrentLevelIndex = nil - } - - self.webView.alpha = self.playerIsFirstFrameReady ? 1.0 : 0.0 - if self.playerIsReady { - if !self.hasRequestedPlayerLoad { - if !self.playerAvailableLevels.isEmpty { - var selectedLevelIndex: Int? - if let minimizedQualityFile = HLSVideoContent.minimizedHLSQualityFile(file: self.fileReference) { - if let dimensions = minimizedQualityFile.media.dimensions { - for (index, level) in self.playerAvailableLevels { - if level.height == Int(dimensions.height) { - selectedLevelIndex = index - break - } - } - } - } - if selectedLevelIndex == nil { - selectedLevelIndex = self.playerAvailableLevels.sorted(by: { $0.value.height > $1.value.height }).first?.key - } - if let selectedLevelIndex { - self.hasRequestedPlayerLoad = true - self.webView.evaluateJavaScript("playerLoad(\(selectedLevelIndex));", completionHandler: nil) - } - } - } - - self.webView.evaluateJavaScript("playerSetBaseRate(\(self.requestedBaseRate));", completionHandler: nil) - - if self.requestedPlaying { - self.requestPlay() - } else { - self.requestPause() - } - } - - self.updateStatus() - case "playerCurrentTime": - guard let eventData = body["data"] as? [String: Any] else { - return - } - guard let value = eventData["value"] as? Double else { - return - } - self.playerTime = value - self.playerTimeGenerationTimestamp = CACurrentMediaTime() - self.updateStatus() - default: - break - } - } - } - - if let playerSource = self.playerSource { - self.serverDisposable = SharedHLSServer.shared.registerPlayer(source: playerSource, completion: { [weak self] in - Queue.mainQueue().async { - guard let self else { - return - } - - let htmlUrl = "http://127.0.0.1:\(SharedHLSServer.shared.port)/\(playerSource.id)/index.html" - self.webView.load(URLRequest(url: URL(string: htmlUrl)!)) - } - }) - } - } - - deinit { - self.serverDisposable?.dispose() - self.audioSessionDisposable.dispose() - - self.statusTimer?.invalidate() - } - - private func updateStatus() { - let isPlaying = self.requestedPlaying && self.playerRate != 0.0 - let status: MediaPlayerPlaybackStatus - if self.requestedPlaying && !isPlaying { - status = .buffering(initial: false, whilePlaying: self.requestedPlaying, progress: 0.0, display: true) - } else { - status = self.requestedPlaying ? .playing : .paused - } - var timestamp = self.playerTime - if timestamp.isFinite && !timestamp.isNaN { - } else { - timestamp = 0.0 - } - self.statusValue = MediaPlayerStatus(generationTimestamp: self.playerTimeGenerationTimestamp, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: timestamp, baseRate: self.requestedBaseRate, seekId: self.seekId, status: status, soundEnabled: true) - self._status.set(self.statusValue) - - if case .playing = status { - if self.statusTimer == nil { - self.statusTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 30.0, repeats: true, block: { [weak self] _ in - guard let self else { - return - } - self.updateStatus() - }) - } - } else if let statusTimer = self.statusTimer { - self.statusTimer = nil - statusTimer.invalidate() - } - } - - private func performActionAtEnd() { - for listener in self.playbackCompletedListeners.copyItems() { - listener() - } - } - - func updateLayout(size: CGSize, actualSize: CGSize, transition: ContainedViewLayoutTransition) { - transition.updatePosition(layer: self.webView.layer, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) - transition.updateTransformScale(layer: self.webView.layer, scale: size.width / self.intrinsicDimensions.width) - - transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size)) - - if let dimensions = self.dimensions { - let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) - let makeLayout = self.imageNode.asyncLayout() - let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) - applyLayout() - } - } - - func play() { - assert(Queue.mainQueue().isCurrent()) - if !self.initializedStatus { - self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: self.requestedBaseRate, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) - } - /*if !self.hasAudioSession { - self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in - Queue.mainQueue().async { - guard let self else { - return - } - self.hasAudioSession = true - self.requestPlay() - } - }, deactivate: { [weak self] _ in - return Signal { subscriber in - if let self { - self.hasAudioSession = false - self.requestPause() - } - - subscriber.putCompletion() - - return EmptyDisposable - } - |> runOn(.mainQueue()) - })) - } else*/ do { - self.requestPlay() - } - } - - private func requestPlay() { - self.requestedPlaying = true - if self.playerIsReady { - self.webView.evaluateJavaScript("playerPlay();", completionHandler: nil) - } - self.updateStatus() - } - - private func requestPause() { - self.requestedPlaying = false - if self.playerIsReady { - self.webView.evaluateJavaScript("playerPause();", completionHandler: nil) - } - self.updateStatus() - } - - func pause() { - assert(Queue.mainQueue().isCurrent()) - self.requestPause() - } - - func togglePlayPause() { - assert(Queue.mainQueue().isCurrent()) - - if self.requestedPlaying { - self.pause() - } else { - self.play() - } - } - - func setSoundEnabled(_ value: Bool) { - assert(Queue.mainQueue().isCurrent()) - /*if value { - if !self.hasAudioSession { - self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in - self?.hasAudioSession = true - self?.player?.volume = 1.0 - }, deactivate: { [weak self] _ in - self?.hasAudioSession = false - self?.player?.pause() - return .complete() - })) - } - } else { - self.player?.volume = 0.0 - self.hasAudioSession = false - self.audioSessionDisposable.set(nil) - }*/ - } - - func seek(_ timestamp: Double) { - assert(Queue.mainQueue().isCurrent()) - self.seekId += 1 - - self.webView.evaluateJavaScript("playerSeek(\(timestamp));", completionHandler: nil) - } - - func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { - self.webView.evaluateJavaScript("playerSetIsMuted(false);", completionHandler: nil) - - self.play() - } - - func setSoundMuted(soundMuted: Bool) { - self.webView.evaluateJavaScript("playerSetIsMuted(\(soundMuted));", completionHandler: nil) - } - - func continueWithOverridingAmbientMode(isAmbient: Bool) { - } - - func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { - } - - func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { - self.webView.evaluateJavaScript("playerSetIsMuted(true);", completionHandler: nil) - self.hasAudioSession = false - self.audioSessionDisposable.set(nil) - } - - func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool) { - } - - func setBaseRate(_ baseRate: Double) { - self.requestedBaseRate = baseRate - if self.playerIsReady { - self.webView.evaluateJavaScript("playerSetBaseRate(\(self.requestedBaseRate));", completionHandler: nil) - } - self.updateStatus() - } - - func setVideoQuality(_ videoQuality: UniversalVideoContentVideoQuality) { - self.preferredVideoQuality = videoQuality - - switch videoQuality { - case .auto: - self.requestedLevelIndex = nil - case let .quality(quality): - if let level = self.playerAvailableLevels.first(where: { $0.value.height == quality }) { - self.requestedLevelIndex = level.key - } else { - self.requestedLevelIndex = nil - } - } - - if self.playerIsReady { - self.webView.evaluateJavaScript("playerSetLevel(\(self.requestedLevelIndex ?? -1));", completionHandler: nil) - } - } - - func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? { - guard let playerCurrentLevelIndex = self.playerCurrentLevelIndex else { - return nil - } - guard let currentLevel = self.playerAvailableLevels[playerCurrentLevelIndex] else { - return nil - } - - var available = self.playerAvailableLevels.values.map(\.height) - available.sort(by: { $0 > $1 }) - - return (currentLevel.height, self.preferredVideoQuality, available) - } - - func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { - return self.playbackCompletedListeners.add(f) - } - - func removePlaybackCompleted(_ index: Int) { - self.playbackCompletedListeners.remove(index) - } - - func fetchControl(_ control: UniversalVideoNodeFetchControl) { - } - - func notifyPlaybackControlsHidden(_ hidden: Bool) { - } - - func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { - } -} - diff --git a/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSContentNode.swift b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSContentNode.swift new file mode 100644 index 0000000000..9e82785f4b --- /dev/null +++ b/submodules/TelegramUniversalVideoContent/Sources/HLSVideoJSContentNode.swift @@ -0,0 +1,777 @@ +import Foundation +import SwiftSignalKit +import UniversalMediaPlayer +import Postbox +import TelegramCore +import WebKit +import AsyncDisplayKit +import AccountContext +import TelegramAudio +import Display +import PhotoResources +import TelegramVoip +import RangeSet + +private func parseRange(from rangeString: String) -> Range<Int>? { + guard rangeString.hasPrefix("bytes=") else { + return nil + } + + let rangeValues = rangeString.dropFirst("bytes=".count).split(separator: "-") + + guard rangeValues.count == 2, + let start = Int(rangeValues[0]), + let end = Int(rangeValues[1]) else { + return nil + } + return start..<end + 1 +} + +private final class CustomVideoSchemeHandler: NSObject, WKURLSchemeHandler { + private final class PendingTask { + let sourceTask: any WKURLSchemeTask + let isCompleted = Atomic<Bool>(value: false) + var disposable: Disposable? + + init(source: HLSServerSource, sourceTask: any WKURLSchemeTask) { + self.sourceTask = sourceTask + + var requestRange: Range<Int>? + if let rangeString = sourceTask.request.allHTTPHeaderFields?["Range"] { + requestRange = parseRange(from: rangeString) + } + + guard let url = sourceTask.request.url else { + return + } + let filePath = (url.absoluteString as NSString).lastPathComponent + + if filePath == "master.m3u8" { + self.disposable = source.masterPlaylistData().startStrict(next: { [weak self] data in + guard let self else { + return + } + self.sendResponseAndClose(data: data.data(using: .utf8)!) + }) + } else if filePath.hasPrefix("hls_level_") && filePath.hasSuffix(".m3u8") { + guard let levelIndex = Int(String(filePath[filePath.index(filePath.startIndex, offsetBy: "hls_level_".count) ..< filePath.index(filePath.endIndex, offsetBy: -".m3u8".count)])) else { + self.sendErrorAndClose() + return + } + + self.disposable = source.playlistData(quality: levelIndex).startStrict(next: { [weak self] data in + guard let self else { + return + } + self.sendResponseAndClose(data: data.data(using: .utf8)!) + }) + } else if filePath.hasPrefix("partfile") && filePath.hasSuffix(".mp4") { + let fileId = String(filePath[filePath.index(filePath.startIndex, offsetBy: "partfile".count) ..< filePath.index(filePath.endIndex, offsetBy: -".mp4".count)]) + guard let fileIdValue = Int64(fileId) else { + self.sendErrorAndClose() + return + } + guard let requestRange else { + self.sendErrorAndClose() + return + } + self.disposable = (source.fileData(id: fileIdValue, range: requestRange.lowerBound ..< requestRange.upperBound + 1) + |> take(1)).start(next: { [weak self] result in + guard let self else { + return + } + + if let (file, range, totalSize) = result { + guard let allData = try? Data(contentsOf: URL(fileURLWithPath: file.path), options: .mappedIfSafe) else { + return + } + let data = allData.subdata(in: range) + + self.sendResponseAndClose(data: data, range: requestRange, totalSize: totalSize) + } else { + self.sendErrorAndClose() + } + }) + } else { + self.sendErrorAndClose() + } + } + + deinit { + self.disposable?.dispose() + } + + func cancel() { + } + + func sendErrorAndClose() { + self.sourceTask.didFailWithError(NSError(domain: "LocalVideoError", code: 500, userInfo: nil)) + } + + private func sendResponseAndClose(data: Data, range: Range<Int>? = nil, totalSize: Int? = nil) { + // Create the response with the appropriate content-type and content-length + //let mimeType = "application/octet-stream" + let responseLength = data.count + + // Construct URLResponse with optional range headers (for partial content responses) + var headers: [String: String] = [ + "Content-Length": "\(responseLength)", + "Connection": "close", + "Access-Control-Allow-Origin": "*" + ] + + if let range = range, let totalSize = totalSize { + headers["Content-Range"] = "bytes \(range.lowerBound)-\(range.upperBound)/\(totalSize)" + } + + // Create the URLResponse object + let response = HTTPURLResponse(url: self.sourceTask.request.url!, + statusCode: 200, + httpVersion: "HTTP/1.1", + headerFields: headers) + + // Send the response headers + self.sourceTask.didReceive(response!) + + // Send the response data + self.sourceTask.didReceive(data) + + // Complete the task + self.sourceTask.didFinish() + } + } + + private let source: HLSServerSource + private var pendingTasks: [PendingTask] = [] + + init(source: HLSServerSource) { + self.source = source + } + + func webView(_ webView: WKWebView, start urlSchemeTask: any WKURLSchemeTask) { + self.pendingTasks.append(PendingTask(source: self.source, sourceTask: urlSchemeTask)) + } + + func webView(_ webView: WKWebView, stop urlSchemeTask: any WKURLSchemeTask) { + if let index = self.pendingTasks.firstIndex(where: { $0.sourceTask === urlSchemeTask }) { + let task = self.pendingTasks[index] + self.pendingTasks.remove(at: index) + task.cancel() + } + } +} + +private class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler { + private let f: (WKScriptMessage) -> () + + init(_ f: @escaping (WKScriptMessage) -> ()) { + self.f = f + + super.init() + } + + func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) { + self.f(scriptMessage) + } +} + +final class HLSVideoJSContentNode: ASDisplayNode, UniversalVideoContentNode { + private struct Level { + let bitrate: Int + let width: Int + let height: Int + + init(bitrate: Int, width: Int, height: Int) { + self.bitrate = bitrate + self.width = width + self.height = height + } + } + + private static var sharedBandwidthEstimate: Double? + + private let postbox: Postbox + private let userLocation: MediaResourceUserLocation + private let fileReference: FileMediaReference + private let approximateDuration: Double + private let intrinsicDimensions: CGSize + + private let audioSessionManager: ManagedAudioSession + private let audioSessionDisposable = MetaDisposable() + private var hasAudioSession = false + + private let playerSource: HLSServerSource? + private var serverDisposable: Disposable? + + private let playbackCompletedListeners = Bag<() -> Void>() + + private var initializedStatus = false + private var statusValue = MediaPlayerStatus(generationTimestamp: 0.0, duration: 0.0, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true) + private var isBuffering = false + private var seekId: Int = 0 + private let _status = ValuePromise<MediaPlayerStatus>() + var status: Signal<MediaPlayerStatus, NoError> { + return self._status.get() + } + + private let _bufferingStatus = Promise<(RangeSet<Int64>, Int64)?>() + var bufferingStatus: Signal<(RangeSet<Int64>, Int64)?, NoError> { + return self._bufferingStatus.get() + } + + private let _isNativePictureInPictureActive = ValuePromise<Bool>(false, ignoreRepeated: true) + var isNativePictureInPictureActive: Signal<Bool, NoError> { + return self._isNativePictureInPictureActive.get() + } + + private let _ready = Promise<Void>() + var ready: Signal<Void, NoError> { + return self._ready.get() + } + + private let _preloadCompleted = ValuePromise<Bool>() + var preloadCompleted: Signal<Bool, NoError> { + return self._preloadCompleted.get() + } + + private let imageNode: TransformImageNode + private let webView: WKWebView + + private let fetchDisposable = MetaDisposable() + + private var dimensions: CGSize? + private let dimensionsPromise = ValuePromise<CGSize>(CGSize()) + + private var validLayout: (size: CGSize, actualSize: CGSize)? + + private var statusTimer: Foundation.Timer? + + private var preferredVideoQuality: UniversalVideoContentVideoQuality = .auto + + private var playerIsReady: Bool = false + private var playerIsFirstFrameReady: Bool = false + private var playerIsPlaying: Bool = false + private var playerRate: Double = 0.0 + private var playerDefaultRate: Double = 1.0 + private var playerTime: Double = 0.0 + private var playerTimeGenerationTimestamp: Double = 0.0 + private var playerAvailableLevels: [Int: Level] = [:] + private var playerCurrentLevelIndex: Int? + + private var hasRequestedPlayerLoad: Bool = false + + private var requestedPlaying: Bool = false + private var requestedBaseRate: Double = 1.0 + private var requestedLevelIndex: Int? + + init(accountId: AccountRecordId, postbox: Postbox, audioSessionManager: ManagedAudioSession, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, streamVideo: Bool, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool) { + self.postbox = postbox + self.fileReference = fileReference + self.approximateDuration = fileReference.media.duration ?? 0.0 + self.audioSessionManager = audioSessionManager + self.userLocation = userLocation + self.requestedBaseRate = baseRate + + /*#if DEBUG + if let minimizedQualityFile = HLSVideoContent.minimizedHLSQualityFile(file: self.fileReference) { + let _ = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: .video, reference: minimizedQualityFile.resourceReference(minimizedQualityFile.media.resource), range: (0 ..< 5 * 1024 * 1024, .default)).startStandalone() + } + #endif*/ + + if var dimensions = fileReference.media.dimensions { + if let thumbnail = fileReference.media.previewRepresentations.first { + let dimensionsVertical = dimensions.width < dimensions.height + let thumbnailVertical = thumbnail.dimensions.width < thumbnail.dimensions.height + if dimensionsVertical != thumbnailVertical { + dimensions = PixelDimensions(width: dimensions.height, height: dimensions.width) + } + } + self.dimensions = dimensions.cgSize + } else { + self.dimensions = CGSize(width: 128.0, height: 128.0) + } + + self.imageNode = TransformImageNode() + + let config = WKWebViewConfiguration() + config.allowsInlineMediaPlayback = true + config.mediaTypesRequiringUserActionForPlayback = [] + config.allowsPictureInPictureMediaPlayback = true + + var playerSource: HLSServerSource? + if let qualitySet = HLSQualitySet(baseFile: fileReference) { + let playerSourceValue = HLSServerSource(accountId: accountId.int64, fileId: fileReference.media.fileId.id, postbox: postbox, userLocation: userLocation, playlistFiles: qualitySet.playlistFiles, qualityFiles: qualitySet.qualityFiles) + playerSource = playerSourceValue + let schemeHandler = CustomVideoSchemeHandler(source: playerSourceValue) + config.setURLSchemeHandler(schemeHandler, forURLScheme: "tghls") + } + self.playerSource = playerSource + + let userController = WKUserContentController() + + var handleScriptMessage: ((WKScriptMessage) -> Void)? + userController.add(WeakScriptMessageHandler { message in + handleScriptMessage?(message) + }, name: "performAction") + + let isDebug: Bool + #if DEBUG + isDebug = true + #else + isDebug = false + #endif + + let mediaDimensions = fileReference.media.dimensions?.cgSize ?? CGSize(width: 480.0, height: 320.0) + var intrinsicDimensions = mediaDimensions.aspectFittedOrSmaller(CGSize(width: 1280.0, height: 1280.0)) + + let userScriptJs = """ + playerInitialize({ + 'debug': \(isDebug), + 'width': \(Int(intrinsicDimensions.width)), + 'height': \(Int(intrinsicDimensions.height)), + 'bandwidthEstimate': \(HLSVideoJSContentNode.sharedBandwidthEstimate ?? 500000.0) + }); + """; + let userScript = WKUserScript(source: userScriptJs, injectionTime: .atDocumentEnd, forMainFrameOnly: true) + userController.addUserScript(userScript) + + config.userContentController = userController + + intrinsicDimensions.width = floor(intrinsicDimensions.width / UIScreenScale) + intrinsicDimensions.height = floor(intrinsicDimensions.height / UIScreenScale) + self.intrinsicDimensions = intrinsicDimensions + + self.webView = WKWebView(frame: CGRect(origin: CGPoint(), size: self.intrinsicDimensions), configuration: config) + self.webView.scrollView.isScrollEnabled = false + self.webView.allowsLinkPreview = false + self.webView.allowsBackForwardNavigationGestures = false + self.webView.accessibilityIgnoresInvertColors = true + self.webView.scrollView.contentInsetAdjustmentBehavior = .never + self.webView.alpha = 0.0 + + if #available(iOS 16.4, *) { + #if DEBUG + self.webView.isInspectable = true + #endif + } + + super.init() + + self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: self.userLocation, videoReference: fileReference) |> map { [weak self] getSize, getData in + Queue.mainQueue().async { + if let strongSelf = self, strongSelf.dimensions == nil { + if let dimensions = getSize() { + strongSelf.dimensions = dimensions + strongSelf.dimensionsPromise.set(dimensions) + if let validLayout = strongSelf.validLayout { + strongSelf.updateLayout(size: validLayout.size, actualSize: validLayout.actualSize, transition: .immediate) + } + } + } + } + return getData + }) + + self.addSubnode(self.imageNode) + self.view.addSubview(self.webView) + + self.imageNode.imageUpdated = { [weak self] _ in + self?._ready.set(.single(Void())) + } + + self._bufferingStatus.set(.single(nil)) + + handleScriptMessage = { [weak self] message in + Queue.mainQueue().async { + guard let self else { + return + } + guard let body = message.body as? [String: Any] else { + return + } + guard let eventName = body["event"] as? String else { + return + } + + switch eventName { + case "playerStatus": + guard let eventData = body["data"] as? [String: Any] else { + return + } + if let isReady = eventData["isReady"] as? Bool { + self.playerIsReady = isReady + } else { + self.playerIsReady = false + } + if let isFirstFrameReady = eventData["isFirstFrameReady"] as? Bool { + self.playerIsFirstFrameReady = isFirstFrameReady + } else { + self.playerIsFirstFrameReady = false + } + if let isPlaying = eventData["isPlaying"] as? Bool { + self.playerIsPlaying = isPlaying + } else { + self.playerIsPlaying = false + } + if let rate = eventData["rate"] as? Double { + self.playerRate = rate + } else { + self.playerRate = 0.0 + } + if let defaultRate = eventData["defaultRate"] as? Double { + self.playerDefaultRate = defaultRate + } else { + self.playerDefaultRate = 0.0 + } + if let levels = eventData["levels"] as? [[String: Any]] { + self.playerAvailableLevels.removeAll() + + for level in levels { + guard let levelIndex = level["index"] as? Int else { + continue + } + guard let levelBitrate = level["bitrate"] as? Int else { + continue + } + guard let levelWidth = level["width"] as? Int else { + continue + } + guard let levelHeight = level["height"] as? Int else { + continue + } + self.playerAvailableLevels[levelIndex] = Level( + bitrate: levelBitrate, + width: levelWidth, + height: levelHeight + ) + } + } else { + self.playerAvailableLevels.removeAll() + } + + self._isNativePictureInPictureActive.set(eventData["isPictureInPictureActive"] as? Bool ?? false) + + if let currentLevel = eventData["currentLevel"] as? Int { + if self.playerAvailableLevels[currentLevel] != nil { + self.playerCurrentLevelIndex = currentLevel + } else { + self.playerCurrentLevelIndex = nil + } + } else { + self.playerCurrentLevelIndex = nil + } + + self.webView.alpha = self.playerIsFirstFrameReady ? 1.0 : 0.0 + if self.playerIsReady { + if !self.hasRequestedPlayerLoad { + if !self.playerAvailableLevels.isEmpty { + var selectedLevelIndex: Int? + if let minimizedQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file { + if let dimensions = minimizedQualityFile.media.dimensions { + for (index, level) in self.playerAvailableLevels { + if level.height == Int(dimensions.height) { + selectedLevelIndex = index + break + } + } + } + } + if selectedLevelIndex == nil { + selectedLevelIndex = self.playerAvailableLevels.sorted(by: { $0.value.height > $1.value.height }).first?.key + } + if let selectedLevelIndex { + self.hasRequestedPlayerLoad = true + self.webView.evaluateJavaScript("playerLoad(\(selectedLevelIndex));", completionHandler: nil) + } + } + } + + self.webView.evaluateJavaScript("playerSetBaseRate(\(self.requestedBaseRate));", completionHandler: nil) + + if self.requestedPlaying { + self.requestPlay() + } else { + self.requestPause() + } + } + + self.updateStatus() + case "playerCurrentTime": + guard let eventData = body["data"] as? [String: Any] else { + return + } + guard let value = eventData["value"] as? Double else { + return + } + + self.playerTime = value + self.playerTimeGenerationTimestamp = CACurrentMediaTime() + + var bandwidthEstimate = eventData["bandwidthEstimate"] as? Double + if let bandwidthEstimateValue = bandwidthEstimate, bandwidthEstimateValue.isNaN || bandwidthEstimateValue.isInfinite { + bandwidthEstimate = nil + } + + HLSVideoJSContentNode.sharedBandwidthEstimate = bandwidthEstimate + + self.updateStatus() + default: + break + } + } + } + + if let playerSource = self.playerSource { + self.serverDisposable = SharedHLSServer.shared.registerPlayer(source: playerSource, completion: { [weak self] in + Queue.mainQueue().async { + guard let self else { + return + } + + let htmlUrl = "http://127.0.0.1:\(SharedHLSServer.shared.port)/\(playerSource.id)/index.html" + self.webView.load(URLRequest(url: URL(string: htmlUrl)!)) + } + }) + } + } + + deinit { + self.serverDisposable?.dispose() + self.audioSessionDisposable.dispose() + + self.statusTimer?.invalidate() + } + + private func updateStatus() { + let isPlaying = self.requestedPlaying && self.playerRate != 0.0 + let status: MediaPlayerPlaybackStatus + if self.requestedPlaying && !isPlaying { + status = .buffering(initial: false, whilePlaying: self.requestedPlaying, progress: 0.0, display: true) + } else { + status = self.requestedPlaying ? .playing : .paused + } + var timestamp = self.playerTime + if timestamp.isFinite && !timestamp.isNaN { + } else { + timestamp = 0.0 + } + self.statusValue = MediaPlayerStatus(generationTimestamp: self.playerTimeGenerationTimestamp, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: timestamp, baseRate: self.requestedBaseRate, seekId: self.seekId, status: status, soundEnabled: true) + self._status.set(self.statusValue) + + if case .playing = status { + if self.statusTimer == nil { + self.statusTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 1.0 / 30.0, repeats: true, block: { [weak self] _ in + guard let self else { + return + } + self.updateStatus() + }) + } + } else if let statusTimer = self.statusTimer { + self.statusTimer = nil + statusTimer.invalidate() + } + } + + private func performActionAtEnd() { + for listener in self.playbackCompletedListeners.copyItems() { + listener() + } + } + + func updateLayout(size: CGSize, actualSize: CGSize, transition: ContainedViewLayoutTransition) { + transition.updatePosition(layer: self.webView.layer, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + transition.updateTransformScale(layer: self.webView.layer, scale: size.width / self.intrinsicDimensions.width) + + transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(), size: size)) + + if let dimensions = self.dimensions { + let imageSize = CGSize(width: floor(dimensions.width / 2.0), height: floor(dimensions.height / 2.0)) + let makeLayout = self.imageNode.asyncLayout() + let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) + applyLayout() + } + } + + func play() { + assert(Queue.mainQueue().isCurrent()) + if !self.initializedStatus { + self._status.set(MediaPlayerStatus(generationTimestamp: 0.0, duration: Double(self.approximateDuration), dimensions: CGSize(), timestamp: 0.0, baseRate: self.requestedBaseRate, seekId: self.seekId, status: .buffering(initial: true, whilePlaying: true, progress: 0.0, display: true), soundEnabled: true)) + } + /*if !self.hasAudioSession { + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in + Queue.mainQueue().async { + guard let self else { + return + } + self.hasAudioSession = true + self.requestPlay() + } + }, deactivate: { [weak self] _ in + return Signal { subscriber in + if let self { + self.hasAudioSession = false + self.requestPause() + } + + subscriber.putCompletion() + + return EmptyDisposable + } + |> runOn(.mainQueue()) + })) + } else*/ do { + self.requestPlay() + } + } + + private func requestPlay() { + self.requestedPlaying = true + if self.playerIsReady { + self.webView.evaluateJavaScript("playerPlay();", completionHandler: nil) + } + self.updateStatus() + } + + private func requestPause() { + self.requestedPlaying = false + if self.playerIsReady { + self.webView.evaluateJavaScript("playerPause();", completionHandler: nil) + } + self.updateStatus() + } + + func pause() { + assert(Queue.mainQueue().isCurrent()) + self.requestPause() + } + + func togglePlayPause() { + assert(Queue.mainQueue().isCurrent()) + + if self.requestedPlaying { + self.pause() + } else { + self.play() + } + } + + func setSoundEnabled(_ value: Bool) { + assert(Queue.mainQueue().isCurrent()) + /*if value { + if !self.hasAudioSession { + self.audioSessionDisposable.set(self.audioSessionManager.push(audioSessionType: .play(mixWithOthers: false), activate: { [weak self] _ in + self?.hasAudioSession = true + self?.player?.volume = 1.0 + }, deactivate: { [weak self] _ in + self?.hasAudioSession = false + self?.player?.pause() + return .complete() + })) + } + } else { + self.player?.volume = 0.0 + self.hasAudioSession = false + self.audioSessionDisposable.set(nil) + }*/ + } + + func seek(_ timestamp: Double) { + assert(Queue.mainQueue().isCurrent()) + self.seekId += 1 + + self.webView.evaluateJavaScript("playerSeek(\(timestamp));", completionHandler: nil) + } + + func playOnceWithSound(playAndRecord: Bool, seek: MediaPlayerSeek, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { + self.webView.evaluateJavaScript("playerSetIsMuted(false);", completionHandler: nil) + + self.play() + } + + func setSoundMuted(soundMuted: Bool) { + self.webView.evaluateJavaScript("playerSetIsMuted(\(soundMuted));", completionHandler: nil) + } + + func continueWithOverridingAmbientMode(isAmbient: Bool) { + } + + func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) { + } + + func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) { + self.webView.evaluateJavaScript("playerSetIsMuted(true);", completionHandler: nil) + self.hasAudioSession = false + self.audioSessionDisposable.set(nil) + } + + func setContinuePlayingWithoutSoundOnLostAudioSession(_ value: Bool) { + } + + func setBaseRate(_ baseRate: Double) { + self.requestedBaseRate = baseRate + if self.playerIsReady { + self.webView.evaluateJavaScript("playerSetBaseRate(\(self.requestedBaseRate));", completionHandler: nil) + } + self.updateStatus() + } + + func setVideoQuality(_ videoQuality: UniversalVideoContentVideoQuality) { + self.preferredVideoQuality = videoQuality + + switch videoQuality { + case .auto: + self.requestedLevelIndex = nil + case let .quality(quality): + if let level = self.playerAvailableLevels.first(where: { $0.value.height == quality }) { + self.requestedLevelIndex = level.key + } else { + self.requestedLevelIndex = nil + } + } + + if self.playerIsReady { + self.webView.evaluateJavaScript("playerSetLevel(\(self.requestedLevelIndex ?? -1));", completionHandler: nil) + } + } + + func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? { + guard let playerCurrentLevelIndex = self.playerCurrentLevelIndex else { + return nil + } + guard let currentLevel = self.playerAvailableLevels[playerCurrentLevelIndex] else { + return nil + } + + var available = self.playerAvailableLevels.values.map(\.height) + available.sort(by: { $0 > $1 }) + + return (currentLevel.height, self.preferredVideoQuality, available) + } + + func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int { + return self.playbackCompletedListeners.add(f) + } + + func removePlaybackCompleted(_ index: Int) { + self.playbackCompletedListeners.remove(index) + } + + func fetchControl(_ control: UniversalVideoNodeFetchControl) { + } + + func notifyPlaybackControlsHidden(_ hidden: Bool) { + } + + func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { + } + + func enterNativePictureInPicture() -> Bool { + self.webView.evaluateJavaScript("playerRequestPictureInPicture();", completionHandler: nil) + return true + } + + func exitNativePictureInPicture() { + self.webView.evaluateJavaScript("playerStopPictureInPicture();", completionHandler: nil) + } +} diff --git a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift index 66e35fecb4..07909d446f 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/NativeVideoContent.swift @@ -206,6 +206,10 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent return self._bufferingStatus.get() } + var isNativePictureInPictureActive: Signal<Bool, NoError> { + return .single(false) + } + private let _ready = Promise<Void>() var ready: Signal<Void, NoError> { return self._ready.get() @@ -685,4 +689,11 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { self.playerNode.setCanPlaybackWithoutHierarchy(canPlaybackWithoutHierarchy) } + + func enterNativePictureInPicture() -> Bool { + return false + } + + func exitNativePictureInPicture() { + } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift index 6b03b02bfc..a7e26bb004 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/PlatformVideoContent.swift @@ -141,6 +141,10 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte return self._bufferingStatus.get() } + var isNativePictureInPictureActive: Signal<Bool, NoError> { + return .single(false) + } + private let _ready = Promise<Void>() var ready: Signal<Void, NoError> { return self._ready.get() @@ -471,4 +475,11 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { } + + func enterNativePictureInPicture() -> Bool { + return false + } + + func exitNativePictureInPicture() { + } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift index 51fa878b5c..46d2e9ed8e 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/SystemVideoContent.swift @@ -58,6 +58,10 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent return self._bufferingStatus.get() } + var isNativePictureInPictureActive: Signal<Bool, NoError> { + return .single(false) + } + private let _ready = Promise<Void>() var ready: Signal<Void, NoError> { return self._ready.get() @@ -308,5 +312,12 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { } + + func enterNativePictureInPicture() -> Bool { + return false + } + + func exitNativePictureInPicture() { + } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/UniversalVideoContentManager.swift b/submodules/TelegramUniversalVideoContent/Sources/UniversalVideoContentManager.swift index 84bef2160a..0538d0d4a8 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/UniversalVideoContentManager.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/UniversalVideoContentManager.swift @@ -31,9 +31,12 @@ private final class UniversalVideoContentHolder { var bufferingStatusDisposable: Disposable? var bufferingStatusValue: (RangeSet<Int64>, Int64)? + var isNativePictureInPictureActiveDisposable: Disposable? + var isNativePictureInPictureActiveValue: Bool = false + var playbackCompletedIndex: Int? - init(content: UniversalVideoContent, contentNode: UniversalVideoContentNode & ASDisplayNode, statusUpdated: @escaping (MediaPlayerStatus?) -> Void, bufferingStatusUpdated: @escaping ((RangeSet<Int64>, Int64)?) -> Void, playbackCompleted: @escaping () -> Void) { + init(content: UniversalVideoContent, contentNode: UniversalVideoContentNode & ASDisplayNode, statusUpdated: @escaping (MediaPlayerStatus?) -> Void, bufferingStatusUpdated: @escaping ((RangeSet<Int64>, Int64)?) -> Void, playbackCompleted: @escaping () -> Void, isNativePictureInPictureActiveUpdated: @escaping (Bool) -> Void) { self.content = content self.contentNode = contentNode @@ -51,6 +54,13 @@ private final class UniversalVideoContentHolder { } }) + self.isNativePictureInPictureActiveDisposable = (contentNode.isNativePictureInPictureActive |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self { + strongSelf.isNativePictureInPictureActiveValue = value + isNativePictureInPictureActiveUpdated(value) + } + }) + self.playbackCompletedIndex = contentNode.addPlaybackCompleted { playbackCompleted() } @@ -59,6 +69,7 @@ private final class UniversalVideoContentHolder { deinit { self.statusDisposable?.dispose() self.bufferingStatusDisposable?.dispose() + self.isNativePictureInPictureActiveDisposable?.dispose() if let playbackCompletedIndex = self.playbackCompletedIndex { self.contentNode.removePlaybackCompleted(playbackCompletedIndex) } @@ -133,9 +144,10 @@ private final class UniversalVideoContentHolderCallbacks { let playbackCompleted = Bag<() -> Void>() let status = Bag<(MediaPlayerStatus?) -> Void>() let bufferingStatus = Bag<((RangeSet<Int64>, Int64)?) -> Void>() + let isNativePictureInPictureActive = Bag<(Bool) -> Void>() var isEmpty: Bool { - return self.playbackCompleted.isEmpty && self.status.isEmpty && self.bufferingStatus.isEmpty + return self.playbackCompleted.isEmpty && self.status.isEmpty && self.bufferingStatus.isEmpty && self.isNativePictureInPictureActive.isEmpty } } @@ -190,6 +202,14 @@ public final class UniversalVideoManagerImpl: UniversalVideoManager { } } } + }, isNativePictureInPictureActiveUpdated: { [weak self] value in + if let strongSelf = self { + if let current = strongSelf.holderCallbacks[content.id] { + for subscriber in current.isNativePictureInPictureActive.copyItems() { + subscriber(value) + } + } + } }) self.holders[content.id] = holder } @@ -311,4 +331,37 @@ public final class UniversalVideoManagerImpl: UniversalVideoManager { } } |> runOn(Queue.mainQueue()) } + + public func isNativePictureInPictureActiveSignal(content: UniversalVideoContent) -> Signal<Bool, NoError> { + return Signal { subscriber in + var callbacks: UniversalVideoContentHolderCallbacks + if let current = self.holderCallbacks[content.id] { + callbacks = current + } else { + callbacks = UniversalVideoContentHolderCallbacks() + self.holderCallbacks[content.id] = callbacks + } + + let index = callbacks.isNativePictureInPictureActive.add({ value in + subscriber.putNext(value) + }) + + if let current = self.holders[content.id] { + subscriber.putNext(current.isNativePictureInPictureActiveValue) + } else { + subscriber.putNext(false) + } + + return ActionDisposable { + Queue.mainQueue().async { + if let current = self.holderCallbacks[content.id] { + current.status.remove(index) + if current.playbackCompleted.isEmpty { + self.holderCallbacks.removeValue(forKey: content.id) + } + } + } + } + } |> runOn(Queue.mainQueue()) + } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift index abe11070b1..294aa1211c 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift @@ -227,4 +227,11 @@ final class WebEmbedPlayerNode: ASDisplayNode, WKNavigationDelegate { func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { } + + func enterNativePictureInPicture() -> Bool { + return false + } + + func exitNativePictureInPicture() { + } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift index 1051faef77..71c3c44d87 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedVideoContent.swift @@ -58,6 +58,10 @@ final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoContentNode { return self._bufferingStatus.get() } + var isNativePictureInPictureActive: Signal<Bool, NoError> { + return .single(false) + } + private var seekId: Int = 0 private let _ready = Promise<Void>() @@ -207,4 +211,11 @@ final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoContentNode { func setCanPlaybackWithoutHierarchy(_ canPlaybackWithoutHierarchy: Bool) { } + + func enterNativePictureInPicture() -> Bool { + return false + } + + func exitNativePictureInPicture() { + } }