// ==UserScript== // @name 动漫弹幕播放 // @namespace https://github.com/LesslsMore/anime-danmu-play // @version 0.4.0 // @author lesslsmore // @description 自动匹配加载动漫剧集对应弹幕并播放,目前支持樱花动漫、风车动漫 // @license MIT // @icon https://cdn.yinghuazy.xyz/webjs/stui_tpl/statics/img/favicon.ico // @include /^https:\/\/www\.dmla.*\.com\/play\/.*$/ // @include https://danmu.yhdmjx.com/* // @include https://www.tt776b.com/play/* // @include https://www.dm539.com/play/* // @require https://cdn.jsdelivr.net/npm/axios@1.9.0/dist/axios.min.js // @require https://cdn.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.js // @require https://cdn.jsdelivr.net/npm/artplayer@5.1.1/dist/artplayer.js // @require https://cdn.jsdelivr.net/npm/artplayer-plugin-danmuku@5.0.1/dist/artplayer-plugin-danmuku.js // @require https://cdn.jsdelivr.net/npm/dexie@4.0.8/dist/dexie.min.js // @connect https://api.dandanplay.net/* // @connect https://danmu.yhdmjx.com/* // @connect http://v16m-default.akamaized.net/* // @connect self // @connect * // @grant GM_xmlhttpRequest // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/485364/%E5%8A%A8%E6%BC%AB%E5%BC%B9%E5%B9%95%E6%92%AD%E6%94%BE.user.js // @updateURL https://update.greasyfork.icu/scripts/485364/%E5%8A%A8%E6%BC%AB%E5%BC%B9%E5%B9%95%E6%92%AD%E6%94%BE.meta.js // ==/UserScript== (async function (Dexie, CryptoJS, axios, artplayerPluginDanmuku, Artplayer) { 'use strict'; var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); (function() { var originalSetItem = localStorage.setItem; var originalRemoveItem = localStorage.removeItem; localStorage.setItem = function(key2, value) { var event = new Event("itemInserted"); event.key = key2; event.value = value; document.dispatchEvent(event); originalSetItem.apply(this, arguments); }; localStorage.removeItem = function(key2) { var event = new Event("itemRemoved"); event.key = key2; document.dispatchEvent(event); originalRemoveItem.apply(this, arguments); }; })(); function get_anime_info() { let url2 = window.location.href; let episode2 = parseInt(url2.split("-").pop().split(".")[0]); let include = [ /^https:\/\/www\.dmla.*\.com\/play\/.*$/, // 风车动漫 "https://www.tt776b.com/play/*", // 风车动漫 "https://www.dm539.com/play/*" // 樱花动漫 ]; let els = [ document.querySelector(".stui-player__detail.detail > h1 > a"), document.querySelector("body > div.myui-player.clearfix > div > div > div.myui-player__data.hidden-xs.clearfix > h3 > a"), document.querySelector(".myui-panel__head.active.clearfix > h3 > a") ]; let el; let title2; for (let i = 0; i < include.length; i++) { if (url2.match(include[i])) { el = els[i]; } } if (el != void 0) { title2 = el.text; } else { title2 = ""; console.log("没有自动匹配到动漫名称"); } let anime_url = url2.split("-")[0]; let anime_id2 = parseInt(anime_url.split("/")[4]); let web_video_info2 = { anime_id: anime_id2, episode: episode2, title: title2, url: url2 }; console.log(web_video_info2); return web_video_info2; } function re_render(container) { let player = document.querySelector(".stui-player__video.clearfix"); if (player == void 0) { player = document.querySelector("#player-left"); } let div = player.querySelector("div"); let h = div.offsetHeight; let w = div.offsetWidth; player.removeChild(div); let app = `
`; player.innerHTML = app; } const db_name = "anime"; const db_schema = { info: "&anime_id", // 主键 索引 url: "&anime_id", // 主键 索引 danmu: "&episode_id" // 组合键 索引 }; const db_obj = { [db_name]: get_db(db_name, db_schema) }; const db_url = db_obj[db_name].url; const db_info = db_obj[db_name].info; const db_danmu = db_obj[db_name].danmu; function get_db(db_name2, db_schema2, db_ver = 1) { let db = new Dexie(db_name2); db.version(db_ver).stores(db_schema2); return db; } function createDbMethods(dbInstance, pk, expiryInMinutes = 60) { const old_put = dbInstance.put.bind(dbInstance); const old_get = dbInstance.get.bind(dbInstance); const put = async function(key2, value) { const now2 = /* @__PURE__ */ new Date(); const item = { [pk]: key2, value, expiry: now2.getTime() + expiryInMinutes * 6e4 }; const result = await old_put(item); const event = new Event(old_put.name); event.key = key2; event.value = value; document.dispatchEvent(event); return result; }; const get = async function(key2) { const item = await old_get(key2); const event = new Event(old_get.name); event.key = key2; event.value = item ? item.value : null; document.dispatchEvent(event); if (!item) { return null; } const now2 = /* @__PURE__ */ new Date(); if (now2.getTime() > item.expiry) { await db_url.delete(key2); return null; } return item.value; }; dbInstance.put = put; dbInstance.get = get; return { put, get }; } createDbMethods(db_url, "anime_id", 60); createDbMethods(db_info, "anime_id", 60 * 24 * 7); createDbMethods(db_danmu, "episode_id", 60 * 24 * 7); function xhr_get(url2) { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ url: url2, method: "GET", headers: {}, onload: function(xhr) { resolve(xhr.responseText); } }); }); } const key = CryptoJS.enc.Utf8.parse("57A891D97E332A9D"); const iv = CryptoJS.enc.Utf8.parse("8d312e8d3cde6cbb"); function Decrypt(srcs, key2, iv2) { let decrypt = CryptoJS.AES.decrypt(srcs, key2, { iv: iv2, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); let decryptedStr = decrypt.toString(CryptoJS.enc.Utf8); return decryptedStr.toString(); } async function get_yhdmjx_url(url2) { let body = await xhr_get(url2); let m3u8 = get_m3u8_url(body); console.log(`m3u8: ${m3u8}`); if (m3u8) { let body2 = await xhr_get(m3u8); let aes_data = get_encode_url(body2); if (aes_data) { console.log(`aes: ${aes_data}`); let url3 = Decrypt(aes_data, key, iv); console.log(`url: ${url3}`); let src = url3.split(".com/")[1]; let mp4 = `https://lf16-capcut.faceueditorv.com/${src}`; console.log(`url: ${mp4}`); return { mp4, m3u8 }; } } } function get_m3u8_url(data) { let regex = /"url":"([^"]+)","url_next":"([^"]+)"/g; const matches = data.match(regex); if (matches) { let play = JSON.parse(`{${matches[0]}}`); let m3u8 = `https://danmu.yhdmjx.com/m3u8.php?url=${play.url}`; return m3u8; } else { console.log("No matches found."); } } function get_encode_url(data) { let regex = /getVideoInfo\("([^"]+)"/; const matches = data.match(regex); if (matches) { return matches[1]; } else { console.log("No matches found."); } } async function set_db_url_info(web_video_info2) { let { anime_id: anime_id2, title: title2, url: url2 } = web_video_info2; let var_anime_url = { "episodes": {} }; let db_anime_url = await db_url.get(anime_id2); if (db_anime_url != null) { var_anime_url = db_anime_url; } let src_url; if (!var_anime_url["episodes"].hasOwnProperty(url2)) { let { mp4, m3u8 } = await get_yhdmjx_url(url2); src_url = mp4; if (src_url) { var_anime_url["episodes"][url2] = src_url; await db_url.put(anime_id2, var_anime_url); } } else { src_url = var_anime_url["episodes"][url2]; } console.log("src_url", src_url); web_video_info2["src_url"] = src_url; return { var_anime_url }; } const request = axios.create({ baseURL: "https://lesslsmore-api.vercel.app/", timeout: 15e3 // 请求超时时间 }); request.interceptors.request.use((config) => { return config; }); request.interceptors.response.use((response) => { return response.data; }, (error) => { return Promise.reject(error); }); let end_point = "/proxy"; let API_comment = "/api/v2/comment/"; let API_search_episodes = `/api/v2/search/episodes`; function get_episodeId(animeId, id) { id = id.toString().padStart(4, "0"); let episodeId = `${animeId}${id}`; return episodeId; } async function get_search_episodes(anime, episode2) { const res = await request({ url: `${end_point}${API_search_episodes}`, method: "get", params: { anime, episode: episode2 } }); console.log(res); return res.animes; } async function get_comment(episodeId) { const res = await request({ url: `${end_point}${API_comment}${episodeId}?withRelated=true&chConvert=1`, method: "get" }); return res.comments; } const html_danmu = `" : interstitial.cue.post ? "" : ""}${interstitial.timelineStart.toFixed(2)}-${interstitial.resumeTime.toFixed(2)}]`; } function eventAssetToString(asset) { const start = asset.timelineStart; const duration = asset.duration || 0; return `["${asset.identifier}" ${start.toFixed(2)}-${(start + duration).toFixed(2)}]`; } class HlsAssetPlayer { constructor(HlsPlayerClass, userConfig, _interstitial, assetItem) { this.hls = void 0; this.interstitial = void 0; this.assetItem = void 0; this.tracks = null; this.hasDetails = false; this.mediaAttached = null; this._currentTime = void 0; this._bufferedEosTime = void 0; this.checkPlayout = () => { const interstitial = this.interstitial; const playoutLimit = interstitial.playoutLimit; const currentTime = this.currentTime; if (this.startOffset + currentTime >= playoutLimit) { this.hls.trigger(Events.PLAYOUT_LIMIT_REACHED, {}); } }; const hls = this.hls = new HlsPlayerClass(userConfig); this.interstitial = _interstitial; this.assetItem = assetItem; let uri = assetItem.uri; try { uri = getInterstitialUrl(uri, hls.sessionId).href; } catch (error) { } hls.loadSource(uri); const detailsLoaded = () => { this.hasDetails = true; }; hls.once(Events.LEVEL_LOADED, detailsLoaded); hls.once(Events.AUDIO_TRACK_LOADED, detailsLoaded); hls.once(Events.SUBTITLE_TRACK_LOADED, detailsLoaded); hls.on(Events.MEDIA_ATTACHING, (name, { media }) => { this.removeMediaListeners(); this.mediaAttached = media; const event = this.interstitial; if (event.playoutLimit) { media.addEventListener("timeupdate", this.checkPlayout); } }); } bufferedInPlaceToEnd(media) { var _this$hls; if (!this.interstitial.appendInPlace) { return false; } if ((_this$hls = this.hls) != null && _this$hls.bufferedToEnd) { return true; } if (!media || !this._bufferedEosTime) { return false; } const start = this.timelineOffset; const bufferInfo = BufferHelper.bufferInfo(media, start, 0); const bufferedEnd = this.getAssetTime(bufferInfo.end); return bufferedEnd >= this._bufferedEosTime - 0.02; } get destroyed() { var _this$hls2; return !((_this$hls2 = this.hls) != null && _this$hls2.userConfig); } get assetId() { return this.assetItem.identifier; } get interstitialId() { return this.assetItem.parentIdentifier; } get media() { var _this$hls3; return ((_this$hls3 = this.hls) == null ? void 0 : _this$hls3.media) || null; } get bufferedEnd() { const media = this.media || this.mediaAttached; if (!media) { if (this._bufferedEosTime) { return this._bufferedEosTime; } return this.currentTime; } const bufferInfo = BufferHelper.bufferInfo(media, media.currentTime, 1e-3); return this.getAssetTime(bufferInfo.end); } get currentTime() { const media = this.media || this.mediaAttached; if (!media) { return this._currentTime || 0; } return this.getAssetTime(media.currentTime); } get duration() { const duration = this.assetItem.duration; if (!duration) { return 0; } return duration; } get remaining() { const duration = this.duration; if (!duration) { return 0; } return Math.max(0, duration - this.currentTime); } get startOffset() { return this.assetItem.startOffset; } get timelineOffset() { var _this$hls4; return ((_this$hls4 = this.hls) == null ? void 0 : _this$hls4.config.timelineOffset) || 0; } set timelineOffset(value) { const timelineOffset = this.timelineOffset; if (value !== timelineOffset) { const diff = value - timelineOffset; if (Math.abs(diff) > 1 / 9e4) { if (this.hasDetails) { throw new Error(`Cannot set timelineOffset after playlists are loaded`); } this.hls.config.timelineOffset = value; } } } getAssetTime(time) { const timelineOffset = this.timelineOffset; const duration = this.duration; return Math.min(Math.max(0, time - timelineOffset), duration); } removeMediaListeners() { const media = this.mediaAttached; if (media) { this._currentTime = media.currentTime; this.bufferSnapShot(); media.removeEventListener("timeupdate", this.checkPlayout); } } bufferSnapShot() { if (this.mediaAttached) { var _this$hls5; if ((_this$hls5 = this.hls) != null && _this$hls5.bufferedToEnd) { this._bufferedEosTime = this.bufferedEnd; } } } destroy() { this.removeMediaListeners(); this.hls.destroy(); this.hls = this.interstitial = null; this.tracks = this.mediaAttached = this.checkPlayout = null; } attachMedia(data) { this.hls.attachMedia(data); } detachMedia() { this.removeMediaListeners(); this.mediaAttached = null; this.hls.detachMedia(); } resumeBuffering() { this.hls.resumeBuffering(); } pauseBuffering() { this.hls.pauseBuffering(); } transferMedia() { this.bufferSnapShot(); return this.hls.transferMedia(); } on(event, listener, context) { this.hls.on(event, listener); } once(event, listener, context) { this.hls.once(event, listener); } off(event, listener, context) { this.hls.off(event, listener); } toString() { var _this$hls6, _this$interstitial; return `HlsAssetPlayer: ${eventAssetToString(this.assetItem)} ${(_this$hls6 = this.hls) == null ? void 0 : _this$hls6.sessionId} ${(_this$interstitial = this.interstitial) != null && _this$interstitial.appendInPlace ? "append-in-place" : ""}`; } } const ABUTTING_THRESHOLD_SECONDS = 0.033; class InterstitialsSchedule extends Logger { constructor(onScheduleUpdate, logger2) { super("interstitials-sched", logger2); this.onScheduleUpdate = void 0; this.eventMap = {}; this.events = null; this.items = null; this.durations = { primary: 0, playout: 0, integrated: 0 }; this.onScheduleUpdate = onScheduleUpdate; } destroy() { this.reset(); this.onScheduleUpdate = null; } reset() { this.eventMap = {}; this.setDurations(0, 0, 0); if (this.events) { this.events.forEach((interstitial) => interstitial.reset()); } this.events = this.items = null; } resetErrorsInRange(start, end) { if (this.events) { return this.events.reduce((count, interstitial) => { if (start <= interstitial.startOffset && end > interstitial.startOffset) { delete interstitial.error; return count + 1; } return count; }, 0); } return 0; } get duration() { const items = this.items; return items ? items[items.length - 1].end : 0; } get length() { return this.items ? this.items.length : 0; } getEvent(identifier) { return identifier ? this.eventMap[identifier] || null : null; } hasEvent(identifier) { return identifier in this.eventMap; } findItemIndex(item, time) { if (item.event) { return this.findEventIndex(item.event.identifier); } let index = -1; if (item.nextEvent) { index = this.findEventIndex(item.nextEvent.identifier) - 1; } else if (item.previousEvent) { index = this.findEventIndex(item.previousEvent.identifier) + 1; } const items = this.items; if (items) { if (!items[index]) { if (time === void 0) { time = item.start; } index = this.findItemIndexAtTime(time); } while (index >= 0 && (_items$index = items[index]) != null && _items$index.event) { var _items$index; index--; } } return index; } findItemIndexAtTime(timelinePos, timelineType) { const items = this.items; if (items) { for (let i = 0; i < items.length; i++) { let timeRange = items[i]; if (timelineType && timelineType !== "primary") { timeRange = timeRange[timelineType]; } if (timelinePos === timeRange.start || timelinePos > timeRange.start && timelinePos < timeRange.end) { return i; } } } return -1; } findJumpRestrictedIndex(startIndex, endIndex) { const items = this.items; if (items) { for (let i = startIndex; i <= endIndex; i++) { if (!items[i]) { break; } const event = items[i].event; if (event != null && event.restrictions.jump && !event.appendInPlace) { return i; } } } return -1; } findEventIndex(identifier) { const items = this.items; if (items) { for (let i = items.length; i--; ) { var _items$i$event; if (((_items$i$event = items[i].event) == null ? void 0 : _items$i$event.identifier) === identifier) { return i; } } } return -1; } findAssetIndex(event, timelinePos) { const assetList = event.assetList; const length = assetList.length; if (length > 1) { for (let i = 0; i < length; i++) { const asset = assetList[i]; if (!asset.error) { const timelineStart = asset.timelineStart; if (timelinePos === timelineStart || timelinePos > timelineStart && timelinePos < timelineStart + (asset.duration || 0)) { return i; } } } } return 0; } get assetIdAtEnd() { var _this$items, _this$items2; const interstitialAtEnd = (_this$items = this.items) == null ? void 0 : (_this$items2 = _this$items[this.length - 1]) == null ? void 0 : _this$items2.event; if (interstitialAtEnd) { const assetList = interstitialAtEnd.assetList; const assetAtEnd = assetList[assetList.length - 1]; if (assetAtEnd) { return assetAtEnd.identifier; } } return null; } parseInterstitialDateRanges(mediaSelection, enableAppendInPlace) { const details = mediaSelection.main.details; const { dateRanges } = details; const previousInterstitialEvents = this.events; const interstitialEvents = this.parseDateRanges(dateRanges, { url: details.url }, enableAppendInPlace); const ids = Object.keys(dateRanges); const removedInterstitials = previousInterstitialEvents ? previousInterstitialEvents.filter((event) => !ids.includes(event.identifier)) : []; if (interstitialEvents.length) { interstitialEvents.sort((a, b) => { const aPre = a.cue.pre; const aPost = a.cue.post; const bPre = b.cue.pre; const bPost = b.cue.post; if (aPre && !bPre) { return -1; } if (bPre && !aPre) { return 1; } if (aPost && !bPost) { return 1; } if (bPost && !aPost) { return -1; } if (!aPre && !bPre && !aPost && !bPost) { const startA = a.startTime; const startB = b.startTime; if (startA !== startB) { return startA - startB; } } return a.dateRange.tagOrder - b.dateRange.tagOrder; }); } this.events = interstitialEvents; removedInterstitials.forEach((interstitial) => { this.removeEvent(interstitial); }); this.updateSchedule(mediaSelection, removedInterstitials); } updateSchedule(mediaSelection, removedInterstitials = []) { const events = this.events || []; if (events.length || removedInterstitials.length || this.length < 2) { const currentItems = this.items; const updatedItems = this.parseSchedule(events, mediaSelection); const updated = removedInterstitials.length || (currentItems == null ? void 0 : currentItems.length) !== updatedItems.length || updatedItems.some((item, i) => { return Math.abs(item.playout.start - currentItems[i].playout.start) > 5e-3 || Math.abs(item.playout.end - currentItems[i].playout.end) > 5e-3; }); if (updated) { this.items = updatedItems; this.onScheduleUpdate(removedInterstitials, currentItems); } } } parseDateRanges(dateRanges, baseData, enableAppendInPlace) { const interstitialEvents = []; const ids = Object.keys(dateRanges); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const dateRange = dateRanges[id]; if (dateRange.isInterstitial) { let interstitial = this.eventMap[id]; if (interstitial) { interstitial.setDateRange(dateRange); } else { interstitial = new InterstitialEvent(dateRange, baseData); this.eventMap[id] = interstitial; if (enableAppendInPlace === false) { interstitial.appendInPlace = enableAppendInPlace; } } interstitialEvents.push(interstitial); } } return interstitialEvents; } parseSchedule(interstitialEvents, mediaSelection) { const schedule = []; const details = mediaSelection.main.details; const primaryDuration = details.live ? Infinity : details.edge; let playoutDuration = 0; interstitialEvents = interstitialEvents.filter((event) => !event.error && !(event.cue.once && event.hasPlayed)); if (interstitialEvents.length) { this.resolveOffsets(interstitialEvents, mediaSelection); let primaryPosition = 0; let integratedTime = 0; interstitialEvents.forEach((interstitial, i) => { const preroll = interstitial.cue.pre; const postroll = interstitial.cue.post; const previousEvent = interstitialEvents[i - 1] || null; const appendInPlace = interstitial.appendInPlace; const eventStart = postroll ? primaryDuration : interstitial.startOffset; const interstitialDuration = interstitial.duration; const timelineDuration = interstitial.timelineOccupancy === TimelineOccupancy.Range ? interstitialDuration : 0; const resumptionOffset = interstitial.resumptionOffset; const inSameStartTimeSequence = (previousEvent == null ? void 0 : previousEvent.startTime) === eventStart; const start = eventStart + interstitial.cumulativeDuration; let end = appendInPlace ? start + interstitialDuration : eventStart + resumptionOffset; if (preroll || !postroll && eventStart <= 0) { const integratedStart = integratedTime; integratedTime += timelineDuration; interstitial.timelineStart = start; const playoutStart = playoutDuration; playoutDuration += interstitialDuration; schedule.push({ event: interstitial, start, end, playout: { start: playoutStart, end: playoutDuration }, integrated: { start: integratedStart, end: integratedTime } }); } else if (eventStart <= primaryDuration) { if (!inSameStartTimeSequence) { const segmentDuration = eventStart - primaryPosition; if (segmentDuration > ABUTTING_THRESHOLD_SECONDS) { const timelineStart = primaryPosition; const _integratedStart = integratedTime; integratedTime += segmentDuration; const _playoutStart = playoutDuration; playoutDuration += segmentDuration; const primarySegment = { previousEvent: interstitialEvents[i - 1] || null, nextEvent: interstitial, start: timelineStart, end: timelineStart + segmentDuration, playout: { start: _playoutStart, end: playoutDuration }, integrated: { start: _integratedStart, end: integratedTime } }; schedule.push(primarySegment); } else if (segmentDuration > 0 && previousEvent) { previousEvent.cumulativeDuration += segmentDuration; schedule[schedule.length - 1].end = eventStart; } } if (postroll) { end = start; } interstitial.timelineStart = start; const integratedStart = integratedTime; integratedTime += timelineDuration; const playoutStart = playoutDuration; playoutDuration += interstitialDuration; schedule.push({ event: interstitial, start, end, playout: { start: playoutStart, end: playoutDuration }, integrated: { start: integratedStart, end: integratedTime } }); } else { return; } const resumeTime = interstitial.resumeTime; if (postroll || resumeTime > primaryDuration) { primaryPosition = primaryDuration; } else { primaryPosition = resumeTime; } }); if (primaryPosition < primaryDuration) { var _schedule; const timelineStart = primaryPosition; const integratedStart = integratedTime; const segmentDuration = primaryDuration - primaryPosition; integratedTime += segmentDuration; const playoutStart = playoutDuration; playoutDuration += segmentDuration; schedule.push({ previousEvent: ((_schedule = schedule[schedule.length - 1]) == null ? void 0 : _schedule.event) || null, nextEvent: null, start: primaryPosition, end: timelineStart + segmentDuration, playout: { start: playoutStart, end: playoutDuration }, integrated: { start: integratedStart, end: integratedTime } }); } this.setDurations(primaryDuration, playoutDuration, integratedTime); } else { const start = 0; schedule.push({ previousEvent: null, nextEvent: null, start, end: primaryDuration, playout: { start, end: primaryDuration }, integrated: { start, end: primaryDuration } }); this.setDurations(primaryDuration, primaryDuration, primaryDuration); } return schedule; } setDurations(primary, playout, integrated) { this.durations = { primary, playout, integrated }; } resolveOffsets(interstitialEvents, mediaSelection) { const details = mediaSelection.main.details; const primaryDuration = details.live ? Infinity : details.edge; let cumulativeDuration = 0; let lastScheduledStart = -1; interstitialEvents.forEach((interstitial, i) => { const preroll = interstitial.cue.pre; const postroll = interstitial.cue.post; const eventStart = preroll ? 0 : postroll ? primaryDuration : interstitial.startTime; this.updateAssetDurations(interstitial); const inSameStartTimeSequence = lastScheduledStart === eventStart; if (inSameStartTimeSequence) { interstitial.cumulativeDuration = cumulativeDuration; } else { cumulativeDuration = 0; lastScheduledStart = eventStart; } if (!postroll && interstitial.snapOptions.in) { interstitial.resumeAnchor = findFragmentByPTS(null, details.fragments, interstitial.startOffset + interstitial.resumptionOffset, 0, 0) || void 0; } if (interstitial.appendInPlace && !interstitial.appendInPlaceStarted) { const alignedSegmentStart = this.primaryCanResumeInPlaceAt(interstitial, mediaSelection); if (!alignedSegmentStart) { interstitial.appendInPlace = false; } } if (!interstitial.appendInPlace && i + 1 < interstitialEvents.length) { const timeBetween = interstitialEvents[i + 1].startTime - interstitialEvents[i].resumeTime; if (timeBetween < ABUTTING_THRESHOLD_SECONDS) { interstitialEvents[i + 1].appendInPlace = false; if (interstitialEvents[i + 1].appendInPlace) { this.warn(`Could not change append strategy for abutting event ${interstitial}`); } } } const resumeOffset = isFiniteNumber(interstitial.resumeOffset) ? interstitial.resumeOffset : interstitial.duration; cumulativeDuration += resumeOffset; }); } primaryCanResumeInPlaceAt(interstitial, mediaSelection) { const resumeTime = interstitial.resumeTime; const resumesInPlaceAt = interstitial.startTime + interstitial.resumptionOffset; if (Math.abs(resumeTime - resumesInPlaceAt) > ALIGNED_END_THRESHOLD_SECONDS) { this.log(`"${interstitial.identifier}" resumption ${resumeTime} not aligned with estimated timeline end ${resumesInPlaceAt}`); return false; } if (!mediaSelection) { this.log(`"${interstitial.identifier}" resumption ${resumeTime} can not be aligned with media (none selected)`); return false; } const playlists = Object.keys(mediaSelection); return !playlists.some((playlistType) => { const details = mediaSelection[playlistType].details; const playlistEnd = details.edge; if (resumeTime >= playlistEnd) { this.log(`"${interstitial.identifier}" resumption ${resumeTime} past ${playlistType} playlist end ${playlistEnd}`); return false; } const startFragment = findFragmentByPTS(null, details.fragments, resumeTime); if (!startFragment) { this.log(`"${interstitial.identifier}" resumption ${resumeTime} does not align with any fragments in ${playlistType} playlist (${details.fragStart}-${details.fragmentEnd})`); return true; } const allowance = playlistType === "audio" ? 0.175 : 0; const alignedWithSegment = Math.abs(startFragment.start - resumeTime) < ALIGNED_END_THRESHOLD_SECONDS + allowance || Math.abs(startFragment.end - resumeTime) < ALIGNED_END_THRESHOLD_SECONDS + allowance; if (!alignedWithSegment) { this.log(`"${interstitial.identifier}" resumption ${resumeTime} not aligned with ${playlistType} fragment bounds (${startFragment.start}-${startFragment.end} sn: ${startFragment.sn} cc: ${startFragment.cc})`); return true; } return false; }); } updateAssetDurations(interstitial) { if (!interstitial.assetListLoaded) { return; } const eventStart = interstitial.timelineStart; let sumDuration = 0; let hasUnknownDuration = false; let hasErrors = false; interstitial.assetList.forEach((asset, i) => { const timelineStart = eventStart + sumDuration; asset.startOffset = sumDuration; asset.timelineStart = timelineStart; hasUnknownDuration || (hasUnknownDuration = asset.duration === null); hasErrors || (hasErrors = !!asset.error); const duration = asset.error ? 0 : asset.duration || 0; sumDuration += duration; }); if (hasUnknownDuration && !hasErrors) { interstitial.duration = Math.max(sumDuration, interstitial.duration); } else { interstitial.duration = sumDuration; } } removeEvent(interstitial) { interstitial.reset(); delete this.eventMap[interstitial.identifier]; } } function segmentToString(segment) { return `[${segment.event ? '"' + segment.event.identifier + '"' : "primary"}: ${segment.start.toFixed(2)}-${segment.end.toFixed(2)}]`; } class AssetListLoader { constructor(hls) { this.hls = void 0; this.hls = hls; } destroy() { this.hls = null; } loadAssetList(interstitial, hlsStartOffset) { const assetListUrl = interstitial.assetListUrl; let url2; try { url2 = getInterstitialUrl(assetListUrl, this.hls.sessionId, interstitial.baseUrl); } catch (error) { const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_ERROR, error, assetListUrl); this.hls.trigger(Events.ERROR, errorData); return; } if (hlsStartOffset && url2.protocol !== "data:") { url2.searchParams.set("_HLS_start_offset", "" + hlsStartOffset); } const config = this.hls.config; const Loader = config.loader; const loader = new Loader(config); const context = { responseType: "json", url: url2.href }; const loadPolicy = config.interstitialAssetListLoadPolicy.default; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0 }; const callbacks = { onSuccess: (response, stats, context2, networkDetails) => { const assetListResponse = response.data; const assets = assetListResponse == null ? void 0 : assetListResponse.ASSETS; if (!Array.isArray(assets)) { const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_PARSING_ERROR, new Error(`Invalid interstitial asset list`), context2.url, stats, networkDetails); this.hls.trigger(Events.ERROR, errorData); return; } interstitial.assetListResponse = assetListResponse; this.hls.trigger(Events.ASSET_LIST_LOADED, { event: interstitial, assetListResponse, networkDetails }); }, onError: (error, context2, networkDetails, stats) => { const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_ERROR, new Error(`Error loading X-ASSET-LIST: HTTP status ${error.code} ${error.text} (${context2.url})`), context2.url, stats, networkDetails); this.hls.trigger(Events.ERROR, errorData); }, onTimeout: (stats, context2, networkDetails) => { const errorData = this.assignAssetListError(interstitial, ErrorDetails.ASSET_LIST_LOAD_TIMEOUT, new Error(`Timeout loading X-ASSET-LIST (${context2.url})`), context2.url, stats, networkDetails); this.hls.trigger(Events.ERROR, errorData); } }; loader.load(context, loaderConfig, callbacks); this.hls.trigger(Events.ASSET_LIST_LOADING, { event: interstitial }); return loader; } assignAssetListError(interstitial, details, error, url2, stats, networkDetails) { interstitial.error = error; return { type: ErrorTypes.NETWORK_ERROR, details, fatal: false, interstitial, url: url2, error, networkDetails, stats }; } } function addEventListener(el, type, listener) { removeEventListener(el, type, listener); el.addEventListener(type, listener); } function removeEventListener(el, type, listener) { el.removeEventListener(type, listener); } function playWithCatch(media) { media == null ? void 0 : media.play().catch(() => { }); } class InterstitialsController extends Logger { constructor(hls, HlsPlayerClass) { super("interstitials", hls.logger); this.HlsPlayerClass = void 0; this.hls = void 0; this.assetListLoader = void 0; this.mediaSelection = null; this.altSelection = null; this.media = null; this.detachedData = null; this.requiredTracks = null; this.manager = null; this.playerQueue = []; this.bufferedPos = -1; this.timelinePos = -1; this.schedule = void 0; this.playingItem = null; this.bufferingItem = null; this.waitingItem = null; this.endedItem = null; this.playingAsset = null; this.endedAsset = null; this.bufferingAsset = null; this.shouldPlay = false; this.onPlay = () => { this.shouldPlay = true; }; this.onPause = () => { this.shouldPlay = false; }; this.onSeeking = () => { const currentTime = this.currentTime; if (currentTime === void 0 || this.playbackDisabled) { return; } const diff = currentTime - this.timelinePos; const roundingError = Math.abs(diff) < 1 / 7056e5; if (roundingError) { return; } const backwardSeek = diff <= -0.01; this.timelinePos = currentTime; this.bufferedPos = currentTime; const playingItem = this.playingItem; if (!playingItem) { this.checkBuffer(); return; } if (backwardSeek) { const resetCount = this.schedule.resetErrorsInRange(currentTime, currentTime - diff); if (resetCount) { this.updateSchedule(); } } this.checkBuffer(); if (backwardSeek && currentTime < playingItem.start || currentTime >= playingItem.end) { var _this$media; const scheduleIndex = this.schedule.findItemIndexAtTime(this.timelinePos); if (!this.isInterstitial(playingItem) && (_this$media = this.media) != null && _this$media.paused) { this.shouldPlay = false; } if (!backwardSeek) { const playingIndex = this.findItemIndex(playingItem); if (scheduleIndex > playingIndex) { const jumpIndex = this.schedule.findJumpRestrictedIndex(playingIndex + 1, scheduleIndex); if (jumpIndex > playingIndex) { this.setSchedulePosition(jumpIndex); return; } } } this.setSchedulePosition(scheduleIndex); return; } const playingAsset = this.playingAsset; if (!playingAsset) { if (this.playingLastItem && this.isInterstitial(playingItem)) { const restartAsset = playingItem.event.assetList[0]; if (restartAsset) { this.endedItem = this.playingItem; this.playingItem = null; this.setScheduleToAssetAtTime(currentTime, restartAsset); } } return; } const start = playingAsset.timelineStart; const duration = playingAsset.duration || 0; if (backwardSeek && currentTime < start || currentTime >= start + duration) { this.setScheduleToAssetAtTime(currentTime, playingAsset); } }; this.onTimeupdate = () => { const currentTime = this.currentTime; if (currentTime === void 0 || this.playbackDisabled) { return; } if (currentTime > this.timelinePos) { this.timelinePos = currentTime; if (currentTime > this.bufferedPos) { this.checkBuffer(); } } else { return; } const playingItem = this.playingItem; if (!playingItem || this.playingLastItem) { return; } if (currentTime >= playingItem.end) { this.timelinePos = playingItem.end; const playingIndex = this.findItemIndex(playingItem); this.setSchedulePosition(playingIndex + 1); } const playingAsset = this.playingAsset; if (!playingAsset) { return; } const end = playingAsset.timelineStart + (playingAsset.duration || 0); if (currentTime >= end) { this.setScheduleToAssetAtTime(currentTime, playingAsset); } }; this.onScheduleUpdate = (removedInterstitials, previousItems) => { const schedule = this.schedule; const playingItem = this.playingItem; const interstitialEvents = schedule.events || []; const scheduleItems = schedule.items || []; const durations = schedule.durations; const removedIds = removedInterstitials.map((interstitial) => interstitial.identifier); const interstitialsUpdated = !!(interstitialEvents.length || removedIds.length); if (interstitialsUpdated) { this.log(`INTERSTITIALS_UPDATED (${interstitialEvents.length}): ${interstitialEvents} Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`); } if (removedIds.length) { this.log(`Removed events ${removedIds}`); } this.playerQueue.forEach((player) => { if (player.interstitial.appendInPlace) { const timelineStart = player.assetItem.timelineStart; const diff = player.timelineOffset - timelineStart; if (diff) { try { player.timelineOffset = timelineStart; } catch (e) { if (Math.abs(diff) > ALIGNED_END_THRESHOLD_SECONDS) { this.warn(`${e} ("${player.assetId}" ${player.timelineOffset}->${timelineStart})`); } } } } }); if (playingItem) { const updatedPlayingItem = this.updateItem(playingItem, this.timelinePos); if (this.itemsMatch(playingItem, updatedPlayingItem)) { this.playingItem = updatedPlayingItem; this.waitingItem = this.endedItem = null; } } else { this.waitingItem = this.updateItem(this.waitingItem); this.endedItem = this.updateItem(this.endedItem); } const bufferingItem = this.bufferingItem; if (bufferingItem) { const updatedBufferingItem = this.updateItem(bufferingItem, this.bufferedPos); if (this.itemsMatch(bufferingItem, updatedBufferingItem)) { this.bufferingItem = updatedBufferingItem; } else if (bufferingItem.event) { this.bufferingItem = this.playingItem; this.clearInterstitial(bufferingItem.event, null); } } removedInterstitials.forEach((interstitial) => { interstitial.assetList.forEach((asset) => { this.clearAssetPlayer(asset.identifier, null); }); }); if (interstitialsUpdated || previousItems) { this.hls.trigger(Events.INTERSTITIALS_UPDATED, { events: interstitialEvents.slice(0), schedule: scheduleItems.slice(0), durations, removedIds }); if (this.isInterstitial(playingItem) && removedIds.includes(playingItem.event.identifier)) { this.warn(`Interstitial "${playingItem.event.identifier}" removed while playing`); this.primaryFallback(playingItem.event); return; } this.checkBuffer(); } }; this.hls = hls; this.HlsPlayerClass = HlsPlayerClass; this.assetListLoader = new AssetListLoader(hls); this.schedule = new InterstitialsSchedule(this.onScheduleUpdate, hls.logger); this.registerListeners(); } registerListeners() { const hls = this.hls; hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); 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_UPDATED, this.onLevelUpdated, this); hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.on(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this); hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.on(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this); hls.on(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this); hls.on(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this); hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.on(Events.BUFFERED_TO_END, this.onBufferedToEnd, this); hls.on(Events.MEDIA_ENDED, this.onMediaEnded, this); hls.on(Events.ERROR, this.onError, this); hls.on(Events.DESTROYING, this.onDestroying, this); } unregisterListeners() { const hls = this.hls; if (!hls) { return; } hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); 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_UPDATED, this.onLevelUpdated, this); hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); hls.off(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this); hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); hls.off(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this); hls.off(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this); hls.off(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this); hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); hls.off(Events.BUFFERED_TO_END, this.onBufferedToEnd, this); hls.off(Events.MEDIA_ENDED, this.onMediaEnded, this); hls.off(Events.ERROR, this.onError, this); hls.off(Events.DESTROYING, this.onDestroying, this); } startLoad() { this.resumeBuffering(); } stopLoad() { this.pauseBuffering(); } resumeBuffering() { var _this$getBufferingPla; (_this$getBufferingPla = this.getBufferingPlayer()) == null ? void 0 : _this$getBufferingPla.resumeBuffering(); } pauseBuffering() { var _this$getBufferingPla2; (_this$getBufferingPla2 = this.getBufferingPlayer()) == null ? void 0 : _this$getBufferingPla2.pauseBuffering(); } destroy() { this.unregisterListeners(); this.stopLoad(); if (this.assetListLoader) { this.assetListLoader.destroy(); } this.emptyPlayerQueue(); this.clearScheduleState(); if (this.schedule) { this.schedule.destroy(); } this.media = this.detachedData = this.mediaSelection = this.requiredTracks = this.altSelection = this.manager = null; this.hls = this.HlsPlayerClass = this.schedule = this.log = null; this.assetListLoader = null; this.onPlay = this.onPause = this.onSeeking = this.onTimeupdate = null; this.onScheduleUpdate = null; } onDestroying() { const media = this.primaryMedia || this.media; if (media) { this.removeMediaListeners(media); } } removeMediaListeners(media) { removeEventListener(media, "play", this.onPlay); removeEventListener(media, "pause", this.onPause); removeEventListener(media, "seeking", this.onSeeking); removeEventListener(media, "timeupdate", this.onTimeupdate); } onMediaAttaching(event, data) { const media = this.media = data.media; addEventListener(media, "seeking", this.onSeeking); addEventListener(media, "timeupdate", this.onTimeupdate); addEventListener(media, "play", this.onPlay); addEventListener(media, "pause", this.onPause); } onMediaAttached(event, data) { const playingItem = this.effectivePlayingItem; const detachedMedia = this.detachedData; this.detachedData = null; if (playingItem === null) { this.checkStart(); } else if (!detachedMedia) { this.clearScheduleState(); const playingIndex = this.findItemIndex(playingItem); this.setSchedulePosition(playingIndex); } } clearScheduleState() { this.playingItem = this.bufferingItem = this.waitingItem = this.endedItem = this.playingAsset = this.endedAsset = this.bufferingAsset = null; } onMediaDetaching(event, data) { const transferringMedia = !!data.transferMedia; const media = this.media; this.media = null; if (transferringMedia) { return; } if (media) { this.removeMediaListeners(media); } if (this.detachedData) { const player = this.getBufferingPlayer(); if (player) { this.playingAsset = this.endedAsset = this.bufferingAsset = this.bufferingItem = this.waitingItem = this.detachedData = null; player.detachMedia(); } this.shouldPlay = false; } } get interstitialsManager() { if (!this.manager) { if (!this.hls) { return null; } const c = this; const effectiveBufferingItem = () => c.bufferingItem || c.waitingItem; const getAssetPlayer = (asset) => asset ? c.getAssetPlayer(asset.identifier) : asset; const getMappedTime = (item, timelineType, asset, controllerField, assetPlayerField) => { if (item) { let time = item[timelineType].start; const interstitial = item.event; if (interstitial) { if (timelineType === "playout" || interstitial.timelineOccupancy !== TimelineOccupancy.Point) { const assetPlayer = getAssetPlayer(asset); if ((assetPlayer == null ? void 0 : assetPlayer.interstitial) === interstitial) { time += assetPlayer.assetItem.startOffset + assetPlayer[assetPlayerField]; } } } else { const value = controllerField === "bufferedPos" ? getBufferedEnd() : c[controllerField]; time += value - item.start; } return time; } return 0; }; const findMappedTime = (primaryTime, timelineType) => { if (primaryTime !== 0 && timelineType !== "primary" && c.schedule.length) { var _c$schedule$items; const index = c.schedule.findItemIndexAtTime(primaryTime); const item = (_c$schedule$items = c.schedule.items) == null ? void 0 : _c$schedule$items[index]; if (item) { const diff = item[timelineType].start - item.start; return primaryTime + diff; } } return primaryTime; }; const getBufferedEnd = () => { const value = c.bufferedPos; if (value === Number.MAX_VALUE) { return getMappedDuration("primary"); } return Math.max(value, 0); }; const getMappedDuration = (timelineType) => { var _c$primaryDetails; if ((_c$primaryDetails = c.primaryDetails) != null && _c$primaryDetails.live) { return c.primaryDetails.edge; } return c.schedule.durations[timelineType]; }; const seekTo = (time, timelineType) => { var _item$event, _c$schedule$items2; const item = c.effectivePlayingItem; if (item != null && (_item$event = item.event) != null && _item$event.restrictions.skip) { return; } c.log(`seek to ${time} "${timelineType}"`); const playingItem = c.effectivePlayingItem; const targetIndex = c.schedule.findItemIndexAtTime(time, timelineType); const targetItem = (_c$schedule$items2 = c.schedule.items) == null ? void 0 : _c$schedule$items2[targetIndex]; const bufferingPlayer = c.getBufferingPlayer(); const bufferingInterstitial = bufferingPlayer == null ? void 0 : bufferingPlayer.interstitial; const appendInPlace = bufferingInterstitial == null ? void 0 : bufferingInterstitial.appendInPlace; const seekInItem = playingItem && c.itemsMatch(playingItem, targetItem); if (playingItem && (appendInPlace || seekInItem)) { const assetPlayer = getAssetPlayer(c.playingAsset); const media = (assetPlayer == null ? void 0 : assetPlayer.media) || c.primaryMedia; if (media) { const currentTime = timelineType === "primary" ? media.currentTime : getMappedTime(playingItem, timelineType, c.playingAsset, "timelinePos", "currentTime"); const diff = time - currentTime; const seekToTime = (appendInPlace ? currentTime : media.currentTime) + diff; if (seekToTime >= 0 && (!assetPlayer || appendInPlace || seekToTime <= assetPlayer.duration)) { media.currentTime = seekToTime; return; } } } if (targetItem) { let seekToTime = time; if (timelineType !== "primary") { const primarySegmentStart = targetItem[timelineType].start; const diff = time - primarySegmentStart; seekToTime = targetItem.start + diff; } const targetIsPrimary = !c.isInterstitial(targetItem); if ((!c.isInterstitial(playingItem) || playingItem.event.appendInPlace) && (targetIsPrimary || targetItem.event.appendInPlace)) { const media = c.media || (appendInPlace ? bufferingPlayer == null ? void 0 : bufferingPlayer.media : null); if (media) { media.currentTime = seekToTime; } } else if (playingItem) { const playingIndex = c.findItemIndex(playingItem); if (targetIndex > playingIndex) { const jumpIndex = c.schedule.findJumpRestrictedIndex(playingIndex + 1, targetIndex); if (jumpIndex > playingIndex) { c.setSchedulePosition(jumpIndex); return; } } let assetIndex = 0; if (targetIsPrimary) { c.timelinePos = seekToTime; c.checkBuffer(); } else { var _targetItem$event; const assetList = targetItem == null ? void 0 : (_targetItem$event = targetItem.event) == null ? void 0 : _targetItem$event.assetList; if (assetList) { const eventTime = time - (targetItem[timelineType] || targetItem).start; for (let i = assetList.length; i--; ) { const asset = assetList[i]; if (asset.duration && eventTime >= asset.startOffset && eventTime < asset.startOffset + asset.duration) { assetIndex = i; break; } } } } c.setSchedulePosition(targetIndex, assetIndex); } } }; const getActiveInterstitial = () => { const playingItem = c.effectivePlayingItem; if (c.isInterstitial(playingItem)) { return playingItem; } const bufferingItem = effectiveBufferingItem(); if (c.isInterstitial(bufferingItem)) { return bufferingItem; } return null; }; const interstitialPlayer = { get currentTime() { const interstitialItem = getActiveInterstitial(); const playingItem = c.effectivePlayingItem; if (playingItem && playingItem === interstitialItem) { return getMappedTime(playingItem, "playout", c.effectivePlayingAsset, "timelinePos", "currentTime") - playingItem.playout.start; } return 0; }, set currentTime(time) { const interstitialItem = getActiveInterstitial(); const playingItem = c.effectivePlayingItem; if (playingItem && playingItem === interstitialItem) { seekTo(time + playingItem.playout.start, "playout"); } }, get duration() { const interstitialItem = getActiveInterstitial(); if (interstitialItem) { return interstitialItem.playout.end - interstitialItem.playout.start; } return 0; }, get assetPlayers() { var _getActiveInterstitia; const assetList = (_getActiveInterstitia = getActiveInterstitial()) == null ? void 0 : _getActiveInterstitia.event.assetList; if (assetList) { return assetList.map((asset) => c.getAssetPlayer(asset.identifier)); } return []; }, get playingIndex() { var _getActiveInterstitia2; const interstitial = (_getActiveInterstitia2 = getActiveInterstitial()) == null ? void 0 : _getActiveInterstitia2.event; if (interstitial && c.effectivePlayingAsset) { return interstitial.findAssetIndex(c.effectivePlayingAsset); } return -1; }, get scheduleItem() { return getActiveInterstitial(); } }; this.manager = { get events() { var _c$schedule, _c$schedule$events; return ((_c$schedule = c.schedule) == null ? void 0 : (_c$schedule$events = _c$schedule.events) == null ? void 0 : _c$schedule$events.slice(0)) || []; }, get schedule() { var _c$schedule2, _c$schedule2$items; return ((_c$schedule2 = c.schedule) == null ? void 0 : (_c$schedule2$items = _c$schedule2.items) == null ? void 0 : _c$schedule2$items.slice(0)) || []; }, get interstitialPlayer() { if (getActiveInterstitial()) { return interstitialPlayer; } return null; }, get playerQueue() { return c.playerQueue.slice(0); }, get bufferingAsset() { return c.bufferingAsset; }, get bufferingItem() { return effectiveBufferingItem(); }, get bufferingIndex() { const item = effectiveBufferingItem(); return c.findItemIndex(item); }, get playingAsset() { return c.effectivePlayingAsset; }, get playingItem() { return c.effectivePlayingItem; }, get playingIndex() { const item = c.effectivePlayingItem; return c.findItemIndex(item); }, primary: { get bufferedEnd() { return getBufferedEnd(); }, get currentTime() { const timelinePos = c.timelinePos; return timelinePos > 0 ? timelinePos : 0; }, set currentTime(time) { seekTo(time, "primary"); }, get duration() { return getMappedDuration("primary"); }, get seekableStart() { var _c$primaryDetails2; return ((_c$primaryDetails2 = c.primaryDetails) == null ? void 0 : _c$primaryDetails2.fragmentStart) || 0; } }, integrated: { get bufferedEnd() { return getMappedTime(effectiveBufferingItem(), "integrated", c.bufferingAsset, "bufferedPos", "bufferedEnd"); }, get currentTime() { return getMappedTime(c.effectivePlayingItem, "integrated", c.effectivePlayingAsset, "timelinePos", "currentTime"); }, set currentTime(time) { seekTo(time, "integrated"); }, get duration() { return getMappedDuration("integrated"); }, get seekableStart() { var _c$primaryDetails3; return findMappedTime(((_c$primaryDetails3 = c.primaryDetails) == null ? void 0 : _c$primaryDetails3.fragmentStart) || 0, "integrated"); } }, skip: () => { const item = c.effectivePlayingItem; const event = item == null ? void 0 : item.event; if (event && !event.restrictions.skip) { const index = c.findItemIndex(item); if (event.appendInPlace) { const time = item.playout.start + item.event.duration; seekTo(time + 1e-3, "playout"); } else { c.advanceAfterAssetEnded(event, index, Infinity); } } } }; } return this.manager; } // Schedule getters get effectivePlayingItem() { return this.waitingItem || this.playingItem || this.endedItem; } get effectivePlayingAsset() { return this.playingAsset || this.endedAsset; } get playingLastItem() { var _this$schedule; const playingItem = this.playingItem; const items = (_this$schedule = this.schedule) == null ? void 0 : _this$schedule.items; if (!this.playbackStarted || !playingItem || !items) { return false; } return this.findItemIndex(playingItem) === items.length - 1; } get playbackStarted() { return this.effectivePlayingItem !== null; } // Media getters and event callbacks get currentTime() { var _this$bufferingItem, _this$bufferingItem$e, _media; if (this.mediaSelection === null) { return void 0; } const queuedForPlayback = this.waitingItem || this.playingItem; if (this.isInterstitial(queuedForPlayback) && !queuedForPlayback.event.appendInPlace) { return void 0; } let media = this.media; if (!media && (_this$bufferingItem = this.bufferingItem) != null && (_this$bufferingItem$e = _this$bufferingItem.event) != null && _this$bufferingItem$e.appendInPlace) { media = this.primaryMedia; } const currentTime = (_media = media) == null ? void 0 : _media.currentTime; if (currentTime === void 0 || !isFiniteNumber(currentTime)) { return void 0; } return currentTime; } get primaryMedia() { var _this$detachedData; return this.media || ((_this$detachedData = this.detachedData) == null ? void 0 : _this$detachedData.media) || null; } isInterstitial(item) { return !!(item != null && item.event); } retreiveMediaSource(assetId, toSegment) { const player = this.getAssetPlayer(assetId); if (player) { this.transferMediaFromPlayer(player, toSegment); } } transferMediaFromPlayer(player, toSegment) { const appendInPlace = player.interstitial.appendInPlace; const playerMedia = player.media; if (appendInPlace && playerMedia === this.primaryMedia) { this.bufferingAsset = null; if (!toSegment || this.isInterstitial(toSegment) && !toSegment.event.appendInPlace) { if (toSegment && playerMedia) { this.detachedData = { media: playerMedia }; return; } } const attachMediaSourceData = player.transferMedia(); this.log(`transfer MediaSource from ${player} ${stringify(attachMediaSourceData)}`); this.detachedData = attachMediaSourceData; } else if (toSegment && playerMedia) { this.shouldPlay || (this.shouldPlay = !playerMedia.paused); } } transferMediaTo(player, media) { var _this$detachedData2, _attachMediaSourceDat; if (player.media === media) { return; } let attachMediaSourceData = null; const primaryPlayer = this.hls; const isAssetPlayer = player !== primaryPlayer; const appendInPlace = isAssetPlayer && player.interstitial.appendInPlace; const detachedMediaSource = (_this$detachedData2 = this.detachedData) == null ? void 0 : _this$detachedData2.mediaSource; let logFromSource; if (primaryPlayer.media) { if (appendInPlace) { attachMediaSourceData = primaryPlayer.transferMedia(); this.detachedData = attachMediaSourceData; } logFromSource = `Primary`; } else if (detachedMediaSource) { const bufferingPlayer = this.getBufferingPlayer(); if (bufferingPlayer) { attachMediaSourceData = bufferingPlayer.transferMedia(); logFromSource = `${bufferingPlayer}`; } else { logFromSource = `detached MediaSource`; } } else { logFromSource = `detached media`; } if (!attachMediaSourceData) { if (detachedMediaSource) { attachMediaSourceData = this.detachedData; this.log(`using detachedData: MediaSource ${stringify(attachMediaSourceData)}`); } else if (!this.detachedData || primaryPlayer.media === media) { const playerQueue = this.playerQueue; if (playerQueue.length > 1) { playerQueue.forEach((queuedPlayer) => { if (isAssetPlayer && queuedPlayer.interstitial.appendInPlace !== appendInPlace) { const interstitial = queuedPlayer.interstitial; this.clearInterstitial(queuedPlayer.interstitial, null); interstitial.appendInPlace = false; if (interstitial.appendInPlace) { this.warn(`Could not change append strategy for queued assets ${interstitial}`); } } }); } this.hls.detachMedia(); this.detachedData = { media }; } } const transferring = attachMediaSourceData && "mediaSource" in attachMediaSourceData && ((_attachMediaSourceDat = attachMediaSourceData.mediaSource) == null ? void 0 : _attachMediaSourceDat.readyState) !== "closed"; const dataToAttach = transferring && attachMediaSourceData ? attachMediaSourceData : media; this.log(`${transferring ? "transfering MediaSource" : "attaching media"} to ${isAssetPlayer ? player : "Primary"} from ${logFromSource}`); if (dataToAttach === attachMediaSourceData) { const isAssetAtEndOfSchedule = isAssetPlayer && player.assetId === this.schedule.assetIdAtEnd; dataToAttach.overrides = { duration: this.schedule.duration, endOfStream: !isAssetPlayer || isAssetAtEndOfSchedule, cueRemoval: !isAssetPlayer }; } player.attachMedia(dataToAttach); } onInterstitialCueEnter() { this.onTimeupdate(); } // Scheduling methods checkStart() { const schedule = this.schedule; const interstitialEvents = schedule.events; if (!interstitialEvents || this.playbackDisabled || !this.media) { return; } if (this.bufferedPos === -1) { this.bufferedPos = 0; } const timelinePos = this.timelinePos; const effectivePlayingItem = this.effectivePlayingItem; if (timelinePos === -1) { const startPosition = this.hls.startPosition; this.timelinePos = startPosition; if (interstitialEvents.length && interstitialEvents[0].cue.pre) { const index = schedule.findEventIndex(interstitialEvents[0].identifier); this.setSchedulePosition(index); } else if (startPosition >= 0 || !this.primaryLive) { const start = this.timelinePos = startPosition > 0 ? startPosition : 0; const index = schedule.findItemIndexAtTime(start); this.setSchedulePosition(index); } } else if (effectivePlayingItem && !this.playingItem) { const index = schedule.findItemIndex(effectivePlayingItem); this.setSchedulePosition(index); } } advanceAfterAssetEnded(interstitial, index, assetListIndex) { const nextAssetIndex = assetListIndex + 1; if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex) && !interstitial.assetList[nextAssetIndex].error) { this.setSchedulePosition(index, nextAssetIndex); } else { const scheduleItems = this.schedule.items; if (scheduleItems) { const nextIndex = index + 1; const scheduleLength = scheduleItems.length; if (nextIndex >= scheduleLength) { this.setSchedulePosition(-1); return; } const resumptionTime = interstitial.resumeTime; if (this.timelinePos < resumptionTime) { this.timelinePos = resumptionTime; this.checkBuffer(); } this.setSchedulePosition(nextIndex); } } } setScheduleToAssetAtTime(time, playingAsset) { const schedule = this.schedule; const parentIdentifier = playingAsset.parentIdentifier; const interstitial = schedule.getEvent(parentIdentifier); if (interstitial) { const itemIndex = schedule.findEventIndex(parentIdentifier); const assetListIndex = schedule.findAssetIndex(interstitial, time); this.setSchedulePosition(itemIndex, assetListIndex); } } setSchedulePosition(index, assetListIndex) { const scheduleItems = this.schedule.items; if (!scheduleItems || this.playbackDisabled) { return; } this.log(`setSchedulePosition ${index}, ${assetListIndex}`); const scheduledItem = index >= 0 ? scheduleItems[index] : null; const currentItem = this.playingItem; const playingLastItem = this.playingLastItem; if (this.isInterstitial(currentItem)) { var _interstitial$assetLi; const interstitial = currentItem.event; const playingAsset = this.playingAsset; const assetId = playingAsset == null ? void 0 : playingAsset.identifier; const player = assetId ? this.getAssetPlayer(assetId) : null; if (player && assetId && (!this.eventItemsMatch(currentItem, scheduledItem) || assetListIndex !== void 0 && assetId !== ((_interstitial$assetLi = interstitial.assetList) == null ? void 0 : _interstitial$assetLi[assetListIndex].identifier))) { var _this$detachedData3; const _assetListIndex = interstitial.findAssetIndex(playingAsset); this.log(`INTERSTITIAL_ASSET_ENDED ${_assetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`); this.endedAsset = playingAsset; this.playingAsset = null; this.hls.trigger(Events.INTERSTITIAL_ASSET_ENDED, { asset: playingAsset, assetListIndex: _assetListIndex, event: interstitial, schedule: scheduleItems.slice(0), scheduleIndex: index, player }); this.retreiveMediaSource(assetId, scheduledItem); if (player.media && !((_this$detachedData3 = this.detachedData) != null && _this$detachedData3.mediaSource)) { player.detachMedia(); } } if (!this.eventItemsMatch(currentItem, scheduledItem)) { this.endedItem = currentItem; this.playingItem = null; this.log(`INTERSTITIAL_ENDED ${interstitial} ${segmentToString(currentItem)}`); interstitial.hasPlayed = true; this.hls.trigger(Events.INTERSTITIAL_ENDED, { event: interstitial, schedule: scheduleItems.slice(0), scheduleIndex: index }); if (interstitial.cue.once) { this.updateSchedule(); const items = this.schedule.items; if (scheduledItem && items) { const updatedIndex = this.schedule.findItemIndex(scheduledItem); this.advanceSchedule(updatedIndex, items, assetListIndex, currentItem, playingLastItem); } return; } } } this.advanceSchedule(index, scheduleItems, assetListIndex, currentItem, playingLastItem); } advanceSchedule(index, scheduleItems, assetListIndex, currentItem, playedLastItem) { const scheduledItem = index >= 0 ? scheduleItems[index] : null; const media = this.primaryMedia; const playerQueue = this.playerQueue; if (playerQueue.length) { playerQueue.forEach((player) => { const interstitial = player.interstitial; const queuedIndex = this.schedule.findEventIndex(interstitial.identifier); if (queuedIndex < index || queuedIndex > index + 1) { this.clearInterstitial(interstitial, scheduledItem); } }); } if (this.isInterstitial(scheduledItem)) { this.timelinePos = Math.min(Math.max(this.timelinePos, scheduledItem.start), scheduledItem.end); const interstitial = scheduledItem.event; if (assetListIndex === void 0) { assetListIndex = this.schedule.findAssetIndex(interstitial, this.timelinePos); } const waitingItem = this.waitingItem; if (!this.assetsBuffered(scheduledItem, media)) { this.setBufferingItem(scheduledItem); } let player = this.preloadAssets(interstitial, assetListIndex); if (!this.eventItemsMatch(scheduledItem, waitingItem || currentItem)) { this.waitingItem = scheduledItem; this.log(`INTERSTITIAL_STARTED ${segmentToString(scheduledItem)} ${interstitial.appendInPlace ? "append in place" : ""}`); this.hls.trigger(Events.INTERSTITIAL_STARTED, { event: interstitial, schedule: scheduleItems.slice(0), scheduleIndex: index }); } if (!interstitial.assetListLoaded) { this.log(`Waiting for ASSET-LIST to complete loading ${interstitial}`); return; } if (interstitial.assetListLoader) { interstitial.assetListLoader.destroy(); interstitial.assetListLoader = void 0; } if (!media) { this.log(`Waiting for attachMedia to start Interstitial ${interstitial}`); return; } this.waitingItem = this.endedItem = null; this.playingItem = scheduledItem; const assetItem = interstitial.assetList[assetListIndex]; if (!assetItem) { const nextItem = scheduleItems[index + 1]; const _media2 = this.media; if (nextItem && _media2 && !this.isInterstitial(nextItem) && _media2.currentTime < nextItem.start) { _media2.currentTime = this.timelinePos = nextItem.start; } this.advanceAfterAssetEnded(interstitial, index, assetListIndex || 0); return; } if (!player) { player = this.getAssetPlayer(assetItem.identifier); } if (player === null || player.destroyed) { const assetListLength = interstitial.assetList.length; this.warn(`asset ${assetListIndex + 1}/${assetListLength} player destroyed ${interstitial}`); player = this.createAssetPlayer(interstitial, assetItem, assetListIndex); } if (!this.eventItemsMatch(scheduledItem, this.bufferingItem)) { if (interstitial.appendInPlace && this.isAssetBuffered(assetItem)) { return; } } this.startAssetPlayer(player, assetListIndex, scheduleItems, index, media); if (this.shouldPlay) { playWithCatch(player.media); } } else if (scheduledItem !== null) { this.resumePrimary(scheduledItem, index, currentItem); if (this.shouldPlay) { playWithCatch(this.hls.media); } } else if (playedLastItem && this.isInterstitial(currentItem)) { this.endedItem = null; this.playingItem = currentItem; if (!currentItem.event.appendInPlace) { this.attachPrimary(this.schedule.durations.primary, null); } } } get playbackDisabled() { return this.hls.config.enableInterstitialPlayback === false; } get primaryDetails() { var _this$mediaSelection, _this$mediaSelection$; return (_this$mediaSelection = this.mediaSelection) == null ? void 0 : (_this$mediaSelection$ = _this$mediaSelection.main) == null ? void 0 : _this$mediaSelection$.details; } get primaryLive() { var _this$primaryDetails; return !!((_this$primaryDetails = this.primaryDetails) != null && _this$primaryDetails.live); } resumePrimary(scheduledItem, index, fromItem) { var _this$detachedData4; this.playingItem = scheduledItem; this.playingAsset = this.endedAsset = null; this.waitingItem = this.endedItem = null; this.bufferedToItem(scheduledItem); this.log(`resuming ${segmentToString(scheduledItem)}`); if (!((_this$detachedData4 = this.detachedData) != null && _this$detachedData4.mediaSource)) { let timelinePos = this.timelinePos; if (timelinePos < scheduledItem.start || timelinePos >= scheduledItem.end) { timelinePos = this.getPrimaryResumption(scheduledItem, index); this.timelinePos = timelinePos; } this.attachPrimary(timelinePos, scheduledItem); } if (!fromItem) { return; } const scheduleItems = this.schedule.items; if (!scheduleItems) { return; } this.log(`resumed ${segmentToString(scheduledItem)}`); this.hls.trigger(Events.INTERSTITIALS_PRIMARY_RESUMED, { schedule: scheduleItems.slice(0), scheduleIndex: index }); this.checkBuffer(); } getPrimaryResumption(scheduledItem, index) { const itemStart = scheduledItem.start; if (this.primaryLive) { const details = this.primaryDetails; if (index === 0) { return this.hls.startPosition; } else if (details && (itemStart < details.fragmentStart || itemStart > details.edge)) { return this.hls.liveSyncPosition || -1; } } return itemStart; } isAssetBuffered(asset) { const player = this.getAssetPlayer(asset.identifier); if (player != null && player.hls) { return player.hls.bufferedToEnd; } const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, this.timelinePos, 0); return bufferInfo.end + 1 >= asset.timelineStart + (asset.duration || 0); } attachPrimary(timelinePos, item, skipSeekToStartPosition) { if (item) { this.setBufferingItem(item); } else { this.bufferingItem = this.playingItem; } this.bufferingAsset = null; const media = this.primaryMedia; if (!media) { return; } const hls = this.hls; if (hls.media) { this.checkBuffer(); } else { this.transferMediaTo(hls, media); if (skipSeekToStartPosition) { this.startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition); } } if (!skipSeekToStartPosition) { this.timelinePos = timelinePos; this.startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition); } } startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition) { var _hls$mainForwardBuffe; const hls = this.hls; if (!hls.loadingEnabled || !hls.media || Math.abs((((_hls$mainForwardBuffe = hls.mainForwardBufferInfo) == null ? void 0 : _hls$mainForwardBuffe.start) || hls.media.currentTime) - timelinePos) > 0.5) { hls.startLoad(timelinePos, skipSeekToStartPosition); } else if (!hls.bufferingEnabled) { hls.resumeBuffering(); } } // HLS.js event callbacks onManifestLoading() { this.stopLoad(); this.schedule.reset(); this.emptyPlayerQueue(); this.clearScheduleState(); this.shouldPlay = false; this.bufferedPos = this.timelinePos = -1; this.mediaSelection = this.altSelection = this.manager = this.requiredTracks = null; this.hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); this.hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); } onLevelUpdated(event, data) { if (data.level === -1) { return; } const main = this.hls.levels[data.level]; const currentSelection = _objectSpread2(_objectSpread2({}, this.mediaSelection || this.altSelection), {}, { main }); this.mediaSelection = currentSelection; this.schedule.parseInterstitialDateRanges(currentSelection, this.hls.config.interstitialAppendInPlace); if (!this.effectivePlayingItem && this.schedule.items) { this.checkStart(); } } onAudioTrackUpdated(event, data) { const audio = this.hls.audioTracks[data.id]; const previousSelection = this.mediaSelection; if (!previousSelection) { this.altSelection = _objectSpread2(_objectSpread2({}, this.altSelection), {}, { audio }); return; } const currentSelection = _objectSpread2(_objectSpread2({}, previousSelection), {}, { audio }); this.mediaSelection = currentSelection; } onSubtitleTrackUpdated(event, data) { const subtitles = this.hls.subtitleTracks[data.id]; const previousSelection = this.mediaSelection; if (!previousSelection) { this.altSelection = _objectSpread2(_objectSpread2({}, this.altSelection), {}, { subtitles }); return; } const currentSelection = _objectSpread2(_objectSpread2({}, previousSelection), {}, { subtitles }); this.mediaSelection = currentSelection; } onAudioTrackSwitching(event, data) { const audioOption = getBasicSelectionOption(data); this.playerQueue.forEach((player) => player.hls.setAudioOption(data) || player.hls.setAudioOption(audioOption)); } onSubtitleTrackSwitch(event, data) { const subtitleOption = getBasicSelectionOption(data); this.playerQueue.forEach((player) => player.hls.setSubtitleOption(data) || data.id !== -1 && player.hls.setSubtitleOption(subtitleOption)); } onBufferCodecs(event, data) { const requiredTracks = data.tracks; if (requiredTracks) { this.requiredTracks = requiredTracks; } } onBufferAppended(event, data) { this.checkBuffer(); } onBufferFlushed(event, data) { const playingItem = this.playingItem; if (playingItem && !this.itemsMatch(playingItem, this.bufferingItem) && !this.isInterstitial(playingItem)) { const timelinePos = this.timelinePos; this.bufferedPos = timelinePos; this.checkBuffer(); } } onBufferedToEnd(event) { const interstitialEvents = this.schedule.events; if (this.bufferedPos < Number.MAX_VALUE && interstitialEvents) { for (let i = 0; i < interstitialEvents.length; i++) { const interstitial = interstitialEvents[i]; if (interstitial.cue.post) { var _this$schedule$items; const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier); const item = (_this$schedule$items = this.schedule.items) == null ? void 0 : _this$schedule$items[scheduleIndex]; if (this.isInterstitial(item) && this.eventItemsMatch(item, this.bufferingItem)) { this.bufferedToItem(item, 0); } break; } } this.bufferedPos = Number.MAX_VALUE; } } onMediaEnded(event) { const playingItem = this.playingItem; if (!this.playingLastItem && playingItem) { const playingIndex = this.findItemIndex(playingItem); this.setSchedulePosition(playingIndex + 1); } else { this.shouldPlay = false; } } updateItem(previousItem, time) { const items = this.schedule.items; if (previousItem && items) { const index = this.findItemIndex(previousItem, time); return items[index] || null; } return null; } itemsMatch(a, b) { return !!b && (a === b || a.event && b.event && this.eventItemsMatch(a, b) || !a.event && !b.event && this.findItemIndex(a) === this.findItemIndex(b)); } eventItemsMatch(a, b) { var _b$event; return !!b && (a === b || a.event.identifier === ((_b$event = b.event) == null ? void 0 : _b$event.identifier)); } findItemIndex(item, time) { return item ? this.schedule.findItemIndex(item, time) : -1; } updateSchedule() { const mediaSelection = this.mediaSelection; if (!mediaSelection) { return; } this.schedule.updateSchedule(mediaSelection, []); } // Schedule buffer control checkBuffer(starved) { const items = this.schedule.items; if (!items) { return; } const bufferInfo = BufferHelper.bufferInfo(this.primaryMedia, this.timelinePos, 0); if (starved) { this.bufferedPos = this.timelinePos; } starved || (starved = bufferInfo.len < 1); this.updateBufferedPos(bufferInfo.end, items, starved); } updateBufferedPos(bufferEnd, items, bufferIsEmpty) { const schedule = this.schedule; const bufferingItem = this.bufferingItem; if (this.bufferedPos > bufferEnd) { return; } if (items.length === 1 && this.itemsMatch(items[0], bufferingItem)) { this.bufferedPos = bufferEnd; return; } const playingItem = this.playingItem; const playingIndex = this.findItemIndex(playingItem); let bufferEndIndex = schedule.findItemIndexAtTime(bufferEnd); if (this.bufferedPos < bufferEnd) { var _nextItemToBuffer$eve, _bufferingItem$event; const bufferingIndex = this.findItemIndex(bufferingItem); const nextToBufferIndex = Math.min(bufferingIndex + 1, items.length - 1); const nextItemToBuffer = items[nextToBufferIndex]; if (bufferEndIndex === -1 && bufferingItem && bufferEnd >= bufferingItem.end || (_nextItemToBuffer$eve = nextItemToBuffer.event) != null && _nextItemToBuffer$eve.appendInPlace && bufferEnd + 0.01 >= nextItemToBuffer.start) { bufferEndIndex = nextToBufferIndex; } if (nextToBufferIndex - playingIndex > 1 && (bufferingItem == null ? void 0 : (_bufferingItem$event = bufferingItem.event) == null ? void 0 : _bufferingItem$event.appendInPlace) === false) { return; } this.bufferedPos = bufferEnd; if (bufferEndIndex > bufferingIndex && bufferEndIndex > playingIndex) { this.bufferedToItem(nextItemToBuffer); } else { const details = this.primaryDetails; if (this.primaryLive && details && bufferEnd > details.edge - details.targetduration && nextItemToBuffer.start < details.edge + this.hls.config.interstitialLiveLookAhead && this.isInterstitial(nextItemToBuffer)) { this.preloadAssets(nextItemToBuffer.event, 0); } } } else if (bufferIsEmpty && playingItem && !this.itemsMatch(playingItem, bufferingItem)) { if (bufferEndIndex === playingIndex) { this.bufferedToItem(playingItem); } else if (bufferEndIndex === playingIndex + 1) { this.bufferedToItem(items[bufferEndIndex]); } } } assetsBuffered(item, media) { const assetList = item.event.assetList; if (assetList.length === 0) { return false; } return !item.event.assetList.some((asset) => { const player = this.getAssetPlayer(asset.identifier); return !(player != null && player.bufferedInPlaceToEnd(media)); }); } setBufferingItem(item) { const bufferingLast = this.bufferingItem; const schedule = this.schedule; if (!this.itemsMatch(item, bufferingLast)) { const { items, events } = schedule; if (!items || !events) { return bufferingLast; } const isInterstitial = this.isInterstitial(item); const bufferingPlayer = this.getBufferingPlayer(); this.bufferingItem = item; this.bufferedPos = Math.max(item.start, Math.min(item.end, this.timelinePos)); if (!this.playbackDisabled) { const timeRemaining = bufferingPlayer ? bufferingPlayer.remaining : bufferingLast ? bufferingLast.end - this.timelinePos : 0; this.log(`buffered to boundary ${segmentToString(item)}` + (bufferingLast ? ` (${timeRemaining.toFixed(2)} remaining)` : "")); if (isInterstitial) { item.event.assetList.forEach((asset) => { const player = this.getAssetPlayer(asset.identifier); if (player) { player.resumeBuffering(); } }); } else { this.hls.resumeBuffering(); this.playerQueue.forEach((player) => player.pauseBuffering()); } } this.hls.trigger(Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY, { events: events.slice(0), schedule: items.slice(0), bufferingIndex: this.findItemIndex(item), playingIndex: this.findItemIndex(this.playingItem) }); } else if (this.bufferingItem !== item) { this.bufferingItem = item; } return bufferingLast; } bufferedToItem(item, assetListIndex = 0) { const bufferingLast = this.setBufferingItem(item); if (this.playbackDisabled) { return; } if (this.isInterstitial(item)) { this.bufferedToEvent(item, assetListIndex); } else if (bufferingLast !== null) { this.bufferingAsset = null; const detachedData = this.detachedData; if (detachedData) { if (detachedData.mediaSource) { const skipSeekToStartPosition = true; this.attachPrimary(item.start, item, skipSeekToStartPosition); } else { this.preloadPrimary(item); } } else { this.preloadPrimary(item); } } } preloadPrimary(item) { const index = this.findItemIndex(item); const timelinePos = this.getPrimaryResumption(item, index); this.startLoadingPrimaryAt(timelinePos); } bufferedToEvent(item, assetListIndex) { const interstitial = item.event; const neverLoaded = interstitial.assetList.length === 0 && !interstitial.assetListLoader; const playOnce = interstitial.cue.once; if (neverLoaded || !playOnce) { const player = this.preloadAssets(interstitial, assetListIndex); if (player != null && player.interstitial.appendInPlace) { const assetItem = interstitial.assetList[assetListIndex]; const media = this.primaryMedia; if (assetItem && media) { this.bufferAssetPlayer(player, media); } } } } preloadAssets(interstitial, assetListIndex) { const uri = interstitial.assetUrl; const assetListLength = interstitial.assetList.length; const neverLoaded = assetListLength === 0 && !interstitial.assetListLoader; const playOnce = interstitial.cue.once; if (neverLoaded) { const timelineStart = interstitial.timelineStart; if (interstitial.appendInPlace) { var _playingItem$nextEven; const playingItem = this.playingItem; if (!this.isInterstitial(playingItem) && (playingItem == null ? void 0 : (_playingItem$nextEven = playingItem.nextEvent) == null ? void 0 : _playingItem$nextEven.identifier) === interstitial.identifier) { this.flushFrontBuffer(timelineStart + 0.25); } } let hlsStartOffset; let liveStartPosition = 0; if (!this.playingItem && this.primaryLive) { liveStartPosition = this.hls.startPosition; if (liveStartPosition === -1) { liveStartPosition = this.hls.liveSyncPosition || 0; } } if (liveStartPosition && !(interstitial.cue.pre || interstitial.cue.post)) { const startOffset = liveStartPosition - timelineStart; if (startOffset > 0) { hlsStartOffset = Math.round(startOffset * 1e3) / 1e3; } } this.log(`Load interstitial asset ${assetListIndex + 1}/${uri ? 1 : assetListLength} ${interstitial}${hlsStartOffset ? ` live-start: ${liveStartPosition} start-offset: ${hlsStartOffset}` : ""}`); if (uri) { return this.createAsset(interstitial, 0, 0, timelineStart, interstitial.duration, uri); } const assetListLoader = this.assetListLoader.loadAssetList(interstitial, hlsStartOffset); if (assetListLoader) { interstitial.assetListLoader = assetListLoader; } } else if (!playOnce && assetListLength) { for (let i = assetListIndex; i < assetListLength; i++) { const asset = interstitial.assetList[i]; const playerIndex = this.getAssetPlayerQueueIndex(asset.identifier); if ((playerIndex === -1 || this.playerQueue[playerIndex].destroyed) && !asset.error) { this.createAssetPlayer(interstitial, asset, i); } } return this.getAssetPlayer(interstitial.assetList[assetListIndex].identifier); } return null; } flushFrontBuffer(startOffset) { const requiredTracks = this.requiredTracks; if (!requiredTracks) { return; } this.log(`Removing front buffer starting at ${startOffset}`); const sourceBufferNames = Object.keys(requiredTracks); sourceBufferNames.forEach((type) => { this.hls.trigger(Events.BUFFER_FLUSHING, { startOffset, endOffset: Infinity, type }); }); } // Interstitial Asset Player control getAssetPlayerQueueIndex(assetId) { const playerQueue = this.playerQueue; for (let i = 0; i < playerQueue.length; i++) { if (assetId === playerQueue[i].assetId) { return i; } } return -1; } getAssetPlayer(assetId) { const index = this.getAssetPlayerQueueIndex(assetId); return this.playerQueue[index] || null; } getBufferingPlayer() { const { playerQueue, primaryMedia } = this; if (primaryMedia) { for (let i = 0; i < playerQueue.length; i++) { if (playerQueue[i].media === primaryMedia) { return playerQueue[i]; } } } return null; } createAsset(interstitial, assetListIndex, startOffset, timelineStart, duration, uri) { const assetItem = { parentIdentifier: interstitial.identifier, identifier: generateAssetIdentifier(interstitial, uri, assetListIndex), duration, startOffset, timelineStart, uri }; return this.createAssetPlayer(interstitial, assetItem, assetListIndex); } createAssetPlayer(interstitial, assetItem, assetListIndex) { this.log(`create HLSAssetPlayer for ${eventAssetToString(assetItem)}`); const primary = this.hls; const userConfig = primary.userConfig; let videoPreference = userConfig.videoPreference; const currentLevel = primary.loadLevelObj || primary.levels[primary.currentLevel]; if (videoPreference || currentLevel) { videoPreference = _extends({}, videoPreference); if (currentLevel.videoCodec) { videoPreference.videoCodec = currentLevel.videoCodec; } if (currentLevel.videoRange) { videoPreference.allowedVideoRanges = [currentLevel.videoRange]; } } const selectedAudio = primary.audioTracks[primary.audioTrack]; const selectedSubtitle = primary.subtitleTracks[primary.subtitleTrack]; let startPosition = 0; if (this.primaryLive || interstitial.appendInPlace) { const timePastStart = this.timelinePos - assetItem.timelineStart; if (timePastStart > 1) { const duration = assetItem.duration; if (duration && timePastStart < duration) { startPosition = timePastStart; } } } const assetId = assetItem.identifier; const playerConfig = _objectSpread2(_objectSpread2({}, userConfig), {}, { autoStartLoad: true, startFragPrefetch: true, primarySessionId: primary.sessionId, assetPlayerId: assetId, abrEwmaDefaultEstimate: primary.bandwidthEstimate, interstitialsController: void 0, startPosition, liveDurationInfinity: false, testBandwidth: false, videoPreference, audioPreference: selectedAudio || userConfig.audioPreference, subtitlePreference: selectedSubtitle || userConfig.subtitlePreference }); if (interstitial.appendInPlace) { interstitial.appendInPlaceStarted = true; if (assetItem.timelineStart) { playerConfig.timelineOffset = assetItem.timelineStart; } } const cmcd = playerConfig.cmcd; if (cmcd != null && cmcd.sessionId && cmcd.contentId) { playerConfig.cmcd = _extends({}, cmcd, { contentId: hash(assetItem.uri) }); } if (this.getAssetPlayer(assetId)) { this.warn(`Duplicate date range identifier ${interstitial} and asset ${assetId}`); } const player = new HlsAssetPlayer(this.HlsPlayerClass, playerConfig, interstitial, assetItem); this.playerQueue.push(player); interstitial.assetList[assetListIndex] = assetItem; const updateAssetPlayerDetails = (details) => { if (details.live) { const error = new Error(`Interstitials MUST be VOD assets ${interstitial}`); const errorData = { fatal: true, type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR, error }; this.handleAssetItemError(errorData, interstitial, this.schedule.findEventIndex(interstitial.identifier), assetListIndex, error.message); return; } const duration = details.edge - details.fragmentStart; const currentAssetDuration = assetItem.duration; if (currentAssetDuration === null || duration > currentAssetDuration) { this.log(`Interstitial asset "${assetId}" duration change ${currentAssetDuration} > ${duration}`); assetItem.duration = duration; this.updateSchedule(); } }; player.on(Events.LEVEL_UPDATED, (event, { details }) => updateAssetPlayerDetails(details)); player.on(Events.LEVEL_PTS_UPDATED, (event, { details }) => updateAssetPlayerDetails(details)); const onBufferCodecs = (event, data) => { const inQueuPlayer = this.getAssetPlayer(assetId); if (inQueuPlayer && data.tracks) { inQueuPlayer.off(Events.BUFFER_CODECS, onBufferCodecs); inQueuPlayer.tracks = data.tracks; const media = this.primaryMedia; if (this.bufferingAsset === inQueuPlayer.assetItem && media && !inQueuPlayer.media) { this.bufferAssetPlayer(inQueuPlayer, media); } } }; player.on(Events.BUFFER_CODECS, onBufferCodecs); const bufferedToEnd = () => { var _this$schedule$items2; const inQueuPlayer = this.getAssetPlayer(assetId); this.log(`buffered to end of asset ${inQueuPlayer}`); if (!inQueuPlayer) { return; } const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier); const assetListIndex2 = interstitial.findAssetIndex(assetItem); const nextAssetIndex = assetListIndex2 + 1; const item = (_this$schedule$items2 = this.schedule.items) == null ? void 0 : _this$schedule$items2[scheduleIndex]; if (this.isInterstitial(item)) { if (assetListIndex2 !== -1 && !interstitial.isAssetPastPlayoutLimit(nextAssetIndex) && !interstitial.assetList[nextAssetIndex].error) { this.bufferedToItem(item, nextAssetIndex); } else { var _this$schedule$items3; const nextItem = (_this$schedule$items3 = this.schedule.items) == null ? void 0 : _this$schedule$items3[scheduleIndex + 1]; if (nextItem) { this.bufferedToItem(nextItem); } } } }; player.on(Events.BUFFERED_TO_END, bufferedToEnd); const endedWithAssetIndex = (assetIndex) => { return () => { const inQueuPlayer = this.getAssetPlayer(assetId); if (!inQueuPlayer) { return; } this.shouldPlay = true; const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier); this.advanceAfterAssetEnded(interstitial, scheduleIndex, assetIndex); }; }; player.once(Events.MEDIA_ENDED, endedWithAssetIndex(assetListIndex)); player.once(Events.PLAYOUT_LIMIT_REACHED, endedWithAssetIndex(Infinity)); player.on(Events.ERROR, (event, data) => { const inQueuPlayer = this.getAssetPlayer(assetId); if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) { if (inQueuPlayer != null && inQueuPlayer.media) { const assetCurrentTime = inQueuPlayer.currentTime; const distanceFromEnd = inQueuPlayer.duration - assetCurrentTime; if (assetCurrentTime && interstitial.appendInPlace && distanceFromEnd / inQueuPlayer.media.playbackRate < 0.5) { this.log(`Advancing buffer past end of asset ${assetId} ${interstitial} at ${inQueuPlayer.media.currentTime}`); bufferedToEnd(); } else { this.warn(`Stalled at ${assetCurrentTime} of ${assetCurrentTime + distanceFromEnd} in asset ${assetId} ${interstitial}`); this.onTimeupdate(); this.checkBuffer(true); } } return; } this.handleAssetItemError(data, interstitial, this.schedule.findEventIndex(interstitial.identifier), assetListIndex, `Asset player error ${data.error} ${interstitial}`); }); player.on(Events.DESTROYING, () => { const inQueuPlayer = this.getAssetPlayer(assetId); if (!inQueuPlayer) { return; } const error = new Error(`Asset player destroyed unexpectedly ${assetId}`); const errorData = { fatal: true, type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR, error }; this.handleAssetItemError(errorData, interstitial, this.schedule.findEventIndex(interstitial.identifier), assetListIndex, error.message); }); this.hls.trigger(Events.INTERSTITIAL_ASSET_PLAYER_CREATED, { asset: assetItem, assetListIndex, event: interstitial, player }); return player; } clearInterstitial(interstitial, toSegment) { interstitial.assetList.forEach((asset) => { this.clearAssetPlayer(asset.identifier, toSegment); }); interstitial.reset(); } clearAssetPlayer(assetId, toSegment) { const playerIndex = this.getAssetPlayerQueueIndex(assetId); if (playerIndex !== -1) { this.log(`clearAssetPlayer "${assetId}" toSegment: ${toSegment ? segmentToString(toSegment) : toSegment}`); const player = this.playerQueue[playerIndex]; this.transferMediaFromPlayer(player, toSegment); this.playerQueue.splice(playerIndex, 1); player.destroy(); } } emptyPlayerQueue() { let player; while (player = this.playerQueue.pop()) { player.destroy(); } this.playerQueue = []; } startAssetPlayer(player, assetListIndex, scheduleItems, scheduleIndex, media) { const { interstitial, assetItem, assetId } = player; const assetListLength = interstitial.assetList.length; const playingAsset = this.playingAsset; this.endedAsset = null; this.playingAsset = assetItem; if (!playingAsset || playingAsset.identifier !== assetId) { if (playingAsset) { this.clearAssetPlayer(playingAsset.identifier, scheduleItems[scheduleIndex]); delete playingAsset.error; } this.log(`INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${player}`); this.hls.trigger(Events.INTERSTITIAL_ASSET_STARTED, { asset: assetItem, assetListIndex, event: interstitial, schedule: scheduleItems.slice(0), scheduleIndex, player }); } this.bufferAssetPlayer(player, media); } bufferAssetPlayer(player, media) { var _this$schedule$items4, _this$detachedData5; const { interstitial, assetItem, assetId } = player; const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier); const item = (_this$schedule$items4 = this.schedule.items) == null ? void 0 : _this$schedule$items4[scheduleIndex]; if (!item) { return; } this.setBufferingItem(item); this.bufferingAsset = assetItem; const bufferingPlayer = this.getBufferingPlayer(); if (bufferingPlayer === player) { return; } const appendInPlaceNext = interstitial.appendInPlace; if (appendInPlaceNext && (bufferingPlayer == null ? void 0 : bufferingPlayer.interstitial.appendInPlace) === false) { return; } const activeTracks = (bufferingPlayer == null ? void 0 : bufferingPlayer.tracks) || ((_this$detachedData5 = this.detachedData) == null ? void 0 : _this$detachedData5.tracks) || this.requiredTracks; if (appendInPlaceNext && assetItem !== this.playingAsset) { if (!player.tracks) { return; } if (activeTracks && !isCompatibleTrackChange(activeTracks, player.tracks)) { const error = new Error(`Asset "${assetId}" SourceBuffer tracks ('${Object.keys(player.tracks)}') are not compatible with primary content tracks ('${Object.keys(activeTracks)}')`); const errorData = { fatal: true, type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR, error }; const assetListIndex = interstitial.findAssetIndex(assetItem); this.handleAssetItemError(errorData, interstitial, scheduleIndex, assetListIndex, error.message); return; } } this.transferMediaTo(player, media); } handleAssetItemError(data, interstitial, scheduleIndex, assetListIndex, errorMessage) { if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) { return; } const assetItem = interstitial.assetList[assetListIndex] || null; let player = null; if (assetItem) { const playerIndex = this.getAssetPlayerQueueIndex(assetItem.identifier); player = this.playerQueue[playerIndex] || null; } const items = this.schedule.items; const interstitialAssetError = _extends({}, data, { fatal: false, errorAction: createDoNothingErrorAction(true), asset: assetItem, assetListIndex, event: interstitial, schedule: items, scheduleIndex, player }); this.warn(`Asset item error: ${data.error}`); this.hls.trigger(Events.INTERSTITIAL_ASSET_ERROR, interstitialAssetError); if (!data.fatal) { return; } const error = new Error(errorMessage); if (assetItem) { if (this.playingAsset !== assetItem) { this.clearAssetPlayer(assetItem.identifier, null); } assetItem.error = error; } if (!interstitial.assetList.some((asset) => !asset.error)) { interstitial.error = error; } else if (interstitial.appendInPlace) { interstitial.error = error; } this.primaryFallback(interstitial); } primaryFallback(interstitial) { const flushStart = interstitial.timelineStart; const playingItem = this.effectivePlayingItem; this.updateSchedule(); if (playingItem) { this.log(`Fallback to primary from event "${interstitial.identifier}" start: ${flushStart} pos: ${this.timelinePos} playing: ${playingItem ? segmentToString(playingItem) : " "} error: ${interstitial.error}`); if (interstitial.appendInPlace) { this.attachPrimary(flushStart, null); this.flushFrontBuffer(flushStart); } let timelinePos = this.timelinePos; if (timelinePos === -1) { timelinePos = this.hls.startPosition; } const newPlayingItem = this.updateItem(playingItem, timelinePos); if (!this.itemsMatch(playingItem, newPlayingItem)) { const scheduleIndex = this.schedule.findItemIndexAtTime(timelinePos); this.setSchedulePosition(scheduleIndex); } else { this.clearInterstitial(interstitial, null); } } else { this.checkStart(); } } // Asset List loading onAssetListLoaded(event, data) { var _this$bufferingItem2; const interstitial = data.event; const interstitialId = interstitial.identifier; const assets = data.assetListResponse.ASSETS; if (!this.schedule.hasEvent(interstitialId)) { return; } const eventStart = interstitial.timelineStart; const previousDuration = interstitial.duration; let sumDuration = 0; assets.forEach((asset, assetListIndex) => { const duration = parseFloat(asset.DURATION); this.createAsset(interstitial, assetListIndex, sumDuration, eventStart + sumDuration, duration, asset.URI); sumDuration += duration; }); interstitial.duration = sumDuration; this.log(`Loaded asset-list with duration: ${sumDuration} (was: ${previousDuration}) ${interstitial}`); const waitingItem = this.waitingItem; const waitingForItem = (waitingItem == null ? void 0 : waitingItem.event.identifier) === interstitialId; this.updateSchedule(); const bufferingEvent = (_this$bufferingItem2 = this.bufferingItem) == null ? void 0 : _this$bufferingItem2.event; if (waitingForItem) { var _this$schedule$items5; const scheduleIndex = this.schedule.findEventIndex(interstitialId); const item = (_this$schedule$items5 = this.schedule.items) == null ? void 0 : _this$schedule$items5[scheduleIndex]; if (item) { if (!this.playingItem && this.timelinePos > item.end) { const index = this.schedule.findItemIndexAtTime(this.timelinePos); if (index !== scheduleIndex) { interstitial.error = new Error(`Interstitial no longer within playback range ${this.timelinePos} ${interstitial}`); this.primaryFallback(interstitial); return; } } this.setBufferingItem(item); } this.setSchedulePosition(scheduleIndex); } else if ((bufferingEvent == null ? void 0 : bufferingEvent.identifier) === interstitialId && bufferingEvent.appendInPlace) { const assetItem = interstitial.assetList[0]; const player = this.getAssetPlayer(assetItem.identifier); const media = this.primaryMedia; if (assetItem && player && media) { this.bufferAssetPlayer(player, media); } } } onError(event, data) { switch (data.details) { case ErrorDetails.ASSET_LIST_PARSING_ERROR: case ErrorDetails.ASSET_LIST_LOAD_ERROR: case ErrorDetails.ASSET_LIST_LOAD_TIMEOUT: { const interstitial = data.interstitial; if (interstitial) { this.primaryFallback(interstitial); } break; } case ErrorDetails.BUFFER_STALLED_ERROR: { this.onTimeupdate(); this.checkBuffer(true); break; } } } } const TICK_INTERVAL$2 = 500; class SubtitleStreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, "subtitle-stream-controller", PlaylistLevelType.SUBTITLE); this.currentTrackId = -1; this.tracksBuffered = []; this.mainDetails = null; this.registerListeners(); } onHandlerDestroying() { this.unregisterListeners(); super.onHandlerDestroying(); this.mainDetails = null; } registerListeners() { super.registerListeners(); const { hls } = this; hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, 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); } unregisterListeners() { super.unregisterListeners(); const { hls } = this; hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, 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); } startLoad(startPosition, skipSeekToStartPosition) { this.stopLoad(); this.state = State.IDLE; this.setInterval(TICK_INTERVAL$2); this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset; this.startPosition = skipSeekToStartPosition ? -1 : startPosition; this.tick(); } onManifestLoading() { super.onManifestLoading(); this.mainDetails = null; } onMediaDetaching(event, data) { this.tracksBuffered = []; super.onMediaDetaching(event, data); } onLevelLoaded(event, data) { this.mainDetails = data.details; } onSubtitleFragProcessed(event, data) { const { frag, success } = data; if (isMediaFragment(frag)) { this.fragPrevious = frag; } this.state = State.IDLE; if (!success) { return; } const buffered = this.tracksBuffered[this.currentTrackId]; if (!buffered) { return; } let timeRange; const fragStart = frag.start; for (let i = 0; i < buffered.length; i++) { if (fragStart >= buffered[i].start && fragStart <= buffered[i].end) { timeRange = buffered[i]; break; } } const 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); if (this.media) { this.tick(); } } onBufferFlushing(event, data) { const { startOffset, endOffset } = data; if (startOffset === 0 && endOffset !== Number.POSITIVE_INFINITY) { const endOffsetSubtitles = endOffset - 1; if (endOffsetSubtitles <= 0) { return; } data.endOffsetSubtitles = Math.max(0, endOffsetSubtitles); this.tracksBuffered.forEach((buffered) => { for (let 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); } } // If something goes wrong, proceed to next frag, if we were processing one. onError(event, data) { const 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. onSubtitleTracksUpdated(event, { subtitleTracks }) { if (this.levels && subtitleOptionsIdentical(this.levels, subtitleTracks)) { this.levels = subtitleTracks.map((mediaPlaylist) => new Level(mediaPlaylist)); return; } this.tracksBuffered = []; this.levels = subtitleTracks.map((mediaPlaylist) => { const level = new Level(mediaPlaylist); this.tracksBuffered[level.id] = []; return level; }); this.fragmentTracker.removeFragmentsInRange(0, Number.POSITIVE_INFINITY, PlaylistLevelType.SUBTITLE); this.fragPrevious = null; this.mediaBuffer = null; } 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; } const currentTrack = this.levels[this.currentTrackId]; if (currentTrack != null && currentTrack.details) { this.mediaBuffer = this.mediaBufferTimeRanges; } else { this.mediaBuffer = null; } if (currentTrack && this.state !== State.STOPPED) { this.setInterval(TICK_INTERVAL$2); } } // Got a new set of subtitle fragments. onSubtitleTrackLoaded(event, data) { var _track$details; const { currentTrackId, levels } = this; const { details: newDetails, id: trackId } = data; if (!levels) { this.warn(`Subtitle tracks were reset while loading level ${trackId}`); return; } const 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; let sliding = 0; if (newDetails.live || (_track$details = track.details) != null && _track$details.live) { const mainDetails = this.mainDetails; if (newDetails.deltaUpdateFailed || !mainDetails) { return; } const mainSlidingStartFragment = mainDetails.fragments[0]; if (!track.details) { if (newDetails.hasProgramDateTime && mainDetails.hasProgramDateTime) { alignMediaPlaylistByPDT(newDetails, mainDetails); sliding = newDetails.fragmentStart; } else if (mainSlidingStartFragment) { 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) { sliding = mainSlidingStartFragment.start; addSliding(newDetails, sliding); } } } track.details = newDetails; this.levelLastLoaded = track; if (trackId !== currentTrackId) { return; } this.hls.trigger(Events.SUBTITLE_TRACK_UPDATED, { details: newDetails, id: trackId, groupId: data.groupId }); this.tick(); if (newDetails.live && !this.fragCurrent && this.media && this.state === State.IDLE) { const foundFrag = findFragmentByPTS(null, newDetails.fragments, this.media.currentTime, 0); if (!foundFrag) { this.warn("Subtitle playlist not aligned with playback"); track.details = void 0; } } } _handleFragmentLoadComplete(fragLoadedData) { const { frag, payload } = fragLoadedData; const decryptData = frag.decryptdata; const hls = this.hls; if (this.fragContextChanged(frag)) { return; } if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) { const startTime = performance.now(); this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch((err) => { hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.FRAG_DECRYPT_ERROR, fatal: false, error: err, reason: err.message, frag }); throw err; }).then((decryptedData) => { const endTime = performance.now(); hls.trigger(Events.FRAG_DECRYPTED, { frag, payload: decryptedData, stats: { tstart: startTime, tdecrypt: endTime } }); }).catch((err) => { this.warn(`${err.name}: ${err.message}`); this.state = State.IDLE; }); } } doTick() { if (!this.media) { this.state = State.IDLE; return; } if (this.state === State.IDLE) { const { currentTrackId, levels } = this; const track = levels == null ? void 0 : levels[currentTrackId]; if (!track || !levels.length || !track.details) { return; } if (this.waitForLive(track)) { return; } const { config } = this; const currentTime = this.getLoadPosition(); const bufferedInfo = BufferHelper.bufferedInfo(this.tracksBuffered[this.currentTrackId] || [], currentTime, config.maxBufferHole); const { end: targetBufferTime, len: bufferLen } = bufferedInfo; const trackDetails = track.details; const maxBufLen = this.hls.maxBufferLength + trackDetails.levelTargetDuration; if (bufferLen > maxBufLen) { return; } const fragments = trackDetails.fragments; const fragLen = fragments.length; const end = trackDetails.edge; let foundFrag = null; const fragPrevious = this.fragPrevious; if (targetBufferTime < end) { const tolerance = config.maxFragLookUpTolerance; const 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]; } foundFrag = this.filterReplacedPrimary(foundFrag, track.details); if (!foundFrag) { return; } const curSNIdx = foundFrag.sn - trackDetails.startSN; const 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) { const fragToLoad = this.mapToInitFragWhenRequired(foundFrag); if (fragToLoad) { this.loadFragment(fragToLoad, track, targetBufferTime); } } } } loadFragment(frag, level, targetBufferTime) { if (!isMediaFragment(frag)) { this._loadInitSegment(frag, level); } else { super.loadFragment(frag, level, targetBufferTime); } } get mediaBufferTimeRanges() { return new BufferableInstance(this.tracksBuffered[this.currentTrackId] || []); } } class BufferableInstance { constructor(timeranges) { this.buffered = void 0; const 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(index) { return getRange("end", index, timeranges.length); }, start(index) { return getRange("start", index, timeranges.length); } }; } } const specialCea608CharsCodes = { 42: 225, // lowercase a, acute accent 92: 233, // lowercase e, acute accent 94: 237, // lowercase i, acute accent 95: 243, // lowercase o, acute accent 96: 250, // lowercase u, acute accent 123: 231, // lowercase c with cedilla 124: 247, // division symbol 125: 209, // uppercase N tilde 126: 241, // lowercase n tilde 127: 9608, // 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 128: 174, // Registered symbol (R) 129: 176, // degree sign 130: 189, // 1/2 symbol 131: 191, // Inverted (open) question mark 132: 8482, // Trademark symbol (TM) 133: 162, // Cents symbol 134: 163, // Pounds sterling 135: 9834, // Music 8'th note 136: 224, // lowercase a, grave accent 137: 32, // transparent space (regular) 138: 232, // lowercase e, grave accent 139: 226, // lowercase a, circumflex accent 140: 234, // lowercase e, circumflex accent 141: 238, // lowercase i, circumflex accent 142: 244, // lowercase o, circumflex accent 143: 251, // 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 144: 193, // capital letter A with acute 145: 201, // capital letter E with acute 146: 211, // capital letter O with acute 147: 218, // capital letter U with acute 148: 220, // capital letter U with diaresis 149: 252, // lowercase letter U with diaeresis 150: 8216, // opening single quote 151: 161, // inverted exclamation mark 152: 42, // asterisk 153: 8217, // closing single quote 154: 9473, // box drawings heavy horizontal 155: 169, // copyright sign 156: 8480, // Service mark 157: 8226, // (round) bullet 158: 8220, // Left double quotation mark 159: 8221, // Right double quotation mark 160: 192, // uppercase A, grave accent 161: 194, // uppercase A, circumflex 162: 199, // uppercase C with cedilla 163: 200, // uppercase E, grave accent 164: 202, // uppercase E, circumflex 165: 203, // capital letter E with diaresis 166: 235, // lowercase letter e with diaresis 167: 206, // uppercase I, circumflex 168: 207, // uppercase I, with diaresis 169: 239, // lowercase i, with diaresis 170: 212, // uppercase O, circumflex 171: 217, // uppercase U, grave accent 172: 249, // lowercase u, grave accent 173: 219, // uppercase U, circumflex 174: 171, // left-pointing double angle quotation mark 175: 187, // 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 176: 195, // Uppercase A, tilde 177: 227, // Lowercase a, tilde 178: 205, // Uppercase I, acute accent 179: 204, // Uppercase I, grave accent 180: 236, // Lowercase i, grave accent 181: 210, // Uppercase O, grave accent 182: 242, // Lowercase o, grave accent 183: 213, // Uppercase O, tilde 184: 245, // Lowercase o, tilde 185: 123, // Open curly brace 186: 125, // Closing curly brace 187: 92, // Backslash 188: 94, // Caret 189: 95, // Underscore 190: 124, // Pipe (vertical line) 191: 8764, // Tilde operator 192: 196, // Uppercase A, umlaut 193: 228, // Lowercase A, umlaut 194: 214, // Uppercase O, umlaut 195: 246, // Lowercase o, umlaut 196: 223, // Esszett (sharp S) 197: 165, // Yen symbol 198: 164, // Generic currency sign 199: 9475, // Box drawings heavy vertical 200: 197, // Uppercase A, ring 201: 229, // Lowercase A, ring 202: 216, // Uppercase O, stroke 203: 248, // Lowercase o, strok 204: 9487, // Box drawings heavy down and right 205: 9491, // Box drawings heavy down and left 206: 9495, // Box drawings heavy up and right 207: 9499 // Box drawings heavy up and left }; const getCharForByte = (byte) => String.fromCharCode(specialCea608CharsCodes[byte] || byte); const NR_ROWS = 15; const NR_COLS = 100; const rowsLowCh1 = { 17: 1, 18: 3, 21: 5, 22: 7, 23: 9, 16: 11, 19: 12, 20: 14 }; const rowsHighCh1 = { 17: 2, 18: 4, 21: 6, 22: 8, 23: 10, 19: 13, 20: 15 }; const rowsLowCh2 = { 25: 1, 26: 3, 29: 5, 30: 7, 31: 9, 24: 11, 27: 12, 28: 14 }; const rowsHighCh2 = { 25: 2, 26: 4, 29: 6, 30: 8, 31: 10, 27: 13, 28: 15 }; const backgroundColors = ["white", "green", "blue", "cyan", "red", "yellow", "magenta", "black", "transparent"]; class CaptionsLogger { constructor() { this.time = null; this.verboseLevel = 0; } log(severity, msg) { if (this.verboseLevel >= severity) { const m = typeof msg === "function" ? msg() : msg; logger.log(`${this.time} [${severity}] ${m}`); } } } const numArrayToHexArray = function numArrayToHexArray2(numArray) { const hexArray = []; for (let j = 0; j < numArray.length; j++) { hexArray.push(numArray[j].toString(16)); } return hexArray; }; class PenState { constructor() { this.foreground = "white"; this.underline = false; this.italics = false; this.background = "black"; this.flash = false; } reset() { this.foreground = "white"; this.underline = false; this.italics = false; this.background = "black"; this.flash = false; } setStyles(styles) { const attribs = ["foreground", "underline", "italics", "background", "flash"]; for (let i = 0; i < attribs.length; i++) { const style = attribs[i]; if (styles.hasOwnProperty(style)) { this[style] = styles[style]; } } } isDefault() { return this.foreground === "white" && !this.underline && !this.italics && this.background === "black" && !this.flash; } equals(other) { return this.foreground === other.foreground && this.underline === other.underline && this.italics === other.italics && this.background === other.background && this.flash === other.flash; } copy(newPenState) { this.foreground = newPenState.foreground; this.underline = newPenState.underline; this.italics = newPenState.italics; this.background = newPenState.background; this.flash = newPenState.flash; } toString() { return "color=" + this.foreground + ", underline=" + this.underline + ", italics=" + this.italics + ", background=" + this.background + ", flash=" + this.flash; } } class StyledUnicodeChar { constructor() { this.uchar = " "; this.penState = new PenState(); } reset() { this.uchar = " "; this.penState.reset(); } setChar(uchar, newPenState) { this.uchar = uchar; this.penState.copy(newPenState); } setPenState(newPenState) { this.penState.copy(newPenState); } equals(other) { return this.uchar === other.uchar && this.penState.equals(other.penState); } copy(newChar) { this.uchar = newChar.uchar; this.penState.copy(newChar.penState); } isEmpty() { return this.uchar === " " && this.penState.isDefault(); } } class Row { constructor(logger2) { this.chars = []; this.pos = 0; this.currPenState = new PenState(); this.cueStartTime = null; this.logger = void 0; for (let i = 0; i < NR_COLS; i++) { this.chars.push(new StyledUnicodeChar()); } this.logger = logger2; } equals(other) { for (let i = 0; i < NR_COLS; i++) { if (!this.chars[i].equals(other.chars[i])) { return false; } } return true; } copy(other) { for (let i = 0; i < NR_COLS; i++) { this.chars[i].copy(other.chars[i]); } } isEmpty() { let empty = true; for (let i = 0; i < NR_COLS; i++) { if (!this.chars[i].isEmpty()) { empty = false; break; } } return empty; } /** * Set the cursor to a valid column. */ 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. */ moveCursor(relPos) { const newPos = this.pos + relPos; if (relPos > 1) { for (let 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. */ backSpace() { this.moveCursor(-1); this.chars[this.pos].setChar(" ", this.currPenState); } insertChar(byte) { if (byte >= 144) { this.backSpace(); } const char = getCharForByte(byte); if (this.pos >= NR_COLS) { this.logger.log(0, () => "Cannot insert " + byte.toString(16) + " (" + char + ") at position " + this.pos + ". Skipping it!"); return; } this.chars[this.pos].setChar(char, this.currPenState); this.moveCursor(1); } clearFromPos(startPos) { let i; for (i = startPos; i < NR_COLS; i++) { this.chars[i].reset(); } } clear() { this.clearFromPos(0); this.pos = 0; this.currPenState.reset(); } clearToEndOfRow() { this.clearFromPos(this.pos); } getTextString() { const chars = []; let empty = true; for (let i = 0; i < NR_COLS; i++) { const char = this.chars[i].uchar; if (char !== " ") { empty = false; } chars.push(char); } if (empty) { return ""; } else { return chars.join(""); } } setPenStyles(styles) { this.currPenState.setStyles(styles); const currChar = this.chars[this.pos]; currChar.setPenState(this.currPenState); } } class CaptionScreen { constructor(logger2) { this.rows = []; this.currRow = NR_ROWS - 1; this.nrRollUpRows = null; this.lastOutputScreen = null; this.logger = void 0; for (let i = 0; i < NR_ROWS; i++) { this.rows.push(new Row(logger2)); } this.logger = logger2; } reset() { for (let i = 0; i < NR_ROWS; i++) { this.rows[i].clear(); } this.currRow = NR_ROWS - 1; } equals(other) { let equal = true; for (let i = 0; i < NR_ROWS; i++) { if (!this.rows[i].equals(other.rows[i])) { equal = false; break; } } return equal; } copy(other) { for (let i = 0; i < NR_ROWS; i++) { this.rows[i].copy(other.rows[i]); } } isEmpty() { let empty = true; for (let i = 0; i < NR_ROWS; i++) { if (!this.rows[i].isEmpty()) { empty = false; break; } } return empty; } backSpace() { const row = this.rows[this.currRow]; row.backSpace(); } clearToEndOfRow() { const row = this.rows[this.currRow]; row.clearToEndOfRow(); } /** * Insert a character (without styling) in the current row. */ insertChar(char) { const row = this.rows[this.currRow]; row.insertChar(char); } setPen(styles) { const row = this.rows[this.currRow]; row.setPenStyles(styles); } moveCursor(relPos) { const row = this.rows[this.currRow]; row.moveCursor(relPos); } setCursor(absPos) { this.logger.log(2, "setCursor: " + absPos); const row = this.rows[this.currRow]; row.setCursor(absPos); } setPAC(pacData) { this.logger.log(2, () => "pacData = " + stringify(pacData)); let newRow = pacData.row - 1; if (this.nrRollUpRows && newRow < this.nrRollUpRows - 1) { newRow = this.nrRollUpRows - 1; } if (this.nrRollUpRows && this.currRow !== newRow) { for (let i = 0; i < NR_ROWS; i++) { this.rows[i].clear(); } const topRowIndex = this.currRow + 1 - this.nrRollUpRows; const lastOutputScreen = this.lastOutputScreen; if (lastOutputScreen) { const prevLineTime = lastOutputScreen.rows[topRowIndex].cueStartTime; const time = this.logger.time; if (prevLineTime !== null && time !== null && prevLineTime < time) { for (let i = 0; i < this.nrRollUpRows; i++) { this.rows[newRow - this.nrRollUpRows + i + 1].copy(lastOutputScreen.rows[topRowIndex + i]); } } } } this.currRow = newRow; const row = this.rows[this.currRow]; if (pacData.indent !== null) { const indent = pacData.indent; const prevPos = Math.max(indent - 1, 0); row.setCursor(pacData.indent); pacData.color = row.chars[prevPos].penState.foreground; } const 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). */ setBkgData(bkgData) { this.logger.log(2, () => "bkgData = " + stringify(bkgData)); this.backSpace(); this.setPen(bkgData); this.insertChar(32); } setRollUpRows(nrRows) { this.nrRollUpRows = nrRows; } rollUp() { if (this.nrRollUpRows === null) { this.logger.log(3, "roll_up but nrRollUpRows not set yet"); return; } this.logger.log(1, () => this.getDisplayText()); const topRowIndex = this.currRow + 1 - this.nrRollUpRows; const topRow = this.rows.splice(topRowIndex, 1)[0]; topRow.clear(); this.rows.splice(this.currRow, 0, topRow); this.logger.log(2, "Rolling up"); } /** * Get all non-empty rows with as unicode text. */ getDisplayText(asOneRow) { asOneRow = asOneRow || false; const displayText = []; let text = ""; let rowNr = -1; for (let i = 0; i < NR_ROWS; i++) { const 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; } getTextAndFormat() { return this.rows; } } class Cea608Channel { constructor(channelNumber, outputFilter, logger2) { 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(logger2); this.nonDisplayedMemory = new CaptionScreen(logger2); this.lastOutputScreen = new CaptionScreen(logger2); this.currRollUpRow = this.displayedMemory.rows[NR_ROWS - 1]; this.writeScreen = this.displayedMemory; this.mode = null; this.cueStartTime = null; this.logger = logger2; } 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; } getHandler() { return this.outputFilter; } setHandler(newHandler) { this.outputFilter = newHandler; } setPAC(pacData) { this.writeScreen.setPAC(pacData); } setBkgData(bkgData) { this.writeScreen.setBkgData(bkgData); } setMode(newMode) { if (newMode === this.mode) { return; } this.mode = newMode; this.logger.log(2, () => "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; } insertChars(chars) { for (let i = 0; i < chars.length; i++) { this.writeScreen.insertChar(chars[i]); } const screen = this.writeScreen === this.displayedMemory ? "DISP" : "NON_DISP"; this.logger.log(2, () => screen + ": " + this.writeScreen.getDisplayText(true)); if (this.mode === "MODE_PAINT-ON" || this.mode === "MODE_ROLL-UP") { this.logger.log(1, () => "DISPLAYED: " + this.displayedMemory.getDisplayText(true)); this.outputDataUpdate(); } } ccRCL() { this.logger.log(2, "RCL - Resume Caption Loading"); this.setMode("MODE_POP-ON"); } ccBS() { this.logger.log(2, "BS - BackSpace"); if (this.mode === "MODE_TEXT") { return; } this.writeScreen.backSpace(); if (this.writeScreen === this.displayedMemory) { this.outputDataUpdate(); } } ccAOF() { } ccAON() { } ccDER() { this.logger.log(2, "DER- Delete to End of Row"); this.writeScreen.clearToEndOfRow(); this.outputDataUpdate(); } ccRU(nrRows) { this.logger.log(2, "RU(" + nrRows + ") - Roll Up"); this.writeScreen = this.displayedMemory; this.setMode("MODE_ROLL-UP"); this.writeScreen.setRollUpRows(nrRows); } ccFON() { this.logger.log(2, "FON - Flash On"); this.writeScreen.setPen({ flash: true }); } ccRDC() { this.logger.log(2, "RDC - Resume Direct Captioning"); this.setMode("MODE_PAINT-ON"); } ccTR() { this.logger.log(2, "TR"); this.setMode("MODE_TEXT"); } ccRTD() { this.logger.log(2, "RTD"); this.setMode("MODE_TEXT"); } ccEDM() { this.logger.log(2, "EDM - Erase Displayed Memory"); this.displayedMemory.reset(); this.outputDataUpdate(true); } ccCR() { this.logger.log(2, "CR - Carriage Return"); this.writeScreen.rollUp(); this.outputDataUpdate(true); } ccENM() { this.logger.log(2, "ENM - Erase Non-displayed Memory"); this.nonDisplayedMemory.reset(); } ccEOC() { this.logger.log(2, "EOC - End Of Caption"); if (this.mode === "MODE_POP-ON") { const tmp = this.displayedMemory; this.displayedMemory = this.nonDisplayedMemory; this.nonDisplayedMemory = tmp; this.writeScreen = this.nonDisplayedMemory; this.logger.log(1, () => "DISP: " + this.displayedMemory.getDisplayText()); } this.outputDataUpdate(true); } ccTO(nrCols) { this.logger.log(2, "TO(" + nrCols + ") - Tab Offset"); this.writeScreen.moveCursor(nrCols); } ccMIDROW(secondByte) { const styles = { flash: false }; styles.underline = secondByte % 2 === 1; styles.italics = secondByte >= 46; if (!styles.italics) { const colorIndex = Math.floor(secondByte / 2) - 16; const colors = ["white", "green", "blue", "cyan", "red", "yellow", "magenta"]; styles.foreground = colors[colorIndex]; } else { styles.foreground = "white"; } this.logger.log(2, "MIDROW: " + stringify(styles)); this.writeScreen.setPen(styles); } outputDataUpdate(dispatch = false) { const time = this.logger.time; if (time === null) { return; } if (this.outputFilter) { if (this.cueStartTime === null && !this.displayedMemory.isEmpty()) { 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); } } cueSplitAtTime(t) { if (this.outputFilter) { if (!this.displayedMemory.isEmpty()) { if (this.outputFilter.newCue) { this.outputFilter.newCue(this.cueStartTime, t, this.displayedMemory); } this.cueStartTime = t; } } } } class Cea608Parser { constructor(field, out1, out2) { this.channels = void 0; this.currentChannel = 0; this.cmdHistory = createCmdHistory(); this.logger = void 0; const logger2 = this.logger = new CaptionsLogger(); this.channels = [null, new Cea608Channel(field, out1, logger2), new Cea608Channel(field + 1, out2, logger2)]; } getHandler(channel) { return this.channels[channel].getHandler(); } 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. */ addData(time, byteList) { this.logger.time = time; for (let i = 0; i < byteList.length; i += 2) { const a = byteList[i] & 127; const b = byteList[i + 1] & 127; let cmdFound = false; let charsFound = null; if (a === 0 && b === 0) { continue; } else { this.logger.log(3, () => "[" + numArrayToHexArray([byteList[i], byteList[i + 1]]) + "] -> (" + numArrayToHexArray([a, b]) + ")"); } const cmdHistory = this.cmdHistory; const isControlCode = a >= 16 && a <= 31; if (isControlCode) { if (hasCmdRepeated(a, b, cmdHistory)) { setLastCmd(null, null, cmdHistory); this.logger.log(3, () => "Repeated command (" + numArrayToHexArray([a, b]) + ") is dropped"); continue; } setLastCmd(a, b, this.cmdHistory); cmdFound = this.parseCmd(a, b); if (!cmdFound) { cmdFound = this.parseMidrow(a, b); } if (!cmdFound) { cmdFound = this.parsePAC(a, b); } if (!cmdFound) { cmdFound = this.parseBackgroundAttributes(a, b); } } else { setLastCmd(null, null, cmdHistory); } if (!cmdFound) { charsFound = this.parseChars(a, b); if (charsFound) { const currChNr = this.currentChannel; if (currChNr && currChNr > 0) { const channel = this.channels[currChNr]; channel.insertChars(charsFound); } else { this.logger.log(2, "No channel found yet. TEXT-MODE?"); } } } if (!cmdFound && !charsFound) { this.logger.log(2, () => "Couldn't parse cleaned data " + numArrayToHexArray([a, b]) + " orig: " + numArrayToHexArray([byteList[i], byteList[i + 1]])); } } } /** * Parse Command. * @returns True if a command was found */ parseCmd(a, b) { const cond1 = (a === 20 || a === 28 || a === 21 || a === 29) && b >= 32 && b <= 47; const cond2 = (a === 23 || a === 31) && b >= 33 && b <= 35; if (!(cond1 || cond2)) { return false; } const chNr = a === 20 || a === 21 || a === 23 ? 1 : 2; const channel = this.channels[chNr]; if (a === 20 || a === 21 || a === 28 || a === 29) { if (b === 32) { channel.ccRCL(); } else if (b === 33) { channel.ccBS(); } else if (b === 34) { channel.ccAOF(); } else if (b === 35) { channel.ccAON(); } else if (b === 36) { channel.ccDER(); } else if (b === 37) { channel.ccRU(2); } else if (b === 38) { channel.ccRU(3); } else if (b === 39) { channel.ccRU(4); } else if (b === 40) { channel.ccFON(); } else if (b === 41) { channel.ccRDC(); } else if (b === 42) { channel.ccTR(); } else if (b === 43) { channel.ccRTD(); } else if (b === 44) { channel.ccEDM(); } else if (b === 45) { channel.ccCR(); } else if (b === 46) { channel.ccENM(); } else if (b === 47) { channel.ccEOC(); } } else { channel.ccTO(b - 32); } this.currentChannel = chNr; return true; } /** * Parse midrow styling command */ parseMidrow(a, b) { let chNr = 0; if ((a === 17 || a === 25) && b >= 32 && b <= 47) { if (a === 17) { chNr = 1; } else { chNr = 2; } if (chNr !== this.currentChannel) { this.logger.log(0, "Mismatch channel in midrow parsing"); return false; } const channel = this.channels[chNr]; if (!channel) { return false; } channel.ccMIDROW(b); this.logger.log(3, () => "MIDROW (" + numArrayToHexArray([a, b]) + ")"); return true; } return false; } /** * Parse Preable Access Codes (Table 53). * @returns {Boolean} Tells if PAC found */ parsePAC(a, b) { let row; const case1 = (a >= 17 && a <= 23 || a >= 25 && a <= 31) && b >= 64 && b <= 127; const case2 = (a === 16 || a === 24) && b >= 64 && b <= 95; if (!(case1 || case2)) { return false; } const chNr = a <= 23 ? 1 : 2; if (b >= 64 && b <= 95) { row = chNr === 1 ? rowsLowCh1[a] : rowsLowCh2[a]; } else { row = chNr === 1 ? rowsHighCh1[a] : rowsHighCh2[a]; } const 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 */ interpretPAC(row, byte) { let pacIndex; const pacData = { color: null, italics: false, indent: null, underline: false, row }; if (byte > 95) { pacIndex = byte - 96; } else { pacIndex = byte - 64; } pacData.underline = (pacIndex & 1) === 1; if (pacIndex <= 13) { pacData.color = ["white", "green", "blue", "cyan", "red", "yellow", "magenta", "white"][Math.floor(pacIndex / 2)]; } else if (pacIndex <= 15) { pacData.italics = true; pacData.color = "white"; } else { pacData.indent = Math.floor((pacIndex - 16) / 2) * 4; } return pacData; } /** * Parse characters. * @returns An array with 1 to 2 codes corresponding to chars, if found. null otherwise. */ parseChars(a, b) { let channelNr; let charCodes = null; let charCode1 = null; if (a >= 25) { channelNr = 2; charCode1 = a - 8; } else { channelNr = 1; charCode1 = a; } if (charCode1 >= 17 && charCode1 <= 19) { let oneCode; if (charCode1 === 17) { oneCode = b + 80; } else if (charCode1 === 18) { oneCode = b + 112; } else { oneCode = b + 144; } this.logger.log(2, () => "Special char '" + getCharForByte(oneCode) + "' in channel " + channelNr); charCodes = [oneCode]; } else if (a >= 32 && a <= 127) { charCodes = b === 0 ? [a] : [a, b]; } if (charCodes) { this.logger.log(3, () => "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 */ parseBackgroundAttributes(a, b) { const case1 = (a === 16 || a === 24) && b >= 32 && b <= 47; const case2 = (a === 23 || a === 31) && b >= 45 && b <= 47; if (!(case1 || case2)) { return false; } let index; const bkgData = {}; if (a === 16 || a === 24) { index = Math.floor((b - 32) / 2); bkgData.background = backgroundColors[index]; if (b % 2 === 1) { bkgData.background = bkgData.background + "_semi"; } } else if (b === 45) { bkgData.background = "transparent"; } else { bkgData.foreground = "black"; if (b === 47) { bkgData.underline = true; } } const chNr = a <= 23 ? 1 : 2; const channel = this.channels[chNr]; channel.setBkgData(bkgData); return true; } /** * Reset state of parser and its channels. */ reset() { for (let i = 0; i < Object.keys(this.channels).length; i++) { const 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. */ cueSplitAtTime(t) { for (let i = 0; i < this.channels.length; i++) { const channel = this.channels[i]; if (channel) { channel.cueSplitAtTime(t); } } } } 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 VTTCue = function() { if (optionalSelf != null && optionalSelf.VTTCue) { return self.VTTCue; } const AllowedDirections = ["", "lr", "rl"]; const AllowedAlignments = ["start", "middle", "end", "left", "right"]; function isAllowedValue(allowed, value) { if (typeof value !== "string") { return false; } if (!Array.isArray(allowed)) { return false; } const lcValue = value.toLowerCase(); 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, ...rest) { let i = 1; for (; i < arguments.length; i++) { const cobj = arguments[i]; for (const p in cobj) { obj[p] = cobj[p]; } } return obj; } function VTTCue2(startTime, endTime, text) { const cue = this; const baseObj = { enumerable: true }; cue.hasBeenReset = false; let _id = ""; let _pauseOnExit = false; let _startTime = startTime; let _endTime = endTime; let _text = text; let _region = null; let _vertical = ""; let _snapToLines = true; let _line = "auto"; let _lineAlign = "start"; let _position = 50; let _positionAlign = "middle"; let _size = 50; let _align = "middle"; Object.defineProperty(cue, "id", extend({}, baseObj, { get: function() { return _id; }, set: function(value) { _id = "" + value; } })); Object.defineProperty(cue, "pauseOnExit", extend({}, baseObj, { get: function() { return _pauseOnExit; }, set: function(value) { _pauseOnExit = !!value; } })); Object.defineProperty(cue, "startTime", extend({}, baseObj, { get: function() { return _startTime; }, set: function(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() { return _endTime; }, set: function(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() { return _text; }, set: function(value) { _text = "" + value; this.hasBeenReset = true; } })); Object.defineProperty(cue, "region", extend({}, baseObj, { get: function() { return _region; }, set: function(value) { _region = value; this.hasBeenReset = true; } })); Object.defineProperty(cue, "vertical", extend({}, baseObj, { get: function() { return _vertical; }, set: function(value) { const setting = findDirectionSetting(value); 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() { return _snapToLines; }, set: function(value) { _snapToLines = !!value; this.hasBeenReset = true; } })); Object.defineProperty(cue, "line", extend({}, baseObj, { get: function() { return _line; }, set: function(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() { return _lineAlign; }, set: function(value) { const 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() { return _position; }, set: function(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() { return _positionAlign; }, set: function(value) { const 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() { return _size; }, set: function(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() { return _align; }, set: function(value) { const setting = findAlignSetting(value); if (!setting) { throw new SyntaxError("An invalid or illegal string was specified."); } _align = setting; this.hasBeenReset = true; } })); cue.displayState = void 0; } VTTCue2.prototype.getCueAsHTML = function() { const WebVTT = self.WebVTT; return WebVTT.convertCueToDOMTree(self, this.text); }; return VTTCue2; }(); class StringDecoder { decode(data, options) { if (!data) { return ""; } if (typeof data !== "string") { throw new Error("Error - expected string data."); } return decodeURIComponent(encodeURIComponent(data)); } } function parseTimeStamp(input) { function computeSeconds(h, m2, s, f) { return (h | 0) * 3600 + (m2 | 0) * 60 + (s | 0) + parseFloat(f || 0); } const m = input.match(/^(?:(\d+):)?(\d{2}):(\d{2})(\.\d+)?/); if (!m) { return null; } if (parseFloat(m[2]) > 59) { return computeSeconds(m[2], m[3], 0, m[4]); } return computeSeconds(m[1], m[2], m[3], m[4]); } class Settings { constructor() { this.values = /* @__PURE__ */ Object.create(null); } // Only accept the first assignment to any key. 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. 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. has(k) { return k in this.values; } // Accept a setting if its one of the given alternatives. alt(k, v, a) { for (let n = 0; n < a.length; ++n) { if (v === a[n]) { this.set(k, v); break; } } } // Accept a setting if its a valid (signed) integer. integer(k, v) { if (/^-?\d+$/.test(v)) { this.set(k, parseInt(v, 10)); } } // Accept a setting if its a valid percentage. percent(k, v) { if (/^([\d]{1,3})(\.[\d]*)?%$/.test(v)) { const percent = parseFloat(v); if (percent >= 0 && percent <= 100) { this.set(k, percent); return true; } } return false; } } function parseOptions(input, callback, keyValueDelim, groupDelim) { const groups = groupDelim ? input.split(groupDelim) : [input]; for (const i in groups) { if (typeof groups[i] !== "string") { continue; } const kv = groups[i].split(keyValueDelim); if (kv.length !== 2) { continue; } const k = kv[0]; const v = kv[1]; callback(k, v); } } const defaults = new VTTCue(0, 0, ""); const center = defaults.align === "middle" ? "middle" : "center"; function parseCue(input, cue, regionList) { const oInput = input; function consumeTimeStamp() { const ts = parseTimeStamp(input); if (ts === null) { throw new Error("Malformed timestamp: " + oInput); } input = input.replace(/^[^\sa-zA-Z-]+/, ""); return ts; } function consumeCueSettings(input2, cue2) { const settings = new Settings(); parseOptions(input2, function(k, v) { let vals; switch (k) { case "region": for (let 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/); cue2.region = settings.get("region", null); cue2.vertical = settings.get("vertical", ""); let line = settings.get("line", "auto"); if (line === "auto" && defaults.line === -1) { line = -1; } cue2.line = line; cue2.lineAlign = settings.get("lineAlign", "start"); cue2.snapToLines = settings.get("snapToLines", true); cue2.size = settings.get("size", 100); cue2.align = settings.get("align", center); let position = settings.get("position", "auto"); if (position === "auto" && defaults.position === 50) { position = cue2.align === "start" || cue2.align === "left" ? 0 : cue2.align === "end" || cue2.align === "right" ? 100 : 50; } cue2.position = position; } function skipWhitespace() { input = input.replace(/^\s+/, ""); } skipWhitespace(); cue.startTime = consumeTimeStamp(); skipWhitespace(); if (input.slice(0, 3) !== "-->") { throw new Error("Malformed time stamp (time stamps must be separated by '-->'): " + oInput); } input = input.slice(3); skipWhitespace(); cue.endTime = consumeTimeStamp(); skipWhitespace(); consumeCueSettings(input, cue); } function fixLineBreaks(input) { return input.replace(/
/gi, "\n"); } class VTTParser { constructor() { 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; } parse(data) { const _this = this; if (data) { _this.buffer += _this.decoder.decode(data, { stream: true }); } function collectNextLine() { let buffer = _this.buffer; let pos = 0; buffer = fixLineBreaks(buffer); while (pos < buffer.length && buffer[pos] !== "\r" && buffer[pos] !== "\n") { ++pos; } const line = buffer.slice(0, pos); if (buffer[pos] === "\r") { ++pos; } if (buffer[pos] === "\n") { ++pos; } _this.buffer = buffer.slice(pos); return line; } function parseHeader2(input) { parseOptions(input, function(k, v) { }, /:/); } try { let line = ""; if (_this.state === "INITIAL") { if (!/\r\n|\n/.test(_this.buffer)) { return this; } line = collectNextLine(); const m = line.match(/^()?WEBVTT([ \t].*)?$/); if (!(m != null && m[0])) { throw new Error("Malformed WebVTT signature."); } _this.state = "HEADER"; } let alreadyCollectedLine = false; while (_this.buffer) { if (!/\r\n|\n/.test(_this.buffer)) { return this; } if (!alreadyCollectedLine) { line = collectNextLine(); } else { alreadyCollectedLine = false; } switch (_this.state) { case "HEADER": if (/:/.test(line)) { parseHeader2(line); } else if (!line) { _this.state = "ID"; } continue; case "NOTE": if (!line) { _this.state = "ID"; } continue; case "ID": if (/^NOTE($|[ \t])/.test(line)) { _this.state = "NOTE"; break; } if (!line) { continue; } _this.cue = new VTTCue(0, 0, ""); _this.state = "CUE"; if (line.indexOf("-->") === -1) { _this.cue.id = line; continue; } case "CUE": if (!_this.cue) { _this.state = "BADCUE"; continue; } try { parseCue(line, _this.cue, _this.regionList); } catch (e) { _this.cue = null; _this.state = "BADCUE"; continue; } _this.state = "CUETEXT"; continue; case "CUETEXT": { const hasSubstring = line.indexOf("-->") !== -1; if (!line || hasSubstring && (alreadyCollectedLine = true)) { 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": if (!line) { _this.state = "ID"; } } } } catch (e) { if (_this.state === "CUETEXT" && _this.cue && _this.oncue) { _this.oncue(_this.cue); } _this.cue = null; _this.state = _this.state === "INITIAL" ? "BADWEBVTT" : "BADCUE"; } return this; } flush() { const _this = this; try { if (_this.cue || _this.state === "HEADER") { _this.buffer += "\n\n"; _this.parse(); } 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; } } const LINEBREAKS = /\r\n|\n\r|\n|\r/g; const startsWith = function startsWith2(inputString, searchString, position = 0) { return inputString.slice(position, position + searchString.length) === searchString; }; const cueString2millis = function cueString2millis2(timeString) { let ts = parseInt(timeString.slice(-3)); const secs = parseInt(timeString.slice(-6, -4)); const mins = parseInt(timeString.slice(-9, -7)); const 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 += 1e3 * secs; ts += 60 * 1e3 * mins; ts += 60 * 60 * 1e3 * hours; return ts; }; function generateCueId(startTime, endTime, text) { return hash(startTime.toString()) + hash(endTime.toString()) + hash(text); } const calculateOffset = function calculateOffset2(vttCCs, cc, presentationTime) { let currCC = vttCCs[cc]; let prevCC = vttCCs[currCC.prevCC]; if (!prevCC || !prevCC.new && currCC.new) { vttCCs.ccOffset = vttCCs.presentationOffset = currCC.start; currCC.new = false; return; } 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) { const parser = new VTTParser(); const vttLines = utf8ArrayToStr(new Uint8Array(vttByteArray)).trim().replace(LINEBREAKS, "\n").split("\n"); const cues = []; const init90kHz = initPTS ? toMpegTsClockFromTimescale(initPTS.baseTime, initPTS.timescale) : 0; let cueTime = "00:00.000"; let timestampMapMPEGTS = 0; let timestampMapLOCAL = 0; let parsingError; let inHeader = true; parser.oncue = function(cue) { const currCC = vttCCs[cc]; let cueOffset = vttCCs.ccOffset; const webVttMpegTsMapOffset = (timestampMapMPEGTS - init90kHz) / 9e4; if (currCC != null && currCC.new) { if (timestampMapLOCAL !== void 0) { cueOffset = vttCCs.ccOffset = currCC.start; } else { calculateOffset(vttCCs, cc, webVttMpegTsMapOffset); } } if (webVttMpegTsMapOffset) { if (!initPTS) { parsingError = new Error("Missing initPTS for VTT MPEGTS"); return; } cueOffset = webVttMpegTsMapOffset - vttCCs.presentationOffset; } const duration = cue.endTime - cue.startTime; const startTime = normalizePts((cue.startTime + cueOffset - timestampMapLOCAL) * 9e4, timeOffset * 9e4) / 9e4; cue.startTime = Math.max(startTime, 0); cue.endTime = Math.max(startTime + duration, 0); const text = cue.text.trim(); cue.text = decodeURIComponent(encodeURIComponent(text)); 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); }; vttLines.forEach((line) => { if (inHeader) { if (startsWith(line, "X-TIMESTAMP-MAP=")) { inHeader = false; line.slice(16).split(",").forEach((timestamp) => { if (startsWith(timestamp, "LOCAL:")) { cueTime = timestamp.slice(6); } else if (startsWith(timestamp, "MPEGTS:")) { timestampMapMPEGTS = parseInt(timestamp.slice(7)); } }); try { timestampMapLOCAL = cueString2millis(cueTime) / 1e3; } catch (error) { parsingError = error; } return; } else if (line === "") { inHeader = false; } } parser.parse(line + "\n"); }); parser.flush(); } const IMSC1_CODEC = "stpp.ttml.im1t"; const HMSF_REGEX = /^(\d{2,}):(\d{2}):(\d{2}):(\d{2})\.?(\d+)?$/; const TIME_UNIT_REGEX = /^(\d*(?:\.\d*)?)(h|m|s|ms|f|t)$/; const textAlignToLineAlign = { left: "start", center: "center", right: "end", start: "start", end: "end" }; function parseIMSC1(payload, initPTS, callBack, errorCallBack) { const results = findBox(new Uint8Array(payload), ["mdat"]); if (results.length === 0) { errorCallBack(new Error("Could not parse IMSC1 mdat")); return; } const ttmlList = results.map((mdat) => utf8ArrayToStr(mdat)); const syncTime = toTimescaleFromScale(initPTS.baseTime, 1, initPTS.timescale); try { ttmlList.forEach((ttml) => callBack(parseTTML(ttml, syncTime))); } catch (error) { errorCallBack(error); } } function parseTTML(ttml, syncTime) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(ttml, "text/xml"); const tt = xmlDoc.getElementsByTagName("tt")[0]; if (!tt) { throw new Error("Invalid ttml"); } const defaultRateInfo = { frameRate: 30, subFrameRate: 1, frameRateMultiplier: 0, tickRate: 0 }; const rateInfo = Object.keys(defaultRateInfo).reduce((result, key2) => { result[key2] = tt.getAttribute(`ttp:${key2}`) || defaultRateInfo[key2]; return result; }, {}); const trim = tt.getAttribute("xml:space") !== "preserve"; const styleElements = collectionToDictionary(getElementCollection(tt, "styling", "style")); const regionElements = collectionToDictionary(getElementCollection(tt, "layout", "region")); const cueElements = getElementCollection(tt, "body", "[begin]"); return [].map.call(cueElements, (cueElement) => { const cueText = getTextContent(cueElement, trim); if (!cueText || !cueElement.hasAttribute("begin")) { return null; } const startTime = parseTtmlTime(cueElement.getAttribute("begin"), rateInfo); const duration = parseTtmlTime(cueElement.getAttribute("dur"), rateInfo); let endTime = parseTtmlTime(cueElement.getAttribute("end"), rateInfo); if (startTime === null) { throw timestampParsingError(cueElement); } if (endTime === null) { if (duration === null) { throw timestampParsingError(cueElement); } endTime = startTime + duration; } const cue = new VTTCue(startTime - syncTime, endTime - syncTime, cueText); cue.id = generateCueId(cue.startTime, cue.endTime, cue.text); const region = regionElements[cueElement.getAttribute("region")]; const style = styleElements[cueElement.getAttribute("style")]; const styles = getTtmlStyles(region, style, styleElements); const { textAlign } = styles; if (textAlign) { const lineAlign = textAlignToLineAlign[textAlign]; if (lineAlign) { cue.lineAlign = lineAlign; } cue.align = textAlign; } _extends(cue, styles); return cue; }).filter((cue) => cue !== null); } function getElementCollection(fromElement, parentName, childName) { const parent = fromElement.getElementsByTagName(parentName)[0]; if (parent) { return [].slice.call(parent.querySelectorAll(childName)); } return []; } function collectionToDictionary(elementsWithId) { return elementsWithId.reduce((dict, element) => { const id = element.getAttribute("xml:id"); if (id) { dict[id] = element; } return dict; }, {}); } function getTextContent(element, trim) { return [].slice.call(element.childNodes).reduce((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) { const ttsNs = "http://www.w3.org/ns/ttml#styling"; let regionStyle = null; const styleAttributes = [ "displayAlign", "textAlign", "color", "backgroundColor", "fontSize", "fontFamily" // 'fontWeight', // 'lineHeight', // 'wrapOption', // 'fontStyle', // 'direction', // 'writingMode' ]; const regionStyleName = region != null && region.hasAttribute("style") ? region.getAttribute("style") : null; if (regionStyleName && styleElements.hasOwnProperty(regionStyleName)) { regionStyle = styleElements[regionStyleName]; } return styleAttributes.reduce((styles, name) => { const 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; } let 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) { const m = HMSF_REGEX.exec(timeAttributeValue); const 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) { const m = TIME_UNIT_REGEX.exec(timeAttributeValue); const value = Number(m[1]); const unit = m[2]; switch (unit) { case "h": return value * 3600; case "m": return value * 60; case "ms": return value * 1e3; case "f": return value / rateInfo.frameRate; case "t": return value / rateInfo.tickRate; } return value; } class OutputFilter { constructor(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; } dispatchCue() { if (this.startTime === null) { return; } this.timelineController.addCues(this.trackName, this.startTime, this.endTime, this.screen, this.cueRanges); this.startTime = null; } 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); } reset() { this.cueRanges = []; this.startTime = null; } } class TimelineController { constructor(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; this.lastSn = -1; this.lastPartIndex = -1; this.prevCC = -1; 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); } destroy() { const { hls } = this; 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); this.hls = this.config = this.media = null; this.cea608Parser1 = this.cea608Parser2 = void 0; } initCea608Parsers() { const channel1 = new OutputFilter(this, "textTrack1"); const channel2 = new OutputFilter(this, "textTrack2"); const channel3 = new OutputFilter(this, "textTrack3"); const channel4 = new OutputFilter(this, "textTrack4"); this.cea608Parser1 = new Cea608Parser(1, channel1, channel2); this.cea608Parser2 = new Cea608Parser(3, channel3, channel4); } addCues(trackName, startTime, endTime, screen, cueRanges) { let merged = false; for (let i = cueRanges.length; i--; ) { const cueRange = cueRanges[i]; const 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) { const track = this.captionsTracks[trackName]; this.Cues.newCue(track, startTime, endTime, screen); } else { const cues = this.Cues.newCue(null, startTime, endTime, screen); this.hls.trigger(Events.CUES_PARSED, { type: "captions", cues, track: trackName }); } } // Triggered when an initial PTS is found; used for synchronisation of WebVTT. onInitPtsFound(event, { frag, id, initPTS, timescale }) { const { unparsedVttFrags } = this; if (id === PlaylistLevelType.MAIN) { this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; } if (unparsedVttFrags.length) { this.unparsedVttFrags = []; unparsedVttFrags.forEach((frag2) => { this.onFragLoaded(Events.FRAG_LOADED, frag2); }); } } getExistingTrack(label, language) { const { media } = this; if (media) { for (let i = 0; i < media.textTracks.length; i++) { const textTrack = media.textTracks[i]; if (canReuseVttTextTrack(textTrack, { name: label, lang: language, characteristics: "transcribes-spoken-dialog,describes-music-and-sound" })) { return textTrack; } } } return null; } createCaptionsTrack(trackName) { if (this.config.renderTextTracksNatively) { this.createNativeTrack(trackName); } else { this.createNonNativeTrack(trackName); } } createNativeTrack(trackName) { if (this.captionsTracks[trackName]) { return; } const { captionsProperties, captionsTracks, media } = this; const { label, languageCode } = captionsProperties[trackName]; const existingTrack = this.getExistingTrack(label, languageCode); if (!existingTrack) { const textTrack = this.createTextTrack("captions", label, languageCode); if (textTrack) { textTrack[trackName] = true; captionsTracks[trackName] = textTrack; } } else { captionsTracks[trackName] = existingTrack; clearCurrentCues(captionsTracks[trackName]); sendAddTrackEvent(captionsTracks[trackName], media); } } createNonNativeTrack(trackName) { if (this.nonNativeCaptionsTracks[trackName]) { return; } const trackProperties = this.captionsProperties[trackName]; if (!trackProperties) { return; } const label = trackProperties.label; const track = { _id: trackName, 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] }); } createTextTrack(kind, label, lang) { const media = this.media; if (!media) { return; } return media.addTextTrack(kind, label, lang); } onMediaAttaching(event, data) { this.media = data.media; if (!data.mediaSource) { this._cleanTracks(); } } onMediaDetaching(event, data) { const transferringMedia = !!data.transferMedia; this.media = null; if (transferringMedia) { return; } const { captionsTracks } = this; Object.keys(captionsTracks).forEach((trackName) => { clearCurrentCues(captionsTracks[trackName]); delete captionsTracks[trackName]; }); this.nonNativeCaptionsTracks = {}; } onManifestLoading() { this.lastCc = -1; this.lastSn = -1; this.lastPartIndex = -1; this.prevCC = -1; this.vttCCs = newVTTCCs(); 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(); } } _cleanTracks() { const { media } = this; if (!media) { return; } const textTracks = media.textTracks; if (textTracks) { for (let i = 0; i < textTracks.length; i++) { clearCurrentCues(textTracks[i]); } } } onSubtitleTracksUpdated(event, data) { const tracks = data.subtitleTracks || []; const hasIMSC1 = tracks.some((track) => track.textCodec === IMSC1_CODEC); if (this.config.enableWebVTT || hasIMSC1 && this.config.enableIMSC1) { const listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks); if (listIsIdentical) { this.tracks = tracks; return; } this.textTracks = []; this.tracks = tracks; if (this.config.renderTextTracksNatively) { const media = this.media; const inUseTracks = media ? filterSubtitleTracks(media.textTracks) : null; this.tracks.forEach((track, index) => { let textTrack; if (inUseTracks) { let inUseTrack = null; for (let 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 { const textTrackKind = captionsOrSubtitlesFromCharacteristics(track); textTrack = this.createTextTrack(textTrackKind, track.name, track.lang); if (textTrack) { textTrack.mode = "disabled"; } } if (textTrack) { this.textTracks.push(textTrack); } }); if (inUseTracks != null && inUseTracks.length) { const unusedTextTracks = inUseTracks.filter((t) => t !== null).map((t) => t.label); if (unusedTextTracks.length) { this.hls.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) { const tracksList = this.tracks.map((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 }); } } } onManifestLoaded(event, data) { if (this.config.enableCEA708Captions && data.captions) { data.captions.forEach((captionsTrack) => { const instreamIdMatch = /(?:CC|SERVICE)([1-4])/.exec(captionsTrack.instreamId); if (!instreamIdMatch) { return; } const trackName = `textTrack${instreamIdMatch[1]}`; const trackProperties = this.captionsProperties[trackName]; if (!trackProperties) { return; } trackProperties.label = captionsTrack.name; if (captionsTrack.lang) { trackProperties.languageCode = captionsTrack.lang; } trackProperties.media = captionsTrack; }); } } closedCaptionsForLevel(frag) { const level = this.hls.levels[frag.level]; return level == null ? void 0 : level.attrs["CLOSED-CAPTIONS"]; } onFragLoading(event, data) { if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) { var _data$part$index, _data$part; const { cea608Parser1, cea608Parser2, lastSn } = this; const { cc, sn } = data.frag; const 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; } } onFragLoaded(event, data) { const { frag, payload } = data; if (frag.type === PlaylistLevelType.SUBTITLE) { if (payload.byteLength) { const decryptData = frag.decryptdata; const decrypted = "stats" in data; if (decryptData == null || !decryptData.encrypted || decrypted) { const trackPlaylistMedia = this.tracks[frag.level]; const 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 { this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag, error: new Error("Empty subtitle payload") }); } } } _parseIMSC1(frag, payload) { const hls = this.hls; parseIMSC1(payload, this.initPTS[frag.cc], (cues) => { this._appendCues(cues, frag.level); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: true, frag }); }, (error) => { hls.logger.log(`Failed to parse IMSC1: ${error}`); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag, error }); }); } _parseVTTs(data) { var _frag$initSegment; const { frag, payload } = data; const { initPTS, unparsedVttFrags } = this; const maxAvCC = initPTS.length - 1; if (!initPTS[frag.cc] && maxAvCC === -1) { unparsedVttFrags.push(data); return; } const hls = this.hls; const payloadWebVTT = (_frag$initSegment = frag.initSegment) != null && _frag$initSegment.data ? appendUint8Array(frag.initSegment.data, new Uint8Array(payload)).buffer : payload; parseWebVTT(payloadWebVTT, this.initPTS[frag.cc], this.vttCCs, frag.cc, frag.start, (cues) => { this._appendCues(cues, frag.level); hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: true, frag }); }, (error) => { const missingInitPTS = error.message === "Missing initPTS for VTT MPEGTS"; if (missingInitPTS) { unparsedVttFrags.push(data); } else { this._fallbackToIMSC1(frag, payload); } hls.logger.log(`Failed to parse VTT cue: ${error}`); if (missingInitPTS && maxAvCC > frag.cc) { return; } hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { success: false, frag, error }); }); } _fallbackToIMSC1(frag, payload) { const trackPlaylistMedia = this.tracks[frag.level]; if (!trackPlaylistMedia.textCodec) { parseIMSC1(payload, this.initPTS[frag.cc], () => { trackPlaylistMedia.textCodec = IMSC1_CODEC; this._parseIMSC1(frag, payload); }, () => { trackPlaylistMedia.textCodec = "wvtt"; }); } } _appendCues(cues, fragLevel) { const hls = this.hls; if (this.config.renderTextTracksNatively) { const textTrack = this.textTracks[fragLevel]; if (!textTrack || textTrack.mode === "disabled") { return; } cues.forEach((cue) => addCueToTrack(textTrack, cue)); } else { const currentTrack = this.tracks[fragLevel]; if (!currentTrack) { return; } const track = currentTrack.default ? "default" : "subtitles" + fragLevel; hls.trigger(Events.CUES_PARSED, { type: "subtitles", cues, track }); } } onFragDecrypted(event, data) { const { frag } = data; if (frag.type === PlaylistLevelType.SUBTITLE) { this.onFragLoaded(Events.FRAG_LOADED, data); } } onSubtitleTracksCleared() { this.tracks = []; this.captionsTracks = {}; } onFragParsingUserdata(event, data) { if (!this.enabled || !this.config.enableCEA708Captions) { return; } const { frag, samples } = data; if (frag.type === PlaylistLevelType.MAIN && this.closedCaptionsForLevel(frag) === "NONE") { return; } for (let i = 0; i < samples.length; i++) { const ccBytes = samples[i].bytes; if (ccBytes) { if (!this.cea608Parser1) { this.initCea608Parsers(); } const ccdatas = this.extractCea608Data(ccBytes); this.cea608Parser1.addData(samples[i].pts, ccdatas[0]); this.cea608Parser2.addData(samples[i].pts, ccdatas[1]); } } } onBufferFlushing(event, { startOffset, endOffset, endOffsetSubtitles, type }) { const { media } = this; if (!media || media.currentTime < endOffset) { return; } if (!type || type === "video") { const { captionsTracks } = this; Object.keys(captionsTracks).forEach((trackName) => removeCuesInRange(captionsTracks[trackName], startOffset, endOffset)); } if (this.config.renderTextTracksNatively) { if (startOffset === 0 && endOffsetSubtitles !== void 0) { const { textTracks } = this; Object.keys(textTracks).forEach((trackName) => removeCuesInRange(textTracks[trackName], startOffset, endOffsetSubtitles)); } } } extractCea608Data(byteArray) { const actualCCBytes = [[], []]; const count = byteArray[0] & 31; let position = 2; for (let j = 0; j < count; j++) { const tmpByte = byteArray[position++]; const ccbyte1 = 127 & byteArray[position++]; const ccbyte2 = 127 & byteArray[position++]; if (ccbyte1 === 0 && ccbyte2 === 0) { continue; } const ccValid = (4 & tmpByte) !== 0; if (ccValid) { const ccType = 3 & tmpByte; if (0 === ccType || 1 === ccType) { actualCCBytes[ccType].push(ccbyte1); actualCCBytes[ccType].push(ccbyte2); } } } return actualCCBytes; } } 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 } }; } const WHITESPACE_CHAR = /\s/; const Cues = { newCue(track, startTime, endTime, captionScreen) { const result = []; let row; let cue; let indenting; let indent; let text; const Cue = self.VTTCue || self.TextTrackCue; for (let r = 0; r < captionScreen.rows.length; r++) { row = captionScreen.rows[r]; indenting = true; indent = 0; text = ""; if (!row.isEmpty()) { var _track$cues; for (let 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; } } row.cueStartTime = startTime; if (startTime === endTime) { endTime += 1e-4; } if (indent >= 16) { indent--; } else { indent++; } const cueText = fixLineBreaks(text.trim()); const id = generateCueId(startTime, endTime, cueText); 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"; cue.position = 10 + Math.min(80, Math.floor(indent * 8 / 32) * 10); result.push(cue); } } } if (track && result.length) { result.sort((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((cue2) => addCueToTrack(track, cue2)); } return result; } }; function fetchSupported() { if ( // @ts-ignore self.fetch && self.AbortController && self.ReadableStream && self.Request ) { try { new self.ReadableStream({}); return true; } catch (e) { } } return false; } const BYTERANGE = /(\d+)-(\d+)\/(\d+)/; class FetchLoader { constructor(config) { 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(); } destroy() { this.loader = this.callbacks = this.context = this.config = this.request = null; this.abortInternal(); this.response = null; this.fetchSetup = this.controller = this.stats = null; } abortInternal() { if (this.controller && !this.stats.loading.end) { this.stats.aborted = true; this.controller.abort(); } } abort() { var _this$callbacks; this.abortInternal(); if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { this.callbacks.onAbort(this.stats, this.context, this.response); } } load(context, config, callbacks) { const stats = this.stats; if (stats.loading.start) { throw new Error("Loader can only be used once."); } stats.loading.start = self.performance.now(); const initParams = getRequestParameters(context, this.controller.signal); const isArrayBuffer = context.responseType === "arraybuffer"; const LENGTH = isArrayBuffer ? "byteLength" : "length"; const { maxTimeToFirstByteMs, maxLoadTimeMs } = config.loadPolicy; 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(() => { if (this.callbacks) { this.abortInternal(); this.callbacks.onTimeout(stats, context, this.response); } }, config.timeout); const fetchPromise = isPromise(this.request) ? this.request.then(self.fetch) : self.fetch(this.request); fetchPromise.then((response) => { var _this$callbacks2; this.response = this.loader = response; const first = Math.max(self.performance.now(), stats.loading.start); self.clearTimeout(this.requestTimeout); config.timeout = maxLoadTimeMs; this.requestTimeout = self.setTimeout(() => { if (this.callbacks) { this.abortInternal(); this.callbacks.onTimeout(stats, context, this.response); } }, maxLoadTimeMs - (first - stats.loading.start)); if (!response.ok) { const { status: status2, statusText } = response; throw new FetchError(statusText || "fetch, bad network response", status2, response); } stats.loading.first = first; stats.total = getContentLength(response.headers) || stats.total; const onProgress = (_this$callbacks2 = this.callbacks) == null ? void 0 : _this$callbacks2.onProgress; 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((responseData) => { var _this$callbacks3, _this$callbacks4; const 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); const total = responseData[LENGTH]; if (total) { stats.loaded = stats.total = total; } const loaderResponse = { url: response.url, data: responseData, code: response.status }; const onProgress = (_this$callbacks3 = this.callbacks) == null ? void 0 : _this$callbacks3.onProgress; if (onProgress && !isFiniteNumber(config.highWaterMark)) { onProgress(stats, context, responseData, response); } (_this$callbacks4 = this.callbacks) == null ? void 0 : _this$callbacks4.onSuccess(loaderResponse, stats, context, response); }).catch((error) => { var _this$callbacks5; self.clearTimeout(this.requestTimeout); if (stats.aborted) { return; } const code = !error ? 0 : error.code || 0; const text = !error ? null : error.message; (_this$callbacks5 = this.callbacks) == null ? void 0 : _this$callbacks5.onError({ code, text }, context, error ? error.details : null, stats); }); } getCacheAge() { let result = null; if (this.response) { const ageHeader = this.response.headers.get("age"); result = ageHeader ? parseFloat(ageHeader) : null; } return result; } getResponseHeader(name) { return this.response ? this.response.headers.get(name) : null; } loadProgressively(response, stats, context, highWaterMark = 0, onProgress) { const chunkCache = new ChunkCache(); const reader = response.body.getReader(); const pump = () => { return reader.read().then((data) => { if (data.done) { if (chunkCache.dataLength) { onProgress(stats, context, chunkCache.flush().buffer, response); } return Promise.resolve(new ArrayBuffer(0)); } const chunk = data.value; const len = chunk.length; stats.loaded += len; if (len < highWaterMark || chunkCache.dataLength) { chunkCache.push(chunk); if (chunkCache.dataLength >= highWaterMark) { onProgress(stats, context, chunkCache.flush().buffer, response); } } else { onProgress(stats, context, chunk.buffer, response); } return pump(); }).catch(() => { return Promise.reject(); }); }; return pump(); } } function getRequestParameters(context, signal) { const initParams = { method: "GET", mode: "cors", credentials: "same-origin", 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) { const result = BYTERANGE.exec(byteRangeHeader); if (result) { return parseInt(result[2]) - parseInt(result[1]) + 1; } } function getContentLength(headers) { const contentRange = headers.get("Content-Range"); if (contentRange) { const byteRangeLength = getByteRangeLength(contentRange); if (isFiniteNumber(byteRangeLength)) { return byteRangeLength; } } const contentLength = headers.get("Content-Length"); if (contentLength) { return parseInt(contentLength); } } function getRequest(context, initParams) { return new self.Request(context.url, initParams); } class FetchError extends Error { constructor(message, code, details) { super(message); this.code = void 0; this.details = void 0; this.code = code; this.details = details; } } const AGE_HEADER_LINE_REGEX = /^age:\s*[\d.]+\s*$/im; class XhrLoader { constructor(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; } destroy() { this.callbacks = null; this.abortInternal(); this.loader = null; this.config = null; this.context = null; this.xhrSetup = null; } abortInternal() { const 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(); } } } abort() { var _this$callbacks; this.abortInternal(); if ((_this$callbacks = this.callbacks) != null && _this$callbacks.onAbort) { this.callbacks.onAbort(this.stats, this.context, this.loader); } } 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(); } loadInternal() { const { config, context } = this; if (!config || !context) { return; } const xhr = this.loader = new self.XMLHttpRequest(); const stats = this.stats; stats.loading.first = 0; stats.loaded = 0; stats.aborted = false; const xhrSetup = this.xhrSetup; if (xhrSetup) { Promise.resolve().then(() => { if (this.loader !== xhr || this.stats.aborted) return; return xhrSetup(xhr, context.url); }).catch((error) => { if (this.loader !== xhr || this.stats.aborted) return; xhr.open("GET", context.url, true); return xhrSetup(xhr, context.url); }).then(() => { if (this.loader !== xhr || this.stats.aborted) return; this.openAndSendXhr(xhr, context, config); }).catch((error) => { var _this$callbacks2; (_this$callbacks2 = this.callbacks) == null ? void 0 : _this$callbacks2.onError({ code: xhr.status, text: error.message }, context, xhr, stats); return; }); } else { this.openAndSendXhr(xhr, context, config); } } openAndSendXhr(xhr, context, config) { if (!xhr.readyState) { xhr.open("GET", context.url, true); } const headers = context.headers; const { maxTimeToFirstByteMs, maxLoadTimeMs } = config.loadPolicy; if (headers) { for (const 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; self.clearTimeout(this.requestTimeout); config.timeout = maxTimeToFirstByteMs && isFiniteNumber(maxTimeToFirstByteMs) ? maxTimeToFirstByteMs : maxLoadTimeMs; this.requestTimeout = self.setTimeout(this.loadtimeout.bind(this), config.timeout); xhr.send(); } readystatechange() { const { context, loader: xhr, stats } = this; if (!context || !xhr) { return; } const readyState = xhr.readyState; const config = this.config; if (stats.aborted) { return; } if (readyState >= 2) { if (stats.loading.first === 0) { stats.loading.first = Math.max(self.performance.now(), stats.loading.start); 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; const status2 = xhr.status; const useResponseText = xhr.responseType === "text" ? xhr.responseText : null; if (status2 >= 200 && status2 < 300) { const data = useResponseText != null ? useResponseText : xhr.response; if (data != null) { var _this$callbacks3, _this$callbacks4; stats.loading.end = Math.max(self.performance.now(), stats.loading.first); const len = xhr.responseType === "arraybuffer" ? data.byteLength : data.length; stats.loaded = stats.total = len; stats.bwEstimate = stats.total * 8e3 / (stats.loading.end - stats.loading.first); const onProgress = (_this$callbacks3 = this.callbacks) == null ? void 0 : _this$callbacks3.onProgress; if (onProgress) { onProgress(stats, context, data, xhr); } const _response = { url: xhr.responseURL, data, code: status2 }; (_this$callbacks4 = this.callbacks) == null ? void 0 : _this$callbacks4.onSuccess(_response, stats, context, xhr); return; } } const retryConfig = config.loadPolicy.errorRetry; const retryCount = stats.retry; const response = { url: context.url, data: void 0, code: status2 }; if (shouldRetry(retryConfig, retryCount, false, response)) { this.retry(retryConfig); } else { var _this$callbacks5; logger.error(`${status2} while loading ${context.url}`); (_this$callbacks5 = this.callbacks) == null ? void 0 : _this$callbacks5.onError({ code: status2, text: xhr.statusText }, context, xhr, stats); } } } } loadtimeout() { if (!this.config) return; const retryConfig = this.config.loadPolicy.timeoutRetry; const 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}`); const callbacks = this.callbacks; if (callbacks) { this.abortInternal(); callbacks.onTimeout(this.stats, this.context, this.loader); } } } retry(retryConfig) { const { context, stats } = this; 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`); this.abortInternal(); this.loader = null; self.clearTimeout(this.retryTimeout); this.retryTimeout = self.setTimeout(this.loadInternal.bind(this), this.retryDelay); } loadprogress(event) { const stats = this.stats; stats.loaded = event.loaded; if (event.lengthComputable) { stats.total = event.total; } } getCacheAge() { let result = null; if (this.loader && AGE_HEADER_LINE_REGEX.test(this.loader.getAllResponseHeaders())) { const ageHeader = this.loader.getResponseHeader("age"); result = ageHeader ? parseFloat(ageHeader) : null; } return result; } getResponseHeader(name) { if (this.loader && new RegExp(`^${name}:\\s*[\\d.]+\\s*$`, "im").test(this.loader.getAllResponseHeaders())) { return this.loader.getResponseHeader(name); } return null; } } const defaultLoadPolicy = { maxTimeToFirstByteMs: 8e3, maxLoadTimeMs: 2e4, timeoutRetry: null, errorRetry: null }; const hlsDefaultConfig = _objectSpread2(_objectSpread2({ autoStartLoad: true, // used by stream-controller startPosition: -1, // used by stream-controller defaultAudioCodec: void 0, // 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 maxDevicePixelRatio: Number.POSITIVE_INFINITY, // 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 * 1e3 * 1e3, // used by stream-controller maxFragLookUpTolerance: 0.25, // used by stream-controller maxBufferHole: 0.1, // used by stream-controller and gap-controller detectStallWithCurrentTimeMs: 1250, // used by gap-controller highBufferWatchdogPeriod: 2, // used by gap-controller nudgeOffset: 0.1, // used by gap-controller nudgeMaxRetry: 3, // used by gap-controller nudgeOnVideoHole: true, // used by gap-controller liveSyncDurationCount: 3, // used by latency-controller liveSyncOnStallIncrease: 1, // used by latency-controller liveMaxLatencyDurationCount: Infinity, // used by latency-controller liveSyncDuration: void 0, // used by latency-controller liveMaxLatencyDuration: void 0, // 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: void 0, // used by level-controller startFragPrefetch: false, // used by stream-controller fpsDroppedMonitoringPeriod: 5e3, // used by fps-controller fpsDroppedMonitoringThreshold: 0.2, // used by fps-controller appendErrorMaxRetry: 3, // used by buffer-controller ignorePlaylistParsingErrors: false, loader: XhrLoader, // loader: FetchLoader, fLoader: void 0, // used by fragment-loader pLoader: void 0, // used by playlist-loader xhrSetup: void 0, // used by xhr-loader licenseXhrSetup: void 0, // used by eme-controller licenseResponseCallback: void 0, // 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: void 0, // 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: void 0, enableDateRangeMetadataCues: true, enableEmsgMetadataCues: true, enableEmsgKLVMetadata: false, enableID3MetadataCues: true, enableInterstitialPlayback: true, interstitialAppendInPlace: true, interstitialLiveLookAhead: 10, useMediaCapabilities: true, certLoadPolicy: { default: defaultLoadPolicy }, 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: Infinity, 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 } } }, interstitialAssetListLoadPolicy: { default: { maxTimeToFirstByteMs: 1e4, maxLoadTimeMs: 3e4, timeoutRetry: { maxNumRetry: 0, retryDelayMs: 0, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 0, retryDelayMs: 1e3, maxRetryDelayMs: 8e3 } } }, // These default settings are deprecated in favor of the above policies // and are maintained for backwards compatibility manifestLoadingTimeOut: 1e4, manifestLoadingMaxRetry: 1, manifestLoadingRetryDelay: 1e3, manifestLoadingMaxRetryTimeout: 64e3, levelLoadingTimeOut: 1e4, levelLoadingMaxRetry: 4, levelLoadingRetryDelay: 1e3, levelLoadingMaxRetryTimeout: 64e3, fragLoadingTimeOut: 2e4, fragLoadingMaxRetry: 6, fragLoadingRetryDelay: 1e3, fragLoadingMaxRetryTimeout: 64e3 }, timelineConfig()), {}, { subtitleStreamController: SubtitleStreamController, subtitleTrackController: SubtitleTrackController, timelineController: TimelineController, audioStreamController: AudioStreamController, audioTrackController: AudioTrackController, emeController: EMEController, cmcdController: CMCDController, contentSteeringController: ContentSteeringController, interstitialsController: InterstitialsController }); 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 }; } function mergeConfig(defaultConfig, userConfig, logger2) { 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 !== void 0 && (userConfig.liveSyncDurationCount === void 0 || userConfig.liveMaxLatencyDurationCount <= userConfig.liveSyncDurationCount)) { throw new Error('Illegal hls.js config: "liveMaxLatencyDurationCount" must be greater than "liveSyncDurationCount"'); } if (userConfig.liveMaxLatencyDuration !== void 0 && (userConfig.liveSyncDuration === void 0 || userConfig.liveMaxLatencyDuration <= userConfig.liveSyncDuration)) { throw new Error('Illegal hls.js config: "liveMaxLatencyDuration" must be greater than "liveSyncDuration"'); } const defaultsCopy = deepCpy(defaultConfig); const deprecatedSettingTypes = ["manifest", "level", "frag"]; const deprecatedSettings = ["TimeOut", "MaxRetry", "RetryDelay", "MaxRetryTimeout"]; deprecatedSettingTypes.forEach((type) => { const policyName = `${type === "level" ? "playlist" : type}LoadPolicy`; const policyNotSet = userConfig[policyName] === void 0; const report = []; deprecatedSettings.forEach((setting) => { const deprecatedSetting = `${type}Loading${setting}`; const value = userConfig[deprecatedSetting]; if (value !== void 0 && policyNotSet) { report.push(deprecatedSetting); const 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) { logger2.warn(`hls.js config: "${report.join('", "')}" setting(s) are deprecated, use "${policyName}": ${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((result, key2) => { result[key2] = deepCpy(obj[key2]); return result; }, {}); } return obj; } function enableStreamingMode(config, logger2) { const currentLoader = config.loader; if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) { logger2.log("[config]: Custom loader detected, cannot enable progressive streaming"); config.progressive = false; } else { const canStreamProgressively = fetchSupported(); if (canStreamProgressively) { config.loader = FetchLoader; config.progressive = true; config.enableSoftwareAES = true; logger2.log("[config]: Progressive streaming enabled, using FetchLoader"); } } } const MAX_START_GAP_JUMP = 2; const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1; const SKIP_BUFFER_RANGE_START = 0.05; const TICK_INTERVAL$1 = 100; class GapController extends TaskLoop { constructor(hls, fragmentTracker) { super("gap-controller", hls.logger); this.hls = null; this.fragmentTracker = null; this.media = null; this.mediaSource = void 0; this.nudgeRetry = 0; this.stallReported = false; this.stalled = null; this.moved = false; this.seeking = false; this.buffered = {}; this.lastCurrentTime = 0; this.ended = 0; this.waiting = 0; this.onMediaPlaying = () => { this.ended = 0; this.waiting = 0; }; this.onMediaWaiting = () => { var _this$media; if ((_this$media = this.media) != null && _this$media.seeking) { return; } this.waiting = self.performance.now(); this.tick(); }; this.onMediaEnded = () => { if (this.hls) { var _this$media2; this.ended = ((_this$media2 = this.media) == null ? void 0 : _this$media2.currentTime) || 1; this.hls.trigger(Events.MEDIA_ENDED, { stalled: false }); } }; this.hls = hls; this.fragmentTracker = fragmentTracker; this.registerListeners(); } registerListeners() { const { hls } = this; if (hls) { hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); } } unregisterListeners() { const { hls } = this; if (hls) { hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); } } destroy() { super.destroy(); this.unregisterListeners(); this.media = this.hls = this.fragmentTracker = null; this.mediaSource = void 0; } onMediaAttached(event, data) { this.setInterval(TICK_INTERVAL$1); this.mediaSource = data.mediaSource; const media = this.media = data.media; addEventListener(media, "playing", this.onMediaPlaying); addEventListener(media, "waiting", this.onMediaWaiting); addEventListener(media, "ended", this.onMediaEnded); } onMediaDetaching(event, data) { this.clearInterval(); const { media } = this; if (media) { removeEventListener(media, "playing", this.onMediaPlaying); removeEventListener(media, "waiting", this.onMediaWaiting); removeEventListener(media, "ended", this.onMediaEnded); this.media = null; } this.mediaSource = void 0; } onBufferAppended(event, data) { this.buffered = data.timeRanges; } get hasBuffered() { return Object.keys(this.buffered).length > 0; } tick() { var _this$media3; if (!((_this$media3 = this.media) != null && _this$media3.readyState) || !this.hasBuffered) { return; } const currentTime = this.media.currentTime; this.poll(currentTime, this.lastCurrentTime); this.lastCurrentTime = currentTime; } /** * 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 */ poll(currentTime, lastCurrentTime) { var _this$hls, _this$hls2; const config = (_this$hls = this.hls) == null ? void 0 : _this$hls.config; if (!config) { return; } const { media, stalled } = this; if (!media) { return; } const { seeking } = media; const seeked = this.seeking && !seeking; const beginSeek = !this.seeking && seeking; const pausedEndedOrHalted = media.paused && !seeking || media.ended || media.playbackRate === 0; this.seeking = seeking; if (currentTime !== lastCurrentTime) { if (lastCurrentTime) { this.ended = 0; } this.moved = true; if (!seeking) { this.nudgeRetry = 0; if (config.nudgeOnVideoHole && !pausedEndedOrHalted && currentTime > lastCurrentTime) { this.nudgeOnVideoHole(currentTime, lastCurrentTime); } } if (this.waiting === 0) { this.stallResolved(currentTime); } return; } if (beginSeek || seeked) { if (seeked) { this.stallResolved(currentTime); } return; } if (pausedEndedOrHalted) { this.nudgeRetry = 0; this.stallResolved(currentTime); if (!this.ended && media.ended && this.hls) { this.ended = currentTime || 1; this.hls.trigger(Events.MEDIA_ENDED, { stalled: false }); } return; } if (!BufferHelper.getBuffered(media).length) { this.nudgeRetry = 0; return; } const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const nextStart = bufferInfo.nextStart || 0; const fragmentTracker = this.fragmentTracker; if (seeking && fragmentTracker && this.hls) { const inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, currentTime); const hasEnoughBuffer = bufferInfo.len > MAX_START_GAP_JUMP; const noBufferHole = !nextStart || inFlightDependency || nextStart - currentTime > MAX_START_GAP_JUMP && !fragmentTracker.getPartialFragment(currentTime); if (hasEnoughBuffer || noBufferHole) { return; } this.moved = false; } const levelDetails = (_this$hls2 = this.hls) == null ? void 0 : _this$hls2.latestLevelDetails; if (!this.moved && this.stalled !== null && fragmentTracker) { const isBuffered = bufferInfo.len > 0; if (!isBuffered && !nextStart) { return; } const startJump = Math.max(nextStart, bufferInfo.start || 0) - currentTime; const isLive = !!(levelDetails != null && levelDetails.live); const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP; const partialOrGap = fragmentTracker.getPartialFragment(currentTime); if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) { if (!media.paused) { this._trySkipBufferHole(partialOrGap); } return; } } const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs; const tnow = self.performance.now(); const tWaiting = this.waiting; if (stalled === null) { if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) { this.stalled = tWaiting; } else { this.stalled = tnow; } return; } const stalledDuration = tnow - stalled; if (!seeking && (stalledDuration >= detectStallWithCurrentTimeMs || tWaiting) && this.hls) { var _this$mediaSource; if (((_this$mediaSource = this.mediaSource) == null ? void 0 : _this$mediaSource.readyState) === "ended" && !(levelDetails != null && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) { if (this.ended) { return; } this.ended = currentTime || 1; this.hls.trigger(Events.MEDIA_ENDED, { stalled: true }); return; } this._reportStall(bufferInfo); if (!this.media || !this.hls) { return; } } const bufferedWithHoles = BufferHelper.bufferInfo(media, currentTime, config.maxBufferHole); this._tryFixBufferStall(bufferedWithHoles, stalledDuration); } stallResolved(currentTime) { const stalled = this.stalled; if (stalled && this.hls) { this.stalled = null; if (this.stallReported) { const stalledDuration = self.performance.now() - stalled; this.log(`playback not stuck anymore @${currentTime}, after ${Math.round(stalledDuration)}ms`); this.stallReported = false; this.waiting = 0; this.hls.trigger(Events.STALL_RESOLVED, {}); } } } nudgeOnVideoHole(currentTime, lastCurrentTime) { var _this$buffered$audio; const videoSourceBuffered = this.buffered.video; if (this.hls && this.media && this.fragmentTracker && (_this$buffered$audio = this.buffered.audio) != null && _this$buffered$audio.length && videoSourceBuffered && videoSourceBuffered.length > 1 && currentTime > videoSourceBuffered.end(0)) { const audioBufferInfo = BufferHelper.bufferedInfo(BufferHelper.timeRangesToArray(this.buffered.audio), currentTime, 0); if (audioBufferInfo.len > 1 && lastCurrentTime >= audioBufferInfo.start) { const videoTimes = BufferHelper.timeRangesToArray(videoSourceBuffered); const lastBufferedIndex = BufferHelper.bufferedInfo(videoTimes, lastCurrentTime, 0).bufferedIndex; if (lastBufferedIndex > -1 && lastBufferedIndex < videoTimes.length - 1) { const bufferedIndex = BufferHelper.bufferedInfo(videoTimes, currentTime, 0).bufferedIndex; const holeStart = videoTimes[lastBufferedIndex].end; const holeEnd = videoTimes[lastBufferedIndex + 1].start; if ((bufferedIndex === -1 || bufferedIndex > lastBufferedIndex) && holeEnd - holeStart < 1 && // `maxBufferHole` may be too small and setting it to 0 should not disable this feature currentTime - holeStart < 2) { const error = new Error(`nudging playhead to flush pipeline after video hole. currentTime: ${currentTime} hole: ${holeStart} -> ${holeEnd} buffered index: ${bufferedIndex}`); this.warn(error.message); this.media.currentTime += 1e-6; const frag = this.fragmentTracker.getPartialFragment(currentTime) || void 0; const bufferInfo = BufferHelper.bufferInfo(this.media, currentTime, 0); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false, error, reason: error.message, frag, buffer: bufferInfo.len, bufferInfo }); } } } } } /** * 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 */ _tryFixBufferStall(bufferInfo, stalledDurationMs) { var _this$hls3, _this$hls4; const { fragmentTracker, media } = this; const config = (_this$hls3 = this.hls) == null ? void 0 : _this$hls3.config; if (!media || !fragmentTracker || !config) { return; } const currentTime = media.currentTime; const levelDetails = (_this$hls4 = this.hls) == null ? void 0 : _this$hls4.latestLevelDetails; const partial = fragmentTracker.getPartialFragment(currentTime); if (partial || levelDetails != null && levelDetails.live && currentTime < levelDetails.fragmentStart) { const targetTime = this._trySkipBufferHole(partial); if (targetTime || !this.media) { return; } } const bufferedRanges = bufferInfo.buffered; if ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && (stalledDurationMs > config.highBufferWatchdogPeriod * 1e3 || this.waiting)) { this.warn("Trying to nudge playhead over buffer-hole"); this._tryNudgeBuffer(bufferInfo); } } /** * 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 */ _reportStall(bufferInfo) { const { hls, media, stallReported, stalled } = this; if (!stallReported && stalled !== null && media && hls) { this.stallReported = true; const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${stringify(bufferInfo)})`); this.warn(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, fatal: false, error, buffer: bufferInfo.len, bufferInfo, stalled: { start: stalled } }); } } /** * 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 */ _trySkipBufferHole(partial) { var _this$hls5; const { fragmentTracker, media } = this; const config = (_this$hls5 = this.hls) == null ? void 0 : _this$hls5.config; if (!media || !fragmentTracker || !config) { return 0; } const currentTime = media.currentTime; const bufferInfo = BufferHelper.bufferInfo(media, currentTime, 0); const startTime = currentTime < bufferInfo.start ? bufferInfo.start : bufferInfo.nextStart; if (startTime && this.hls) { const bufferStarved = bufferInfo.len <= config.maxBufferHole; const waiting = bufferInfo.len > 0 && bufferInfo.len < 1 && media.readyState < 3; const gapLength = startTime - currentTime; if (gapLength > 0 && (bufferStarved || waiting)) { if (gapLength > config.maxBufferHole) { let startGap = false; if (currentTime === 0) { const startFrag = fragmentTracker.getAppendedFrag(0, PlaylistLevelType.MAIN); if (startFrag && startTime < startFrag.end) { startGap = true; } } if (!startGap) { const startProvisioned = partial || fragmentTracker.getAppendedFrag(currentTime, PlaylistLevelType.MAIN); if (startProvisioned) { var _this$hls$loadLevelOb; if (!((_this$hls$loadLevelOb = this.hls.loadLevelObj) != null && _this$hls$loadLevelOb.details)) { return 0; } const inFlightDependency = getInFlightDependency(this.hls.inFlightFragments, startTime); if (inFlightDependency) { return 0; } let moreToLoad = false; let pos = startProvisioned.end; while (pos < startTime) { const provisioned = fragmentTracker.getPartialFragment(pos); if (provisioned) { pos += provisioned.duration; } else { moreToLoad = true; break; } } if (moreToLoad) { return 0; } } } } const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS); this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`); this.moved = true; media.currentTime = targetTime; if (!(partial != null && partial.gap)) { const error = new Error(`fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false, error, reason: error.message, frag: partial || void 0, buffer: bufferInfo.len, bufferInfo }); } return targetTime; } } return 0; } /** * Attempts to fix buffer stalls by advancing the mediaElement's current time by a small amount. * @private */ _tryNudgeBuffer(bufferInfo) { const { hls, media, nudgeRetry } = this; const config = hls == null ? void 0 : hls.config; if (!media || !config) { return 0; } const currentTime = media.currentTime; this.nudgeRetry++; if (nudgeRetry < config.nudgeMaxRetry) { const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset; const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`); this.warn(error.message); media.currentTime = targetTime; hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_NUDGE_ON_STALL, error, fatal: false, buffer: bufferInfo.len, bufferInfo }); } else { const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`); this.error(error.message); hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_STALLED_ERROR, error, fatal: true, buffer: bufferInfo.len, bufferInfo }); } } } function getInFlightDependency(inFlightFragments, currentTime) { const main = inFlight(inFlightFragments.main); if (main && main.start <= currentTime) { return main; } const audio = inFlight(inFlightFragments.audio); if (audio && audio.start <= currentTime) { return audio; } return null; } function inFlight(inFlightData) { if (!inFlightData) { return null; } switch (inFlightData.state) { case State.IDLE: case State.STOPPED: case State.ENDED: case State.ERROR: return null; } return inFlightData.frag; } const MIN_CUE_DURATION = 0.25; function getCueClass() { if (typeof self === "undefined") return void 0; return self.VTTCue || self.TextTrackCue; } function createCueWithDataFields(Cue, startTime, endTime, data, type) { let cue = new Cue(startTime, endTime, ""); try { cue.value = data; if (type) { cue.type = type; } } catch (e) { cue = new Cue(startTime, endTime, stringify(type ? _objectSpread2({ type }, data) : data)); } return cue; } const MAX_CUE_ENDTIME = (() => { const Cue = getCueClass(); try { Cue && new Cue(0, Number.POSITIVE_INFINITY, ""); } catch (e) { return Number.MAX_VALUE; } return Number.POSITIVE_INFINITY; })(); function hexToArrayBuffer(str) { return Uint8Array.from(str.replace(/^0x/, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")).buffer; } class ID3TrackController { constructor(hls) { this.hls = void 0; this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; this.removeCues = true; this.onEventCueEnter = () => { if (!this.hls) { return; } this.hls.trigger(Events.EVENT_CUE_ENTER, {}); }; this.hls = hls; this._registerListeners(); } destroy() { this._unregisterListeners(); this.id3Track = null; this.media = null; this.dateRangeCuesAppended = {}; this.hls = this.onEventCueEnter = null; } _registerListeners() { const { hls } = this; hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); 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); hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this); } _unregisterListeners() { const { hls } = this; hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); 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); hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this); } // Add ID3 metatadata text track. onMediaAttaching(event, data) { var _data$overrides; this.media = data.media; if (((_data$overrides = data.overrides) == null ? void 0 : _data$overrides.cueRemoval) === false) { this.removeCues = false; } } onMediaAttached() { const details = this.hls.latestLevelDetails; if (details) { this.updateDateRangeCues(details); } } onMediaDetaching(event, data) { this.media = null; const transferringMedia = !!data.transferMedia; if (transferringMedia) { return; } if (this.id3Track) { if (this.removeCues) { clearCurrentCues(this.id3Track, this.onEventCueEnter); } this.id3Track = null; } this.dateRangeCuesAppended = {}; } onManifestLoading() { this.dateRangeCuesAppended = {}; } createTrack(media) { const track = this.getID3Track(media.textTracks); track.mode = "hidden"; return track; } getID3Track(textTracks) { if (!this.media) { return; } for (let i = 0; i < textTracks.length; i++) { const textTrack = textTracks[i]; if (textTrack.kind === "metadata" && textTrack.label === "id3") { sendAddTrackEvent(textTrack, this.media); return textTrack; } } return this.media.addTextTrack("metadata", "id3"); } onFragParsingMetadata(event, data) { if (!this.media) { return; } const { hls: { config: { enableEmsgMetadataCues, enableID3MetadataCues } } } = this; if (!enableEmsgMetadataCues && !enableID3MetadataCues) { return; } const { samples } = data; if (!this.id3Track) { this.id3Track = this.createTrack(this.media); } const Cue = getCueClass(); if (!Cue) { return; } for (let i = 0; i < samples.length; i++) { const type = samples[i].type; if (type === MetadataSchema.emsg && !enableEmsgMetadataCues || !enableID3MetadataCues) { continue; } const frames = getId3Frames(samples[i].data); if (frames) { const startTime = samples[i].pts; let endTime = startTime + samples[i].duration; if (endTime > MAX_CUE_ENDTIME) { endTime = MAX_CUE_ENDTIME; } const timeDiff = endTime - startTime; if (timeDiff <= 0) { endTime = startTime + MIN_CUE_DURATION; } for (let j = 0; j < frames.length; j++) { const frame = frames[j]; if (!isId3TimestampFrame(frame)) { this.updateId3CueEnds(startTime, type); const cue = createCueWithDataFields(Cue, startTime, endTime, frame, type); if (cue) { this.id3Track.addCue(cue); } } } } } } updateId3CueEnds(startTime, type) { var _this$id3Track; const cues = (_this$id3Track = this.id3Track) == null ? void 0 : _this$id3Track.cues; if (cues) { for (let i = cues.length; i--; ) { const cue = cues[i]; if (cue.type === type && cue.startTime < startTime && cue.endTime === MAX_CUE_ENDTIME) { cue.endTime = startTime; } } } } onBufferFlushing(event, { startOffset, endOffset, type }) { const { id3Track, hls } = this; if (!hls) { return; } const { config: { enableEmsgMetadataCues, enableID3MetadataCues } } = hls; if (id3Track && (enableEmsgMetadataCues || enableID3MetadataCues)) { let predicate; if (type === "audio") { predicate = (cue) => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues; } else if (type === "video") { predicate = (cue) => cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; } else { predicate = (cue) => cue.type === MetadataSchema.audioId3 && enableID3MetadataCues || cue.type === MetadataSchema.emsg && enableEmsgMetadataCues; } removeCuesInRange(id3Track, startOffset, endOffset, predicate); } } onLevelUpdated(event, { details }) { this.updateDateRangeCues(details, true); } onLevelPtsUpdated(event, data) { if (Math.abs(data.drift) > 0.01) { this.updateDateRangeCues(data.details); } } updateDateRangeCues(details, removeOldCues) { if (!this.media || !details.hasProgramDateTime || !this.hls.config.enableDateRangeMetadataCues) { return; } const { id3Track } = this; const { dateRanges } = details; const ids = Object.keys(dateRanges); let dateRangeCuesAppended = this.dateRangeCuesAppended; if (id3Track && removeOldCues) { var _id3Track$cues; if ((_id3Track$cues = id3Track.cues) != null && _id3Track$cues.length) { const idsToRemove = Object.keys(dateRangeCuesAppended).filter((id) => !ids.includes(id)); for (let i = idsToRemove.length; i--; ) { const id = idsToRemove[i]; const cues = dateRangeCuesAppended[id].cues; delete dateRangeCuesAppended[id]; Object.keys(cues).forEach((key2) => { try { const cue = cues[key2]; cue.removeEventListener("enter", this.onEventCueEnter); id3Track.removeCue(cue); } catch (e) { } }); } } else { dateRangeCuesAppended = this.dateRangeCuesAppended = {}; } } const 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); } const Cue = getCueClass(); for (let i = 0; i < ids.length; i++) { const id = ids[i]; const dateRange = dateRanges[id]; const startTime = dateRange.startTime; const appendedDateRangeCues = dateRangeCuesAppended[id]; const cues = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.cues) || {}; let durationKnown = (appendedDateRangeCues == null ? void 0 : appendedDateRangeCues.durationKnown) || false; let endTime = MAX_CUE_ENDTIME; const { duration, endDate } = dateRange; if (endDate && duration !== null) { endTime = startTime + duration; durationKnown = true; } else if (dateRange.endOnNext && !durationKnown) { const nextDateRangeWithSameClass = ids.reduce((candidateDateRange, id2) => { if (id2 !== dateRange.id) { const otherDateRange = dateRanges[id2]; if (otherDateRange.class === dateRange.class && otherDateRange.startDate > dateRange.startDate && (!candidateDateRange || dateRange.startDate < candidateDateRange.startDate)) { return otherDateRange; } } return candidateDateRange; }, null); if (nextDateRangeWithSameClass) { endTime = nextDateRangeWithSameClass.startTime; durationKnown = true; } } const attributes = Object.keys(dateRange.attr); for (let j = 0; j < attributes.length; j++) { const key2 = attributes[j]; if (!isDateRangeCueAttribute(key2)) { continue; } const cue = cues[key2]; if (cue) { if (durationKnown && !appendedDateRangeCues.durationKnown) { cue.endTime = endTime; } else if (Math.abs(cue.startTime - startTime) > 0.01) { cue.startTime = startTime; cue.endTime = endTime; } } else if (Cue) { let data = dateRange.attr[key2]; if (isSCTE35Attribute(key2)) { data = hexToArrayBuffer(data); } const payload = { key: key2, data }; const _cue = createCueWithDataFields(Cue, startTime, endTime, payload, MetadataSchema.dateRange); if (_cue) { _cue.id = id; this.id3Track.addCue(_cue); cues[key2] = _cue; if (this.hls.config.interstitialsController) { if (key2 === "X-ASSET-LIST" || key2 === "X-ASSET-URL") { _cue.addEventListener("enter", this.onEventCueEnter); } } } } } dateRangeCuesAppended[id] = { cues, dateRange, durationKnown }; } } } class LatencyController { constructor(hls) { this.hls = void 0; this.config = void 0; this.media = null; this.currentTime = 0; this.stallCount = 0; this._latency = null; this._targetLatencyUpdated = false; this.onTimeupdate = () => { const { media } = this; const levelDetails = this.levelDetails; if (!media || !levelDetails) { return; } this.currentTime = media.currentTime; const latency = this.computeLatency(); if (latency === null) { return; } this._latency = latency; const { lowLatencyMode, maxLiveSyncPlaybackRate } = this.config; if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) { return; } const targetLatency = this.targetLatency; if (targetLatency === null) { return; } const distanceFromTarget = latency - targetLatency; const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration); const inLiveRange = distanceFromTarget < liveMinLatencyDuration; if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) { const max = Math.min(2, Math.max(1, maxLiveSyncPlaybackRate)); const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20; const playbackRate = Math.min(max, Math.max(1, rate)); this.changeMediaPlaybackRate(media, playbackRate); } else if (media.playbackRate !== 1 && media.playbackRate !== 0) { this.changeMediaPlaybackRate(media, 1); } }; this.hls = hls; this.config = hls.config; this.registerListeners(); } get levelDetails() { var _this$hls; return ((_this$hls = this.hls) == null ? void 0 : _this$hls.latestLevelDetails) || null; } get latency() { return this._latency || 0; } get maxLatency() { const { config } = this; if (config.liveMaxLatencyDuration !== void 0) { return config.liveMaxLatencyDuration; } const levelDetails = this.levelDetails; return levelDetails ? config.liveMaxLatencyDurationCount * levelDetails.targetduration : 0; } get targetLatency() { const levelDetails = this.levelDetails; if (levelDetails === null || this.hls === null) { return null; } const { holdBack, partHoldBack, targetduration } = levelDetails; const { liveSyncDuration, liveSyncDurationCount, lowLatencyMode } = this.config; const userConfig = this.hls.userConfig; let targetLatency = lowLatencyMode ? partHoldBack || holdBack : holdBack; if (this._targetLatencyUpdated || userConfig.liveSyncDuration || userConfig.liveSyncDurationCount || targetLatency === 0) { targetLatency = liveSyncDuration !== void 0 ? liveSyncDuration : liveSyncDurationCount * targetduration; } const maxLiveSyncOnStallIncrease = targetduration; return targetLatency + Math.min(this.stallCount * this.config.liveSyncOnStallIncrease, maxLiveSyncOnStallIncrease); } set targetLatency(latency) { this.stallCount = 0; this.config.liveSyncDuration = latency; this._targetLatencyUpdated = true; } get liveSyncPosition() { const liveEdge = this.estimateLiveEdge(); const targetLatency = this.targetLatency; if (liveEdge === null || targetLatency === null) { return null; } const levelDetails = this.levelDetails; if (levelDetails === null) { return null; } const edge = levelDetails.edge; const syncPosition = liveEdge - targetLatency - this.edgeStalled; const min = edge - levelDetails.totalduration; const max = edge - (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration); return Math.min(Math.max(min, syncPosition), max); } get drift() { const levelDetails = this.levelDetails; if (levelDetails === null) { return 1; } return levelDetails.drift; } get edgeStalled() { const levelDetails = this.levelDetails; if (levelDetails === null) { return 0; } const maxLevelUpdateAge = (this.config.lowLatencyMode && levelDetails.partTarget || levelDetails.targetduration) * 3; return Math.max(levelDetails.age - maxLevelUpdateAge, 0); } get forwardBufferLength() { const { media } = this; const levelDetails = this.levelDetails; if (!media || !levelDetails) { return 0; } const bufferedRanges = media.buffered.length; return (bufferedRanges ? media.buffered.end(bufferedRanges - 1) : levelDetails.edge) - this.currentTime; } destroy() { this.unregisterListeners(); this.onMediaDetaching(); this.hls = null; } registerListeners() { const { hls } = this; if (!hls) { return; } 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_UPDATED, this.onLevelUpdated, this); hls.on(Events.ERROR, this.onError, this); } unregisterListeners() { const { hls } = this; if (!hls) { return; } 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_UPDATED, this.onLevelUpdated, this); hls.off(Events.ERROR, this.onError, this); } onMediaAttached(event, data) { this.media = data.media; this.media.addEventListener("timeupdate", this.onTimeupdate); } onMediaDetaching() { if (this.media) { this.media.removeEventListener("timeupdate", this.onTimeupdate); this.media = null; } } onManifestLoading() { this._latency = null; this.stallCount = 0; } onLevelUpdated(event, { details }) { if (details.advanced) { this.onTimeupdate(); } if (!details.live && this.media) { this.media.removeEventListener("timeupdate", this.onTimeupdate); } } onError(event, data) { var _this$levelDetails; if (data.details !== ErrorDetails.BUFFER_STALLED_ERROR) { return; } this.stallCount++; if (this.hls && (_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) { this.hls.logger.warn("[latency-controller]: Stall detected, adjusting target latency"); } } changeMediaPlaybackRate(media, playbackRate) { var _this$hls2, _this$targetLatency; if (media.playbackRate === playbackRate) { return; } (_this$hls2 = this.hls) == null ? void 0 : _this$hls2.logger.debug(`[latency-controller]: latency=${this.latency.toFixed(3)}, targetLatency=${(_this$targetLatency = this.targetLatency) == null ? void 0 : _this$targetLatency.toFixed(3)}, forwardBufferLength=${this.forwardBufferLength.toFixed(3)}: adjusting playback rate from ${media.playbackRate} to ${playbackRate}`); media.playbackRate = playbackRate; } estimateLiveEdge() { const levelDetails = this.levelDetails; if (levelDetails === null) { return null; } return levelDetails.edge + levelDetails.age; } computeLatency() { const liveEdge = this.estimateLiveEdge(); if (liveEdge === null) { return null; } return liveEdge - this.currentTime; } } class LevelController extends BasePlaylistController { constructor(hls, contentSteeringController) { super(hls, "level-controller"); 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(); } _registerListeners() { const { hls } = this; 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); } _unregisterListeners() { const { hls } = this; 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); } destroy() { this._unregisterListeners(); this.steering = null; this.resetLevels(); super.destroy(); } stopLoad() { const levels = this._levels; levels.forEach((level) => { level.loadError = 0; level.fragmentError = 0; }); super.stopLoad(); } resetLevels() { this._startLevel = void 0; this.manualLevelIndex = -1; this.currentLevelIndex = -1; this.currentLevel = null; this._levels = []; this._maxAutoLevel = -1; } onManifestLoading(event, data) { this.resetLevels(); } onManifestLoaded(event, data) { const preferManagedMediaSource = this.hls.config.preferManagedMediaSource; const levels = []; const redundantSet = {}; const generatePathwaySet = {}; let resolutionFound = false; let videoCodecFound = false; let audioCodecFound = false; data.levels.forEach((levelParsed) => { var _videoCodec; const attributes = levelParsed.attrs; let { audioCodec, videoCodec } = levelParsed; if (audioCodec) { levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || void 0; } if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf("avc1")) === 0) { videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec); } const { width, height, unknownCodecs } = levelParsed; let unknownUnsupportedCodecCount = unknownCodecs ? unknownCodecs.length : 0; if (unknownCodecs) { for (let i = unknownUnsupportedCodecCount; i--; ) { const unknownCodec = unknownCodecs[i]; if (this.isAudioSupported(unknownCodec)) { levelParsed.audioCodec = audioCodec = audioCodec ? `${audioCodec},${unknownCodec}` : unknownCodec; unknownUnsupportedCodecCount--; sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2; } else if (this.isVideoSupported(unknownCodec)) { levelParsed.videoCodec = videoCodec = videoCodec ? `${videoCodec},${unknownCodec}` : unknownCodec; unknownUnsupportedCodecCount--; sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2; } } } resolutionFound || (resolutionFound = !!(width && height)); videoCodecFound || (videoCodecFound = !!videoCodec); audioCodecFound || (audioCodecFound = !!audioCodec); if (unknownUnsupportedCodecCount || audioCodec && !this.isAudioSupported(audioCodec) || videoCodec && !this.isVideoSupported(videoCodec)) { this.log(`Some or all CODECS not supported "${attributes.CODECS}"`); return; } const { CODECS, "FRAME-RATE": FRAMERATE, "HDCP-LEVEL": HDCP, "PATHWAY-ID": PATHWAY, RESOLUTION, "VIDEO-RANGE": VIDEO_RANGE } = attributes; const contentSteeringPrefix = `${PATHWAY || "."}-`; const levelKey = `${contentSteeringPrefix}${levelParsed.bitrate}-${RESOLUTION}-${FRAMERATE}-${CODECS}-${VIDEO_RANGE}-${HDCP}`; if (!redundantSet[levelKey]) { const level = this.createLevel(levelParsed); redundantSet[levelKey] = level; generatePathwaySet[levelKey] = 1; levels.push(level); } else if (redundantSet[levelKey].uri !== levelParsed.url && !levelParsed.attrs["PATHWAY-ID"]) { const pathwayCount = generatePathwaySet[levelKey] += 1; levelParsed.attrs["PATHWAY-ID"] = new Array(pathwayCount + 1).join("."); const level = this.createLevel(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); } createLevel(levelParsed) { const level = new Level(levelParsed); const supplemental = levelParsed.supplemental; if (supplemental != null && supplemental.videoCodec && !this.isVideoSupported(supplemental.videoCodec)) { const error = new Error(`SUPPLEMENTAL-CODECS not supported "${supplemental.videoCodec}"`); this.log(error.message); level.supportedResult = getUnsupportedResult(error, []); } return level; } isAudioSupported(codec) { return areCodecsMediaSourceSupported(codec, "audio", this.hls.config.preferManagedMediaSource); } isVideoSupported(codec) { return areCodecsMediaSourceSupported(codec, "video", this.hls.config.preferManagedMediaSource); } filterAndSortMediaOptions(filteredLevels, data, resolutionFound, videoCodecFound, audioCodecFound) { let audioTracks = []; let subtitleTracks = []; let levels = filteredLevels; if ((resolutionFound || videoCodecFound) && audioCodecFound) { levels = levels.filter(({ videoCodec, videoRange, width, height }) => (!!videoCodec || !!(width && height)) && isVideoRange(videoRange)); } if (levels.length === 0) { Promise.resolve().then(() => { if (this.hls) { let message = "no level with compatible codecs found in manifest"; let reason = message; if (data.levels.length) { reason = `one or more CODECS in variant not supported: ${stringify(data.levels.map((level) => level.attrs.CODECS).filter((value, index, array) => array.indexOf(value) === index))}`; this.warn(reason); message += ` (${reason})`; } const error = new Error(message); this.hls.trigger(Events.ERROR, { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR, fatal: true, url: data.url, error, reason }); } }); return; } if (data.audioTracks) { audioTracks = data.audioTracks.filter((track) => !track.audioCodec || this.isAudioSupported(track.audioCodec)); assignTrackIdsByGroup(audioTracks); } if (data.subtitles) { subtitleTracks = data.subtitles; assignTrackIdsByGroup(subtitleTracks); } const unsortedLevels = levels.slice(0); levels.sort((a, b) => { if (a.attrs["HDCP-LEVEL"] !== b.attrs["HDCP-LEVEL"]) { return (a.attrs["HDCP-LEVEL"] || "") > (b.attrs["HDCP-LEVEL"] || "") ? 1 : -1; } 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) { const valueA = videoCodecPreferenceValue(a.videoCodec); const valueB = videoCodecPreferenceValue(b.videoCodec); if (valueA !== valueB) { return valueB - valueA; } } if (a.uri === b.uri && a.codecSet !== b.codecSet) { const valueA = codecsSetSelectionPreferenceValue(a.codecSet); const valueB = codecsSetSelectionPreferenceValue(b.codecSet); if (valueA !== valueB) { return valueB - valueA; } } if (a.averageBitrate !== b.averageBitrate) { return a.averageBitrate - b.averageBitrate; } return 0; }); let firstLevelInPlaylist = unsortedLevels[0]; if (this.steering) { levels = this.steering.filterParsedLevels(levels); if (levels.length !== unsortedLevels.length) { for (let i = 0; i < unsortedLevels.length; i++) { if (unsortedLevels[i].pathwayId === levels[0].pathwayId) { firstLevelInPlaylist = unsortedLevels[i]; break; } } } } this._levels = levels; for (let i = 0; i < levels.length; i++) { if (levels[i] === firstLevelInPlaylist) { var _this$hls$userConfig; this._firstLevel = i; const firstLevelBitrate = firstLevelInPlaylist.bitrate; const bandwidthEstimate = this.hls.bandwidthEstimate; this.log(`manifest loaded, ${levels.length} level(s) found, first bitrate: ${firstLevelBitrate}`); if (((_this$hls$userConfig = this.hls.userConfig) == null ? void 0 : _this$hls$userConfig.abrEwmaDefaultEstimate) === void 0) { const startingBwEstimate = Math.min(firstLevelBitrate, this.hls.config.abrEwmaDefaultEstimateMax); if (startingBwEstimate > bandwidthEstimate && bandwidthEstimate === this.hls.abrEwmaDefaultEstimate) { this.hls.bandwidthEstimate = startingBwEstimate; } } break; } } const audioOnly = audioCodecFound && !videoCodecFound; const config = this.hls.config; const altAudioEnabled = !!(config.audioStreamController && config.audioTrackController); const edata = { levels, audioTracks, subtitleTracks, sessionData: data.sessionData, sessionKeys: data.sessionKeys, firstLevel: this._firstLevel, stats: data.stats, audio: audioCodecFound, video: videoCodecFound, altAudio: altAudioEnabled && !audioOnly && audioTracks.some((t) => !!t.url) }; this.hls.trigger(Events.MANIFEST_PARSED, edata); } get levels() { if (this._levels.length === 0) { return null; } return this._levels; } get loadLevelObj() { return this.currentLevel; } get level() { return this.currentLevelIndex; } set level(newLevel) { const levels = this._levels; if (levels.length === 0) { return; } if (newLevel < 0 || newLevel >= levels.length) { const error = new Error("invalid level idx"); const fatal = newLevel < 0; this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.LEVEL_SWITCH_ERROR, level: newLevel, fatal, error, reason: error.message }); if (fatal) { return; } newLevel = Math.min(newLevel, levels.length - 1); } const lastLevelIndex = this.currentLevelIndex; const lastLevel = this.currentLevel; const lastPathwayId = lastLevel ? lastLevel.attrs["PATHWAY-ID"] : void 0; const level = levels[newLevel]; const pathwayId = level.attrs["PATHWAY-ID"]; this.currentLevelIndex = newLevel; this.currentLevel = level; if (lastLevelIndex === newLevel && 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 : ""}`); const 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); const levelDetails = level.details; if (!levelDetails || levelDetails.live) { const hlsUrlParameters = this.switchParams(level.uri, lastLevel == null ? void 0 : lastLevel.details, levelDetails); this.loadPlaylist(hlsUrlParameters); } } get manualLevel() { return this.manualLevelIndex; } set manualLevel(newLevel) { this.manualLevelIndex = newLevel; if (this._startLevel === void 0) { this._startLevel = newLevel; } if (newLevel !== -1) { this.level = newLevel; } } get firstLevel() { return this._firstLevel; } set firstLevel(newLevel) { this._firstLevel = newLevel; } get startLevel() { if (this._startLevel === void 0) { const configStartLevel = this.hls.config.startLevel; if (configStartLevel !== void 0) { return configStartLevel; } return this.hls.firstAutoLevel; } return this._startLevel; } set startLevel(newLevel) { this._startLevel = newLevel; } get pathways() { if (this.steering) { return this.steering.pathways(); } return []; } get pathwayPriority() { if (this.steering) { return this.steering.pathwayPriority; } return null; } set pathwayPriority(pathwayPriority) { if (this.steering) { const pathwaysList = this.steering.pathways(); const filteredPathwayPriority = pathwayPriority.filter((pathwayId) => { return pathwaysList.indexOf(pathwayId) !== -1; }); if (pathwayPriority.length < 1) { this.warn(`pathwayPriority ${pathwayPriority} should contain at least one pathway from list: ${pathwaysList}`); return; } this.steering.pathwayPriority = filteredPathwayPriority; } } 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 onFragBuffered(event, { frag }) { if (frag !== void 0 && frag.type === PlaylistLevelType.MAIN) { const el = frag.elementaryStreams; if (!Object.keys(el).some((type) => !!el[type])) { return; } const 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; } } } onLevelLoaded(event, data) { var _data$deliveryDirecti2; const { level, details } = data; const curLevel = data.levelInfo; if (!curLevel) { var _data$deliveryDirecti; this.warn(`Invalid level index ${level}`); if ((_data$deliveryDirecti = data.deliveryDirectives) != null && _data$deliveryDirecti.skip) { details.deltaUpdateFailed = true; } return; } if (curLevel === this.currentLevel || data.withoutMultiVariant) { if (curLevel.fragmentError === 0) { curLevel.loadError = 0; } let previousDetails = curLevel.details; if (previousDetails === data.details && previousDetails.advanced) { previousDetails = void 0; } this.playlistLoaded(level, data, previousDetails); } else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) { details.deltaUpdateFailed = true; } } loadPlaylist(hlsUrlParameters) { super.loadPlaylist(); if (this.shouldLoadPlaylist(this.currentLevel)) { this.scheduleLoading(this.currentLevel, hlsUrlParameters); } } loadingPlaylist(currentLevel, hlsUrlParameters) { super.loadingPlaylist(currentLevel, hlsUrlParameters); const url2 = this.getUrlWithDirectives(currentLevel.uri, hlsUrlParameters); const currentLevelIndex = this.currentLevelIndex; const pathwayId = currentLevel.attrs["PATHWAY-ID"]; const details = currentLevel.details; const age = details == null ? void 0 : details.age; this.log(`Loading level index ${currentLevelIndex}${(hlsUrlParameters == null ? void 0 : hlsUrlParameters.msn) !== void 0 ? " at sn " + hlsUrlParameters.msn + " part " + hlsUrlParameters.part : ""}${pathwayId ? " Pathway " + pathwayId : ""}${age && details.live ? " age " + age.toFixed(1) + (details.type ? " " + details.type || "" : "") : ""} ${url2}`); this.hls.trigger(Events.LEVEL_LOADING, { url: url2, level: currentLevelIndex, levelInfo: currentLevel, pathwayId: currentLevel.attrs["PATHWAY-ID"], id: 0, // Deprecated Level urlId deliveryDirectives: hlsUrlParameters || null }); } get nextLoadLevel() { if (this.manualLevelIndex !== -1) { return this.manualLevelIndex; } else { return this.hls.nextAutoLevel; } } set nextLoadLevel(nextLevel) { this.level = nextLevel; if (this.manualLevelIndex === -1) { this.hls.nextAutoLevel = nextLevel; } } removeLevel(levelIndex) { var _this$currentLevel; if (this._levels.length === 1) { return; } const levels = this._levels.filter((level, index) => { if (index !== levelIndex) { return true; } if (this.steering) { this.steering.removeLevel(level); } if (level === this.currentLevel) { this.currentLevel = null; this.currentLevelIndex = -1; if (level.details) { level.details.fragments.forEach((f) => 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; } if (this.manualLevelIndex > -1) { this.manualLevelIndex = this.currentLevelIndex; } const maxLevel = levels.length - 1; this._firstLevel = Math.min(this._firstLevel, maxLevel); if (this._startLevel) { this._startLevel = Math.min(this._startLevel, maxLevel); } this.hls.trigger(Events.LEVELS_UPDATED, { levels }); } onLevelsUpdated(event, { levels }) { this._levels = levels; } checkMaxAutoUpdated() { const { autoLevelCapping, maxAutoLevel, maxHdcpLevel } = this.hls; if (this._maxAutoLevel !== maxAutoLevel) { this._maxAutoLevel = maxAutoLevel; this.hls.trigger(Events.MAX_AUTO_LEVEL_UPDATED, { autoLevelCapping, levels: this.levels, maxAutoLevel, minAutoLevel: this.hls.minAutoLevel, maxHdcpLevel }); } } } function assignTrackIdsByGroup(tracks) { const groups = {}; tracks.forEach((track) => { const groupId = track.groupId || ""; track.id = groups[groupId] = groups[groupId] || 0; groups[groupId]++; }); } function getSourceBuffer() { return self.SourceBuffer || self.WebKitSourceBuffer; } function isMSESupported() { const mediaSource = getMediaSource(); if (!mediaSource) { return false; } const sourceBuffer = getSourceBuffer(); return !sourceBuffer || sourceBuffer.prototype && typeof sourceBuffer.prototype.appendBuffer === "function" && typeof sourceBuffer.prototype.remove === "function"; } function isSupported() { if (!isMSESupported()) { return false; } const 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((codecsForVideoContainer) => mediaSource.isTypeSupported(mimeTypeForCodec(codecsForVideoContainer, "video"))) || ["mp4a.40.2", "fLaC"].some((codecForAudioContainer) => mediaSource.isTypeSupported(mimeTypeForCodec(codecForAudioContainer, "audio")))); } function changeTypeSupported() { var _sourceBuffer$prototy; const sourceBuffer = getSourceBuffer(); return typeof (sourceBuffer == null ? void 0 : (_sourceBuffer$prototy = sourceBuffer.prototype) == null ? void 0 : _sourceBuffer$prototy.changeType) === "function"; } const TICK_INTERVAL = 100; class StreamController extends BaseStreamController { constructor(hls, fragmentTracker, keyLoader) { super(hls, fragmentTracker, keyLoader, "stream-controller", PlaylistLevelType.MAIN); this.audioCodecSwap = false; this.level = -1; this._forceStartLoad = false; this._hasEnoughToStart = false; this.altAudio = 0; this.audioOnly = false; this.fragPlaying = null; this.fragLastKbps = 0; this.couldBacktrack = false; this.backtrackFragment = null; this.audioCodecSwitch = false; this.videoBuffer = null; this.onMediaPlaying = () => { this.tick(); }; this.onMediaSeeked = () => { const media = this.media; const currentTime = media ? media.currentTime : null; if (currentTime === null || !isFiniteNumber(currentTime)) { return; } this.log(`Media seeked to ${currentTime.toFixed(3)}`); if (!this.getBufferedFrag(currentTime)) { return; } const bufferInfo = this.getFwdBufferInfoAtPos(media, currentTime, PlaylistLevelType.MAIN, 0); if (bufferInfo === null || bufferInfo.len === 0) { this.warn(`Main forward buffer length at ${currentTime} on "seeked" event ${bufferInfo ? bufferInfo.len : "empty"})`); return; } this.tick(); }; this.registerListeners(); } registerListeners() { super.registerListeners(); const { hls } = 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.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); } unregisterListeners() { super.unregisterListeners(); const { hls } = 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.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); } onHandlerDestroying() { this.onMediaPlaying = this.onMediaSeeked = null; this.unregisterListeners(); super.onHandlerDestroying(); } startLoad(startPosition, skipSeekToStartPosition) { if (this.levels) { const { lastCurrentTime, hls } = this; this.stopLoad(); this.setInterval(TICK_INTERVAL); this.level = -1; if (!this.startFragRequested) { let startLevel = hls.startLevel; if (startLevel === -1) { if (hls.config.testBandwidth && this.levels.length > 1) { startLevel = 0; this.bitrateTest = true; } else { startLevel = hls.firstAutoLevel; } } hls.nextLoadLevel = startLevel; this.level = hls.loadLevel; this._hasEnoughToStart = !!skipSeekToStartPosition; } if (lastCurrentTime > 0 && startPosition === -1 && !skipSeekToStartPosition) { this.log(`Override startPosition with lastCurrentTime @${lastCurrentTime.toFixed(3)}`); startPosition = lastCurrentTime; } this.state = State.IDLE; this.nextLoadPosition = this.lastCurrentTime = startPosition + this.timelineOffset; this.startPosition = skipSeekToStartPosition ? -1 : startPosition; this.tick(); } else { this._forceStartLoad = true; this.state = State.STOPPED; } } stopLoad() { this._forceStartLoad = false; super.stopLoad(); } doTick() { switch (this.state) { case State.WAITING_LEVEL: { const { levels, level } = this; const currentLevel = levels == null ? void 0 : levels[level]; const details = currentLevel == null ? void 0 : currentLevel.details; if (details && (!details.live || this.levelLastLoaded === currentLevel && !this.waitForLive(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; const now2 = self.performance.now(); const retryDate = this.retryDate; if (!retryDate || now2 >= retryDate || (_this$media = this.media) != null && _this$media.seeking) { const { levels, level } = this; const 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(); } onTickEnd() { var _this$media2; super.onTickEnd(); if ((_this$media2 = this.media) != null && _this$media2.readyState && this.media.seeking === false) { this.lastCurrentTime = this.media.currentTime; } this.checkFragmentChanged(); } doTickIdle() { const { hls, levelLastLoaded, levels, media } = this; if (levelLastLoaded === null || !media && !this.primaryPrefetch && (this.startFragRequested || !hls.config.startFragPrefetch)) { return; } if (this.altAudio && this.audioOnly) { return; } const level = this.buffering ? hls.nextLoadLevel : hls.loadLevel; if (!(levels != null && levels[level])) { return; } const levelInfo = levels[level]; const bufferInfo = this.getMainFwdBufferInfo(); if (bufferInfo === null) { return; } const lastDetails = this.getLevelDetails(); if (lastDetails && this._streamEnded(bufferInfo, lastDetails)) { const data = {}; if (this.altAudio === 2) { data.type = "video"; } this.hls.trigger(Events.BUFFER_EOS, data); this.state = State.ENDED; return; } if (!this.buffering) { return; } if (hls.loadLevel !== level && hls.manualLevel === -1) { this.log(`Adapting to level ${level} from level ${this.level}`); } this.level = hls.nextLoadLevel = level; const levelDetails = levelInfo.details; if (!levelDetails || this.state === State.WAITING_LEVEL || this.waitForLive(levelInfo)) { this.level = level; this.state = State.WAITING_LEVEL; this.startFragRequested = false; return; } const bufferLen = bufferInfo.len; const maxBufLen = this.getMaxBufferLength(levelInfo.maxBitrate); if (bufferLen >= maxBufLen) { return; } if (this.backtrackFragment && this.backtrackFragment.start > bufferInfo.end) { this.backtrackFragment = null; } const targetBufferTime = this.backtrackFragment ? this.backtrackFragment.start : bufferInfo.end; let frag = this.getNextFragment(targetBufferTime, levelDetails); if (this.couldBacktrack && !this.fragPrevious && frag && isMediaFragment(frag) && this.fragmentTracker.getState(frag) !== FragmentState.OK) { var _this$backtrackFragme; const backtrackSn = ((_this$backtrackFragme = this.backtrackFragment) != null ? _this$backtrackFragme : frag).sn; const fragIdx = backtrackSn - levelDetails.startSN; const 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; } if (frag && this.isLoopLoading(frag, targetBufferTime)) { const gapStart = frag.gap; if (!gapStart) { const type = this.audioOnly && !this.altAudio ? ElementaryStreamTypes.AUDIO : ElementaryStreamTypes.VIDEO; const 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); } loadFragment(frag, level, targetBufferTime) { const fragState = this.fragmentTracker.getState(frag); if (fragState === FragmentState.NOT_LOADED || fragState === FragmentState.PARTIAL) { if (!isMediaFragment(frag)) { 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 { super.loadFragment(frag, level, targetBufferTime); } } else { this.clearTrackerIfNeeded(frag); } } getBufferedFrag(position) { return this.fragmentTracker.getBufferedFrag(position, PlaylistLevelType.MAIN); } followingBufferedFrag(frag) { if (frag) { 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 */ 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 */ nextLevelSwitch() { const { levels, media } = this; if (media != null && media.readyState) { let fetchdelay; const fragPlayingCurrent = this.getAppendedFrag(media.currentTime); if (fragPlayingCurrent && fragPlayingCurrent.start > 1) { this.flushMainBuffer(0, fragPlayingCurrent.start - 1); } const levelDetails = this.getLevelDetails(); if (levelDetails != null && levelDetails.live) { const bufferInfo = this.getMainFwdBufferInfo(); if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) { return; } } if (!media.paused && levels) { const nextLevelId = this.hls.nextLoadLevel; const nextLevel = levels[nextLevelId]; const fragLastKbps = this.fragLastKbps; if (fragLastKbps && this.fragCurrent) { fetchdelay = this.fragCurrent.duration * nextLevel.maxBitrate / (1e3 * fragLastKbps) + 1; } else { fetchdelay = 0; } } else { fetchdelay = 0; } const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay); if (bufferedFrag) { const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag); if (nextBufferedFrag) { this.abortCurrentFrag(); const maxStart = nextBufferedFrag.maxStartPTS ? nextBufferedFrag.maxStartPTS : nextBufferedFrag.start; const fragDuration = nextBufferedFrag.duration; const startPts = Math.max(bufferedFrag.end, maxStart + Math.min(Math.max(fragDuration - this.config.maxFragLookUpTolerance, fragDuration * (this.couldBacktrack ? 0.5 : 0.125)), fragDuration * (this.couldBacktrack ? 0.75 : 0.25))); this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY); } } } } abortCurrentFrag() { const 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(); } flushMainBuffer(startOffset, endOffset) { super.flushMainBuffer(startOffset, endOffset, this.altAudio === 2 ? "video" : null); } onMediaAttached(event, data) { super.onMediaAttached(event, data); const media = data.media; addEventListener(media, "playing", this.onMediaPlaying); addEventListener(media, "seeked", this.onMediaSeeked); } onMediaDetaching(event, data) { const { media } = this; if (media) { removeEventListener(media, "playing", this.onMediaPlaying); removeEventListener(media, "seeked", this.onMediaSeeked); } this.videoBuffer = null; this.fragPlaying = null; super.onMediaDetaching(event, data); const transferringMedia = !!data.transferMedia; if (transferringMedia) { return; } this._hasEnoughToStart = false; } onManifestLoading() { super.onManifestLoading(); this.log("Trigger BUFFER_RESET"); this.hls.trigger(Events.BUFFER_RESET, void 0); this.couldBacktrack = false; this.fragLastKbps = 0; this.fragPlaying = this.backtrackFragment = null; this.altAudio = 0; this.audioOnly = false; } onManifestParsed(event, data) { let aac = false; let heaac = false; data.levels.forEach((level) => { const 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; } onLevelLoading(event, data) { const { levels } = this; if (!levels || this.state !== State.IDLE) { return; } const level = data.levelInfo; if (!level.details || level.details.live && (this.levelLastLoaded !== level || level.details.expired) || this.waitForCdnTuneIn(level.details)) { this.state = State.WAITING_LEVEL; } } onLevelLoaded(event, data) { var _curLevel$details; const { levels, startFragRequested } = this; const newLevelId = data.level; const newDetails = data.details; const 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}`); const curLevel = data.levelInfo; const 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(); } } let 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); } curLevel.details = newDetails; this.levelLastLoaded = curLevel; if (!startFragRequested) { this.setStartPosition(newDetails, sliding); } this.hls.trigger(Events.LEVEL_UPDATED, { details: newDetails, level: newLevelId }); if (this.state === State.WAITING_LEVEL) { if (this.waitForCdnTuneIn(newDetails)) { return; } this.state = State.IDLE; } if (startFragRequested && newDetails.live) { this.synchronizeToLiveEdge(newDetails); } this.tick(); } synchronizeToLiveEdge(levelDetails) { const { config, media } = this; if (!media) { return; } const liveSyncPosition = this.hls.liveSyncPosition; const currentTime = this.getLoadPosition(); const start = levelDetails.fragmentStart; const end = levelDetails.edge; const withinSlidingWindow = currentTime >= start - config.maxFragLookUpTolerance && currentTime <= end; if (liveSyncPosition !== null && media.duration > liveSyncPosition && (currentTime < liveSyncPosition || !withinSlidingWindow)) { const maxLatency = config.liveMaxLatencyDuration !== void 0 ? config.liveMaxLatencyDuration : config.liveMaxLatencyDurationCount * levelDetails.targetduration; if (!withinSlidingWindow && media.readyState < 4 || currentTime < end - maxLatency) { if (!this._hasEnoughToStart) { this.nextLoadPosition = liveSyncPosition; } 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; } } } } _handleFragmentLoadProgress(data) { var _frag$initSegment; const frag = data.frag; const { part, payload } = data; const { levels } = this; 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; } const currentLevel = levels[frag.level]; if (!currentLevel) { this.warn(`Level ${frag.level} not found on progress`); return; } const 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; } const videoCodec = currentLevel.videoCodec; const accurateTimeOffset = details.PTSKnown || !details.live; const initSegmentData = (_frag$initSegment = frag.initSegment) == null ? void 0 : _frag$initSegment.data; const audioCodec = this._getAudioCodec(currentLevel); const transmuxer = this.transmuxer = this.transmuxer || new TransmuxerInterface(this.hls, PlaylistLevelType.MAIN, this._handleTransmuxComplete.bind(this), this._handleTransmuxerFlush.bind(this)); const partIndex = part ? part.index : -1; const partial = partIndex !== -1; const chunkMeta = new ChunkMetadata(frag.level, frag.sn, frag.stats.chunkCount, payload.byteLength, partIndex, partial); const initPTS = this.initPTS[frag.cc]; transmuxer.push(payload, initSegmentData, audioCodec, videoCodec, frag, part, details.totalduration, accurateTimeOffset, chunkMeta, initPTS); } onAudioTrackSwitching(event, data) { const hls = this.hls; const fromAltAudio = this.altAudio === 2; const altAudio = useAlternateAudio(data.url, hls); 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; const fragCurrent = this.fragCurrent; if (fragCurrent) { this.log("Switching to main audio track, cancel main fragment load"); fragCurrent.abortRequests(); this.fragmentTracker.removeFragment(fragCurrent); } this.resetTransmuxer(); this.resetLoadingState(); } else if (this.audioOnly) { this.resetTransmuxer(); } if (fromAltAudio) { this.fragmentTracker.removeAllFragments(); hls.once(Events.BUFFER_FLUSHED, () => { var _this$hls; (_this$hls = this.hls) == null ? void 0 : _this$hls.trigger(Events.AUDIO_TRACK_SWITCHED, data); }); hls.trigger(Events.BUFFER_FLUSHING, { startOffset: 0, endOffset: Number.POSITIVE_INFINITY, type: null }); return; } hls.trigger(Events.AUDIO_TRACK_SWITCHED, data); } else { this.altAudio = 1; } } onAudioTrackSwitched(event, data) { const altAudio = useAlternateAudio(data.url, this.hls); if (altAudio) { const videoBuffer = this.videoBuffer; 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 ? 2 : 0; this.tick(); } onBufferCreated(event, data) { const tracks = data.tracks; let mediaTrack; let name; let alternate = false; for (const type in tracks) { const track = tracks[type]; if (track.id === "main") { name = type; mediaTrack = track; if (type === "video") { const 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; } } onFragBuffered(event, data) { const { frag, part } = data; const bufferedMainFragment = frag.type === PlaylistLevelType.MAIN; if (bufferedMainFragment) { if (this.fragContextChanged(frag)) { 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; } const stats = part ? part.stats : frag.stats; this.fragLastKbps = Math.round(8 * stats.total / (stats.buffering.end - stats.loading.first)); if (isMediaFragment(frag)) { this.fragPrevious = frag; } this.fragBufferedComplete(frag, part); } const media = this.media; if (!media) { return; } if (!this._hasEnoughToStart && BufferHelper.getBuffered(media).length) { this._hasEnoughToStart = true; this.seekToStartPos(); } if (bufferedMainFragment) { this.tick(); } } get hasEnoughToStart() { return this._hasEnoughToStart; } 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: 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_ADD_CODEC_ERROR: case ErrorDetails.BUFFER_APPEND_ERROR: if (data.parent !== "main") { return; } this.resetLoadingState(); break; case ErrorDetails.BUFFER_FULL_ERROR: if (data.parent !== "main") { return; } if (this.reduceLengthAndFlushBuffer(data)) { this.flushMainBuffer(0, Number.POSITIVE_INFINITY); } break; case ErrorDetails.INTERNAL_EXCEPTION: this.recoverWorkerError(data); break; } } onFragLoadEmergencyAborted() { this.state = State.IDLE; if (!this._hasEnoughToStart) { this.startFragRequested = false; this.nextLoadPosition = this.lastCurrentTime; } this.tickImmediate(); } onBufferFlushed(event, { type }) { if (type !== ElementaryStreamTypes.AUDIO || !this.altAudio) { const mediaBuffer = (type === ElementaryStreamTypes.VIDEO ? this.videoBuffer : this.mediaBuffer) || this.media; if (mediaBuffer) { this.afterBufferFlushed(mediaBuffer, type, PlaylistLevelType.MAIN); this.tick(); } } } onLevelsUpdated(event, data) { if (this.level > -1 && this.fragCurrent) { this.level = this.fragCurrent.level; if (this.level === -1) { this.resetWhenMissingContext(this.fragCurrent); } } this.levels = data.levels; } swapAudioCodec() { this.audioCodecSwap = !this.audioCodecSwap; } /** * Seeks to the set startPosition if not equal to the mediaElement's current time. */ seekToStartPos() { const { media } = this; if (!media) { return; } const currentTime = media.currentTime; let startPosition = this.startPosition; if (startPosition >= 0 && currentTime < startPosition) { if (media.seeking) { this.log(`could not seek to ${startPosition}, already seeking at ${currentTime}`); return; } const timelineOffset = this.timelineOffset; if (timelineOffset && startPosition) { startPosition += timelineOffset; } const details = this.getLevelDetails(); const buffered = BufferHelper.getBuffered(media); const bufferStart = buffered.length ? buffered.start(0) : 0; const delta = bufferStart - startPosition; const skipTolerance = Math.max(this.config.maxBufferHole, this.config.maxFragLookUpTolerance); if (delta > 0 && (delta < skipTolerance || this.loadingParts && delta < 2 * ((details == null ? void 0 : details.partTarget) || 0))) { this.log(`adjusting start position by ${delta} to match buffer start`); startPosition += delta; this.startPosition = startPosition; } if (currentTime < startPosition) { this.log(`seek to target start position ${startPosition} from current time ${currentTime} buffer start ${bufferStart}`); media.currentTime = startPosition; } } } _getAudioCodec(currentLevel) { let 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; } _loadBitrateTestFrag(fragment, level) { fragment.bitrateTest = true; this._doFragLoad(fragment, level).then((data) => { const { hls } = this; const frag = data == null ? void 0 : data.frag; if (!frag || this.fragContextChanged(frag)) { return; } level.fragmentError = 0; this.state = State.IDLE; this.startFragRequested = false; this.bitrateTest = false; const stats = frag.stats; stats.parsing.start = stats.parsing.end = stats.buffering.start = stats.buffering.end = self.performance.now(); hls.trigger(Events.FRAG_LOADED, data); frag.bitrateTest = false; }); } _handleTransmuxComplete(transmuxResult) { var _id3$samples; const id = this.playlistType; const { hls } = this; const { remuxResult, chunkMeta } = transmuxResult; const context = this.getCurrentContext(chunkMeta); if (!context) { this.resetWhenMissingContext(chunkMeta); return; } const { frag, part, level } = context; const { video, text, id3, initSegment } = remuxResult; const { details } = level; const audio = this.altAudio ? void 0 : remuxResult.audio; if (this.fragContextChanged(frag)) { this.fragmentTracker.removeFragment(frag); return; } this.state = State.PARSING; if (initSegment) { if (initSegment != null && initSegment.tracks) { const mapFragment = frag.initSegment || frag; this._bufferInitSegment(level, initSegment.tracks, mapFragment, chunkMeta); hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { frag: mapFragment, id, tracks: initSegment.tracks }); } const initPTS = initSegment.initPTS; const timescale = initSegment.timescale; if (isFiniteNumber(initPTS)) { this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; hls.trigger(Events.INIT_PTS_FOUND, { frag, id, initPTS, timescale }); } } if (video && details) { if (audio && video.type === "audiovideo") { this.logMuxedErr(frag); } const prevFrag = details.fragments[frag.sn - 1 - details.startSN]; const isFirstFragment = frag.sn === details.startSN; const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc; if (remuxResult.independent !== false) { const { startPTS, endPTS, startDTS, endDTS } = video; if (part) { part.elementaryStreams[video.type] = { startPTS, endPTS, startDTS, endDTS }; } else { if (video.firstKeyFrame && video.independent && chunkMeta.id === 1 && !isFirstInDiscontinuity) { this.couldBacktrack = true; } if (video.dropped && video.independent) { const bufferInfo = this.getMainFwdBufferInfo(); const targetBufferTime = (bufferInfo ? bufferInfo.end : this.getLoadPosition()) + this.config.maxBufferHole; const startTime = video.firstKeyFramePTS ? video.firstKeyFramePTS : startPTS; if (!isFirstFragment && targetBufferTime < startTime - this.config.maxBufferHole && !isFirstInDiscontinuity) { this.backtrack(frag); return; } else if (isFirstInDiscontinuity) { frag.gap = true; } frag.setElementaryStreamInfo(video.type, frag.start, endPTS, frag.start, endDTS, true); } else if (isFirstFragment && startPTS - (details.appliedTimelineOffset || 0) > MAX_START_GAP_JUMP) { 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) { frag.gap = true; } else { this.backtrack(frag); return; } } if (audio) { const { startPTS, endPTS, startDTS, endDTS } = audio; if (part) { part.elementaryStreams[ElementaryStreamTypes.AUDIO] = { startPTS, endPTS, startDTS, 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) { const emittedID3 = { id, frag, details, samples: id3.samples }; hls.trigger(Events.FRAG_PARSING_METADATA, emittedID3); } if (details && text) { const emittedText = { id, frag, details, samples: text.samples }; hls.trigger(Events.FRAG_PARSING_USERDATA, emittedText); } } logMuxedErr(frag) { this.warn(`${isMediaFragment(frag) ? "Media" : "Init"} segment with muxed audiovideo where only video expected: ${frag.url}`); } _bufferInitSegment(currentLevel, tracks, frag, chunkMeta) { if (this.state !== State.PARSING) { return; } this.audioOnly = !!tracks.audio && !tracks.video; if (this.altAudio && !this.audioOnly) { delete tracks.audio; if (tracks.audiovideo) { this.logMuxedErr(frag); } } const { audio, video, audiovideo } = tracks; if (audio) { let audioCodec = pickMostCompleteCodecName(audio.codec, currentLevel.audioCodec); if (audioCodec === "mp4a") { audioCodec = "mp4a.40.5"; } const 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"; } } const audioMetadata = audio.metadata; if (audioMetadata && "channelCount" in audioMetadata && (audioMetadata.channelCount || 1) !== 1 && ua.indexOf("firefox") === -1) { audioCodec = "mp4a.40.5"; } } if (audioCodec && audioCodec.indexOf("mp4a.40.5") !== -1 && ua.indexOf("android") !== -1 && audio.container !== "audio/mpeg") { 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 = PlaylistLevelType.MAIN; this.log(`Init audio buffer, container:${audio.container}, codecs[selected/level/parsed]=[${audioCodec || ""}/${currentLevel.audioCodec || ""}/${audio.codec}]`); delete tracks.audiovideo; } if (video) { video.levelCodec = currentLevel.videoCodec; video.id = PlaylistLevelType.MAIN; const parsedVideoCodec = video.codec; if ((parsedVideoCodec == null ? void 0 : parsedVideoCodec.length) === 4) { switch (parsedVideoCodec) { case "hvc1": case "hev1": video.codec = "hvc1.1.6.L120.90"; break; case "av01": video.codec = "av01.0.04M.08"; break; case "avc1": video.codec = "avc1.42e01e"; break; } } this.log(`Init video buffer, container:${video.container}, codecs[level/parsed]=[${currentLevel.videoCodec || ""}/${parsedVideoCodec}]${video.codec !== parsedVideoCodec ? " parsed-corrected=" + video.codec : ""}${video.supplemental ? " supplemental=" + video.supplemental : ""}`); delete tracks.audiovideo; } if (audiovideo) { this.log(`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`); delete tracks.video; delete tracks.audio; } const trackTypes = Object.keys(tracks); if (trackTypes.length) { this.hls.trigger(Events.BUFFER_CODECS, tracks); if (!this.hls) { return; } trackTypes.forEach((trackName) => { const track = tracks[trackName]; const initSegment = track.initSegment; if (initSegment != null && initSegment.byteLength) { this.hls.trigger(Events.BUFFER_APPENDING, { type: trackName, data: initSegment, frag, part: null, chunkMeta, parent: frag.type }); } }); } this.tickImmediate(); } getMainFwdBufferInfo() { const bufferOutput = this.mediaBuffer && this.altAudio === 2 ? this.mediaBuffer : this.media; return this.getFwdBufferInfo(bufferOutput, PlaylistLevelType.MAIN); } get maxBufferLength() { const { levels, level } = this; const levelInfo = levels == null ? void 0 : levels[level]; if (!levelInfo) { return this.config.maxBufferLength; } return this.getMaxBufferLength(levelInfo.maxBitrate); } backtrack(frag) { this.couldBacktrack = true; this.backtrackFragment = frag; this.resetTransmuxer(); this.flushBufferGap(frag); this.fragmentTracker.removeFragment(frag); this.fragPrevious = null; this.nextLoadPosition = frag.start; this.state = State.IDLE; } checkFragmentChanged() { const video = this.media; let fragPlayingCurrent = null; if (video && video.readyState > 1 && video.seeking === false) { const currentTime = video.currentTime; if (BufferHelper.isBuffered(video, currentTime)) { fragPlayingCurrent = this.getAppendedFrag(currentTime); } else if (BufferHelper.isBuffered(video, currentTime + 0.1)) { fragPlayingCurrent = this.getAppendedFrag(currentTime + 0.1); } if (fragPlayingCurrent) { this.backtrackFragment = null; const fragPlaying = this.fragPlaying; const 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 }); } } } } } get nextLevel() { const frag = this.nextBufferedFrag; if (frag) { return frag.level; } return -1; } get currentFrag() { var _this$media3; if (this.fragPlaying) { return this.fragPlaying; } const currentTime = ((_this$media3 = this.media) == null ? void 0 : _this$media3.currentTime) || this.lastCurrentTime; if (isFiniteNumber(currentTime)) { return this.getAppendedFrag(currentTime); } return null; } get currentProgramDateTime() { var _this$media4; const currentTime = ((_this$media4 = this.media) == null ? void 0 : _this$media4.currentTime) || this.lastCurrentTime; if (isFiniteNumber(currentTime)) { const details = this.getLevelDetails(); const frag = this.currentFrag || (details ? findFragmentByPTS(null, details.fragments, currentTime) : null); if (frag) { const programDateTime = frag.programDateTime; if (programDateTime !== null) { const epocMs = programDateTime + (currentTime - frag.start) * 1e3; return new Date(epocMs); } } } return null; } get currentLevel() { const frag = this.currentFrag; if (frag) { return frag.level; } return -1; } get nextBufferedFrag() { const frag = this.currentFrag; if (frag) { return this.followingBufferedFrag(frag); } return null; } get forceStartLoad() { return this._forceStartLoad; } } class KeyLoader { constructor(config) { this.config = void 0; this.keyUriToKeyInfo = {}; this.emeController = null; this.config = config; } abort(type) { for (const uri in this.keyUriToKeyInfo) { const 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(); } } } detach() { for (const uri in this.keyUriToKeyInfo) { const keyInfo = this.keyUriToKeyInfo[uri]; if (keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption) { delete this.keyUriToKeyInfo[uri]; } } } destroy() { this.detach(); for (const uri in this.keyUriToKeyInfo) { const loader = this.keyUriToKeyInfo[uri].loader; if (loader) { loader.destroy(); } } this.keyUriToKeyInfo = {}; } createKeyLoadError(frag, details = ErrorDetails.KEY_LOAD_ERROR, error, networkDetails, response) { return new LoadError({ type: ErrorTypes.NETWORK_ERROR, details, fatal: false, frag, response, error, networkDetails }); } loadClear(loadingFrag, encryptedFragments) { if (this.emeController && this.config.emeEnabled) { const { sn, cc } = loadingFrag; for (let i = 0; i < encryptedFragments.length; i++) { const frag = encryptedFragments[i]; if (cc <= frag.cc && (sn === "initSegment" || frag.sn === "initSegment" || sn < frag.sn)) { this.emeController.selectKeySystemFormat(frag).then((keySystemFormat) => { frag.setKeyFormat(keySystemFormat); }); break; } } } } load(frag) { if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) { return this.emeController.selectKeySystemFormat(frag).then((keySystemFormat) => { return this.loadInternal(frag, keySystemFormat); }); } return this.loadInternal(frag); } loadInternal(frag, keySystemFormat) { var _keyInfo, _keyInfo2; if (keySystemFormat) { frag.setKeyFormat(keySystemFormat); } const decryptdata = frag.decryptdata; if (!decryptdata) { const 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)); } const uri = decryptdata.uri; if (!uri) { return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Invalid key URI: "${uri}"`))); } let keyInfo = this.keyUriToKeyInfo[uri]; if ((_keyInfo = keyInfo) != null && _keyInfo.decryptdata.key) { decryptdata.key = keyInfo.decryptdata.key; return Promise.resolve({ frag, keyInfo }); } if ((_keyInfo2 = keyInfo) != null && _keyInfo2.keyLoadPromise) { var _keyInfo$mediaKeySess; switch ((_keyInfo$mediaKeySess = keyInfo.mediaKeySessionContext) == null ? void 0 : _keyInfo$mediaKeySess.keyStatus) { case void 0: case "status-pending": case "usable": case "usable-in-future": return keyInfo.keyLoadPromise.then((keyLoadedData) => { decryptdata.key = keyLoadedData.keyInfo.decryptdata.key; return { frag, keyInfo }; }); } } keyInfo = this.keyUriToKeyInfo[uri] = { 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") { return this.loadKeyHTTP(keyInfo, frag); } return this.loadKeyEME(keyInfo, frag); case "AES-128": case "AES-256": case "AES-256-CTR": 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}"`))); } } loadKeyEME(keyInfo, frag) { const keyLoadedData = { frag, keyInfo }; if (this.emeController && this.config.emeEnabled) { const keySessionContextPromise = this.emeController.loadKey(keyLoadedData); if (keySessionContextPromise) { return (keyInfo.keyLoadPromise = keySessionContextPromise.then((keySessionContext) => { keyInfo.mediaKeySessionContext = keySessionContext; return keyLoadedData; })).catch((error) => { keyInfo.keyLoadPromise = null; throw error; }); } } return Promise.resolve(keyLoadedData); } loadKeyHTTP(keyInfo, frag) { const config = this.config; const Loader = config.loader; const keyLoader = new Loader(config); frag.keyLoader = keyInfo.loader = keyLoader; return keyInfo.keyLoadPromise = new Promise((resolve, reject) => { const loaderContext = { keyInfo, frag, responseType: "arraybuffer", url: keyInfo.decryptdata.uri }; const loadPolicy = config.keyLoadPolicy.default; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: 0, retryDelay: 0, maxRetryDelay: 0 }; const loaderCallbacks = { onSuccess: (response, stats, context, networkDetails) => { const { frag: frag2, keyInfo: keyInfo2, url: uri } = context; if (!frag2.decryptdata || keyInfo2 !== this.keyUriToKeyInfo[uri]) { return reject(this.createKeyLoadError(frag2, ErrorDetails.KEY_LOAD_ERROR, new Error("after key load, decryptdata unset or changed"), networkDetails)); } keyInfo2.decryptdata.key = frag2.decryptdata.key = new Uint8Array(response.data); frag2.keyLoader = null; keyInfo2.loader = null; resolve({ frag: frag2, keyInfo: keyInfo2 }); }, onError: (response, context, networkDetails, stats) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`HTTP Error ${response.code} loading key ${response.text}`), networkDetails, _objectSpread2({ url: loaderContext.url, data: void 0 }, response))); }, onTimeout: (stats, context, networkDetails) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_TIMEOUT, new Error("key loading timed out"), networkDetails)); }, onAbort: (stats, context, networkDetails) => { this.resetLoader(context); reject(this.createKeyLoadError(frag, ErrorDetails.INTERNAL_ABORTED, new Error("key loading aborted"), networkDetails)); } }; keyLoader.load(loaderContext, loaderConfig, loaderCallbacks); }); } resetLoader(context) { const { frag, keyInfo, url: uri } = context; const loader = keyInfo.loader; if (frag.keyLoader === loader) { frag.keyLoader = null; keyInfo.loader = null; } delete this.keyUriToKeyInfo[uri]; if (loader) { loader.destroy(); } } } function mapContextToLevelType(context) { const { type } = context; switch (type) { case PlaylistContextType.AUDIO_TRACK: return PlaylistLevelType.AUDIO; case PlaylistContextType.SUBTITLE_TRACK: return PlaylistLevelType.SUBTITLE; default: return PlaylistLevelType.MAIN; } } function getResponseUrl(response, context) { let url2 = response.url; if (url2 === void 0 || url2.indexOf("data:") === 0) { url2 = context.url; } return url2; } class PlaylistLoader { constructor(hls) { this.hls = void 0; this.loaders = /* @__PURE__ */ Object.create(null); this.variableList = null; this.onManifestLoaded = this.checkAutostartLoad; this.hls = hls; this.registerListeners(); } startLoad(startPosition) { } stopLoad() { this.destroyInternalLoaders(); } registerListeners() { const { hls } = this; 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); hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); } unregisterListeners() { const { hls } = this; 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); hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); } /** * Returns defaults or configured loader-type overloads (pLoader and loader config params) */ createInternalLoader(context) { const config = this.hls.config; const PLoader = config.pLoader; const Loader = config.loader; const InternalLoader = PLoader || Loader; const loader = new InternalLoader(config); this.loaders[context.type] = loader; return loader; } getInternalLoader(context) { return this.loaders[context.type]; } resetInternalLoader(contextType) { if (this.loaders[contextType]) { delete this.loaders[contextType]; } } /** * Call `destroy` on all internal loader instances mapped (one per context type) */ destroyInternalLoaders() { for (const contextType in this.loaders) { const loader = this.loaders[contextType]; if (loader) { loader.destroy(); } this.resetInternalLoader(contextType); } } destroy() { this.variableList = null; this.unregisterListeners(); this.destroyInternalLoaders(); } onManifestLoading(event, data) { const { url: url2 } = data; this.variableList = null; this.load({ id: null, level: 0, responseType: "text", type: PlaylistContextType.MANIFEST, url: url2, deliveryDirectives: null, levelOrTrack: null }); } onLevelLoading(event, data) { const { id, level, pathwayId, url: url2, deliveryDirectives, levelInfo } = data; this.load({ id, level, pathwayId, responseType: "text", type: PlaylistContextType.LEVEL, url: url2, deliveryDirectives, levelOrTrack: levelInfo }); } onAudioTrackLoading(event, data) { const { id, groupId, url: url2, deliveryDirectives, track } = data; this.load({ id, groupId, level: null, responseType: "text", type: PlaylistContextType.AUDIO_TRACK, url: url2, deliveryDirectives, levelOrTrack: track }); } onSubtitleTrackLoading(event, data) { const { id, groupId, url: url2, deliveryDirectives, track } = data; this.load({ id, groupId, level: null, responseType: "text", type: PlaylistContextType.SUBTITLE_TRACK, url: url2, deliveryDirectives, levelOrTrack: track }); } onLevelsUpdated(event, data) { const loader = this.loaders[PlaylistContextType.LEVEL]; if (loader) { const context = loader.context; if (context && !data.levels.some((lvl) => lvl === context.levelOrTrack)) { loader.abort(); delete this.loaders[PlaylistContextType.LEVEL]; } } } load(context) { var _context$deliveryDire; const config = this.hls.config; let loader = this.getInternalLoader(context); if (loader) { const logger2 = this.hls.logger; const loaderContext = loader.context; if (loaderContext && loaderContext.levelOrTrack === context.levelOrTrack && (loaderContext.url === context.url || loaderContext.deliveryDirectives && !context.deliveryDirectives)) { if (loaderContext.url === context.url) { logger2.log(`[playlist-loader]: ignore ${context.url} ongoing request`); } else { logger2.log(`[playlist-loader]: ignore ${context.url} in favor of ${loaderContext.url}`); } return; } logger2.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`); loader.abort(); } let loadPolicy; if (context.type === PlaylistContextType.MANIFEST) { loadPolicy = config.manifestLoadPolicy.default; } else { loadPolicy = _extends({}, config.playlistLoadPolicy.default, { timeoutRetry: null, errorRetry: null }); } loader = this.createInternalLoader(context); if (isFiniteNumber((_context$deliveryDire = context.deliveryDirectives) == null ? void 0 : _context$deliveryDire.part)) { let 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) { const partTarget = levelDetails.partTarget; const targetDuration = levelDetails.targetduration; if (partTarget && targetDuration) { const maxLowLatencyPlaylistRefresh = Math.max(partTarget * 3, targetDuration * 0.8) * 1e3; loadPolicy = _extends({}, loadPolicy, { maxTimeToFirstByteMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs), maxLoadTimeMs: Math.min(maxLowLatencyPlaylistRefresh, loadPolicy.maxTimeToFirstByteMs) }); } } } const legacyRetryCompatibility = loadPolicy.errorRetry || loadPolicy.timeoutRetry || {}; const loaderConfig = { loadPolicy, timeout: loadPolicy.maxLoadTimeMs, maxRetry: legacyRetryCompatibility.maxNumRetry || 0, retryDelay: legacyRetryCompatibility.retryDelayMs || 0, maxRetryDelay: legacyRetryCompatibility.maxRetryDelayMs || 0 }; const loaderCallbacks = { onSuccess: (response, stats, context2, networkDetails) => { const loader2 = this.getInternalLoader(context2); this.resetInternalLoader(context2.type); const string = response.data; if (string.indexOf("#EXTM3U") !== 0) { this.handleManifestParsingError(response, context2, new Error("no EXTM3U delimiter"), networkDetails || null, stats); return; } stats.parsing.start = performance.now(); if (M3U8Parser.isMediaPlaylist(string) || context2.type !== PlaylistContextType.MANIFEST) { this.handleTrackOrLevelPlaylist(response, stats, context2, networkDetails || null, loader2); } else { this.handleMasterPlaylist(response, stats, context2, networkDetails); } }, onError: (response, context2, networkDetails, stats) => { this.handleNetworkError(context2, networkDetails, false, response, stats); }, onTimeout: (stats, context2, networkDetails) => { this.handleNetworkError(context2, networkDetails, true, void 0, stats); } }; loader.load(context, loaderConfig, loaderCallbacks); } checkAutostartLoad() { if (!this.hls) { return; } const { config: { autoStartLoad, startPosition }, forceStartLoad } = this.hls; if (autoStartLoad || forceStartLoad) { this.hls.logger.log(`${autoStartLoad ? "auto" : "force"} startLoad with configured startPosition ${startPosition}`); this.hls.startLoad(startPosition); } } handleMasterPlaylist(response, stats, context, networkDetails) { const hls = this.hls; const string = response.data; const url2 = getResponseUrl(response, context); const parsedResult = M3U8Parser.parseMasterPlaylist(string, url2); if (parsedResult.playlistParsingError) { this.handleManifestParsingError(response, context, parsedResult.playlistParsingError, networkDetails, stats); return; } const { contentSteering, levels, sessionData, sessionKeys, startTimeOffset, variableList } = parsedResult; this.variableList = variableList; const { AUDIO: audioTracks = [], SUBTITLES: subtitles, "CLOSED-CAPTIONS": captions } = M3U8Parser.parseMasterPlaylistMedia(string, url2, parsedResult); if (audioTracks.length) { const embeddedAudioFound = audioTracks.some((audioTrack) => !audioTrack.url); if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) { this.hls.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, audioTracks, subtitles, captions, contentSteering, url: url2, stats, networkDetails, sessionData, sessionKeys, startTimeOffset, variableList }); } handleTrackOrLevelPlaylist(response, stats, context, networkDetails, loader) { const hls = this.hls; const { id, level, type } = context; const url2 = getResponseUrl(response, context); const levelId = isFiniteNumber(level) ? level : isFiniteNumber(id) ? id : 0; const levelType = mapContextToLevelType(context); const levelDetails = M3U8Parser.parseLevelPlaylist(response.data, url2, levelId, levelType, 0, this.variableList); if (type === PlaylistContextType.MANIFEST) { const singleLevel = { attrs: new AttrList({}), bitrate: 0, details: levelDetails, name: "", url: url2 }; levelDetails.requestScheduled = stats.loading.start + computeReloadInterval(levelDetails, 0); hls.trigger(Events.MANIFEST_LOADED, { levels: [singleLevel], audioTracks: [], url: url2, stats, networkDetails, sessionData: null, sessionKeys: null, contentSteering: null, startTimeOffset: null, variableList: null }); } stats.parsing.end = performance.now(); context.levelDetails = levelDetails; this.handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader); } 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, reason: error.message, response, context, networkDetails, stats }); } handleNetworkError(context, networkDetails, timeout = false, response, stats) { let 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}"`; } const error = new Error(message); this.hls.logger.warn(`[playlist-loader]: ${message}`); let details = ErrorDetails.UNKNOWN; let fatal = false; const 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); } const errorData = { type: ErrorTypes.NETWORK_ERROR, details, fatal, url: context.url, loader, context, error, networkDetails, stats }; if (response) { const url2 = (networkDetails == null ? void 0 : networkDetails.url) || context.url; errorData.response = _objectSpread2({ url: url2, data: void 0 }, response); } this.hls.trigger(Events.ERROR, errorData); } handlePlaylistLoaded(levelDetails, response, stats, context, networkDetails, loader) { const hls = this.hls; const { type, level, id, groupId, deliveryDirectives } = context; const url2 = getResponseUrl(response, context); const parent = mapContextToLevelType(context); const levelIndex = typeof context.level === "number" && parent === PlaylistLevelType.MAIN ? level : void 0; if (!levelDetails.fragments.length) { const _error = levelDetails.playlistParsingError = new Error("No Segments found in Playlist"); hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.LEVEL_EMPTY_ERROR, fatal: false, url: url2, error: _error, reason: _error.message, response, context, level: levelIndex, parent, networkDetails, stats }); return; } if (!levelDetails.targetduration) { levelDetails.playlistParsingError = new Error("Missing Target Duration"); } const error = levelDetails.playlistParsingError; if (error) { this.hls.logger.warn(error); if (!hls.config.ignorePlaylistParsingErrors) { hls.trigger(Events.ERROR, { type: ErrorTypes.NETWORK_ERROR, details: ErrorDetails.LEVEL_PARSING_ERROR, fatal: false, url: url2, error, reason: error.message, response, context, level: levelIndex, parent, networkDetails, stats }); return; } levelDetails.playlistParsingError = null; } 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, levelInfo: context.levelOrTrack || hls.levels[0], level: levelIndex || 0, id: id || 0, stats, networkDetails, deliveryDirectives, withoutMultiVariant: type === PlaylistContextType.MANIFEST }); break; case PlaylistContextType.AUDIO_TRACK: hls.trigger(Events.AUDIO_TRACK_LOADED, { details: levelDetails, track: context.levelOrTrack, id: id || 0, groupId: groupId || "", stats, networkDetails, deliveryDirectives }); break; case PlaylistContextType.SUBTITLE_TRACK: hls.trigger(Events.SUBTITLE_TRACK_LOADED, { details: levelDetails, track: context.levelOrTrack, id: id || 0, groupId: groupId || "", stats, networkDetails, deliveryDirectives }); break; } } } class Hls { /** * Get the video-dev/hls.js package version. */ static get version() { return version; } /** * Check if the required MediaSource Extensions are available. */ static isMSESupported() { return isMSESupported(); } /** * Check if MediaSource Extensions are available and isTypeSupported checks pass for any baseline codecs. */ static isSupported() { return isSupported(); } /** * Get the MediaSource global used for MSE playback (ManagedMediaSource, MediaSource, or WebKitMediaSource). */ static getMediaSource() { return getMediaSource(); } static get Events() { return Events; } static get MetadataSchema() { return MetadataSchema; } static get ErrorTypes() { return ErrorTypes; } static get ErrorDetails() { return ErrorDetails; } /** * Get the default configuration applied to new instances. */ static get DefaultConfig() { if (!Hls.defaultConfig) { return hlsDefaultConfig; } return Hls.defaultConfig; } /** * Replace the default configuration applied to new instances. */ static set DefaultConfig(defaultConfig) { Hls.defaultConfig = defaultConfig; } /** * Creates an instance of an HLS client that can attach to exactly one `HTMLMediaElement`. * @param userConfig - Configuration options applied over `Hls.DefaultConfig` */ constructor(userConfig = {}) { this.config = void 0; this.userConfig = void 0; this.logger = void 0; this.coreComponents = void 0; this.networkControllers = void 0; 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.audioStreamController = void 0; this.subtititleStreamController = void 0; this.audioTrackController = void 0; this.subtitleTrackController = void 0; this.interstitialsController = void 0; this.gapController = void 0; this.emeController = void 0; this.cmcdController = void 0; this._media = null; this._url = null; this._sessionId = void 0; this.triggeringException = void 0; this.started = false; const logger2 = this.logger = enableLogs(userConfig.debug || false, "Hls instance", userConfig.assetPlayerId); const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger2); this.userConfig = userConfig; if (config.progressive) { enableStreamingMode(config, logger2); } const { abrController: _AbrController, bufferController: _BufferController, capLevelController: _CapLevelController, errorController: _ErrorController, fpsController: _FpsController } = config; const errorController = new _ErrorController(this); const abrController = this.abrController = new _AbrController(this); const fragmentTracker = new FragmentTracker(this); const _InterstitialsController = config.interstitialsController; const interstitialsController = _InterstitialsController ? this.interstitialsController = new _InterstitialsController(this, Hls) : null; const bufferController = this.bufferController = new _BufferController(this, fragmentTracker); const capLevelController = this.capLevelController = new _CapLevelController(this); const fpsController = new _FpsController(this); const playListLoader = new PlaylistLoader(this); const _ContentSteeringController = config.contentSteeringController; const contentSteering = _ContentSteeringController ? new _ContentSteeringController(this) : null; const levelController = this.levelController = new LevelController(this, contentSteering); const id3TrackController = new ID3TrackController(this); const keyLoader = new KeyLoader(this.config); const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader); const gapController = this.gapController = new GapController(this, fragmentTracker); capLevelController.setStreamController(streamController); fpsController.setStreamController(streamController); const networkControllers = [playListLoader, levelController, streamController]; if (interstitialsController) { networkControllers.splice(1, 0, interstitialsController); } if (contentSteering) { networkControllers.splice(1, 0, contentSteering); } this.networkControllers = networkControllers; const coreComponents = [abrController, bufferController, gapController, capLevelController, fpsController, id3TrackController, fragmentTracker]; this.audioTrackController = this.createController(config.audioTrackController, networkControllers); const AudioStreamControllerClass = config.audioStreamController; if (AudioStreamControllerClass) { networkControllers.push(this.audioStreamController = new AudioStreamControllerClass(this, fragmentTracker, keyLoader)); } this.subtitleTrackController = this.createController(config.subtitleTrackController, networkControllers); const SubtitleStreamControllerClass = config.subtitleStreamController; if (SubtitleStreamControllerClass) { networkControllers.push(this.subtititleStreamController = 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; networkControllers.push(errorController); const onErrorOut = errorController.onErrorOut; if (typeof onErrorOut === "function") { this.on(Events.ERROR, onErrorOut, errorController); } this.on(Events.MANIFEST_LOADED, playListLoader.onManifestLoaded, playListLoader); } createController(ControllerClass, components) { if (ControllerClass) { const controllerInstance = new ControllerClass(this); if (components) { components.push(controllerInstance); } return controllerInstance; } return null; } // Delegate the EventEmitter through the public API of Hls.js on(event, listener, context = this) { this._emitter.on(event, listener, context); } once(event, listener, context = this) { this._emitter.once(event, listener, context); } removeAllListeners(event) { this._emitter.removeAllListeners(event); } off(event, listener, context = this, once) { this._emitter.off(event, listener, context, once); } listeners(event) { return this._emitter.listeners(event); } emit(event, name, eventObject) { return this._emitter.emit(event, name, eventObject); } trigger(event, eventObject) { if (this.config.debug) { return this.emit(event, event, eventObject); } else { try { return this.emit(event, event, eventObject); } catch (error) { this.logger.error("An internal error happened while handling event " + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error); if (!this.triggeringException) { this.triggeringException = true; const fatal = event === Events.ERROR; this.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal, event, error }); this.triggeringException = false; } } } return false; } listenerCount(event) { return this._emitter.listenerCount(event); } /** * Dispose of the instance */ destroy() { this.logger.log("destroy"); this.trigger(Events.DESTROYING, void 0); this.detachMedia(); this.removeAllListeners(); this._autoLevelCapping = -1; this._url = null; this.networkControllers.forEach((component) => component.destroy()); this.networkControllers.length = 0; this.coreComponents.forEach((component) => component.destroy()); this.coreComponents.length = 0; const config = this.config; config.xhrSetup = config.fetchSetup = void 0; this.userConfig = null; } /** * Attaches Hls.js to a media element */ attachMedia(data) { if (!data || "media" in data && !data.media) { const error = new Error(`attachMedia failed: invalid argument (${data})`); this.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.ATTACH_MEDIA_ERROR, fatal: true, error }); return; } this.logger.log(`attachMedia`); if (this._media) { this.logger.warn(`media must be detached before attaching`); this.detachMedia(); } const attachMediaSource = "media" in data; const media = attachMediaSource ? data.media : data; const attachingData = attachMediaSource ? data : { media }; this._media = media; this.trigger(Events.MEDIA_ATTACHING, attachingData); } /** * Detach Hls.js from the media */ detachMedia() { this.logger.log("detachMedia"); this.trigger(Events.MEDIA_DETACHING, {}); this._media = null; } /** * Detach HTMLMediaElement, MediaSource, and SourceBuffers without reset, for attaching to another instance */ transferMedia() { this._media = null; const transferMedia = this.bufferController.transferMedia(); this.trigger(Events.MEDIA_DETACHING, { transferMedia }); return transferMedia; } /** * Set the source URL. Can be relative or absolute. */ loadSource(url2) { this.stopLoad(); const media = this.media; const loadedSource = this._url; const loadingSource = this._url = urlToolkitExports.buildAbsoluteURL(self.location.href, url2, { alwaysNormalize: true }); this._autoLevelCapping = -1; this._maxHdcpLevel = null; this.logger.log(`loadSource:${loadingSource}`); if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) { this.detachMedia(); this.attachMedia(media); } this.trigger(Events.MANIFEST_LOADING, { url: url2 }); } /** * Gets the currently loaded URL */ get url() { return this._url; } /** * Whether or not enough has been buffered to seek to start position or use `media.currentTime` to determine next load position */ get hasEnoughToStart() { return this.streamController.hasEnoughToStart; } /** * Get the startPosition set on startLoad(position) or on autostart with config.startPosition */ get startPosition() { return this.streamController.startPositionValue; } /** * 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) */ startLoad(startPosition = -1, skipSeekToStartPosition) { this.logger.log(`startLoad(${startPosition + (skipSeekToStartPosition ? "," : "")})`); this.started = true; this.resumeBuffering(); for (let i = 0; i < this.networkControllers.length; i++) { this.networkControllers[i].startLoad(startPosition, skipSeekToStartPosition); if (!this.started || !this.networkControllers) { break; } } } /** * Stop loading of any stream data. */ stopLoad() { this.logger.log("stopLoad"); this.started = false; for (let i = 0; i < this.networkControllers.length; i++) { this.networkControllers[i].stopLoad(); if (this.started || !this.networkControllers) { break; } } } /** * Returns whether loading, toggled with `startLoad()` and `stopLoad()`, is active or not`. */ get loadingEnabled() { return this.started; } /** * Returns state of fragment loading toggled by calling `pauseBuffering()` and `resumeBuffering()`. */ get bufferingEnabled() { return this.streamController.bufferingEnabled; } /** * Resumes stream controller segment loading after `pauseBuffering` has been called. */ resumeBuffering() { if (!this.bufferingEnabled) { this.logger.log(`resume buffering`); this.networkControllers.forEach((controller) => { if (controller.resumeBuffering) { controller.resumeBuffering(); } }); } } /** * Prevents stream controller from loading new segments until `resumeBuffering` is called. * This allows for media buffering to be paused without interupting playlist loading. */ pauseBuffering() { if (this.bufferingEnabled) { this.logger.log(`pause buffering`); this.networkControllers.forEach((controller) => { if (controller.pauseBuffering) { controller.pauseBuffering(); } }); } } get inFlightFragments() { const inFlightData = { [PlaylistLevelType.MAIN]: this.streamController.inFlightFrag }; if (this.audioStreamController) { inFlightData[PlaylistLevelType.AUDIO] = this.audioStreamController.inFlightFrag; } if (this.subtititleStreamController) { inFlightData[PlaylistLevelType.SUBTITLE] = this.subtititleStreamController.inFlightFrag; } return inFlightData; } /** * Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1) */ swapAudioCodec() { this.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. */ recoverMediaError() { this.logger.log("recoverMediaError"); const media = this._media; const time = media == null ? void 0 : media.currentTime; this.detachMedia(); if (media) { this.attachMedia(media); if (time) { this.startLoad(time); } } } removeLevel(levelIndex) { this.levelController.removeLevel(levelIndex); } /** * @returns a UUID for this player instance */ get sessionId() { let _sessionId = this._sessionId; if (!_sessionId) { _sessionId = this._sessionId = uuid(); } return _sessionId; } /** * @returns an array of levels (variants) sorted by HDCP-LEVEL, RESOLUTION (height), FRAME-RATE, CODECS, VIDEO-RANGE, and BANDWIDTH */ get levels() { const levels = this.levelController.levels; return levels ? levels : []; } /** * @returns LevelDetails of last loaded level (variant) or `null` prior to loading a media playlist. */ get latestLevelDetails() { return this.streamController.getLevelDetails() || null; } /** * @returns Level object of selected level (variant) or `null` prior to selecting a level or once the level is removed. */ get loadLevelObj() { return this.levelController.loadLevelObj; } /** * Index of quality level (variant) currently played */ get currentLevel() { 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 currentLevel(newLevel) { this.logger.log(`set currentLevel:${newLevel}`); this.levelController.manualLevel = newLevel; this.streamController.immediateLevelSwitch(); } /** * Index of next quality level loaded as scheduled by stream controller. */ get nextLevel() { 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 nextLevel(newLevel) { this.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 */ get loadLevel() { 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 loadLevel(newLevel) { this.logger.log(`set loadLevel:${newLevel}`); this.levelController.manualLevel = newLevel; } /** * get next quality level loaded */ get nextLoadLevel() { 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 nextLoadLevel(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 */ get firstLevel() { return Math.max(this.levelController.firstLevel, this.minAutoLevel); } /** * Sets "first-level", see getter. */ set firstLevel(newLevel) { this.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. */ get startLevel() { const 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 startLevel(newLevel) { this.logger.log(`set startLevel:${newLevel}`); 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`. */ get capLevelToPlayerSize() { return this.config.capLevelToPlayerSize; } /** * Enables or disables level capping. If disabled after previously enabled, `nextLevelSwitch` will be immediately called. */ set capLevelToPlayerSize(shouldStartCapping) { const newCapLevelToPlayerSize = !!shouldStartCapping; if (newCapLevelToPlayerSize !== this.config.capLevelToPlayerSize) { if (newCapLevelToPlayerSize) { this.capLevelController.startCapping(); } else { this.capLevelController.stopCapping(); this.autoLevelCapping = -1; this.streamController.nextLevelSwitch(); } this.config.capLevelToPlayerSize = newCapLevelToPlayerSize; } } /** * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) */ get autoLevelCapping() { return this._autoLevelCapping; } /** * Returns the current bandwidth estimate in bits per second, when available. Otherwise, `NaN` is returned. */ get bandwidthEstimate() { const { bwEstimator } = this.abrController; if (!bwEstimator) { return NaN; } return bwEstimator.getEstimate(); } set bandwidthEstimate(abrEwmaDefaultEstimate) { this.abrController.resetEstimator(abrEwmaDefaultEstimate); } get abrEwmaDefaultEstimate() { const { bwEstimator } = this.abrController; if (!bwEstimator) { return NaN; } return bwEstimator.defaultEstimate; } /** * get time to first byte estimate * @type {number} */ get ttfbEstimate() { const { bwEstimator } = this.abrController; if (!bwEstimator) { return NaN; } return bwEstimator.getEstimateTTFB(); } /** * Capping/max level value that should be used by automatic level selection algorithm (`ABRController`) */ set autoLevelCapping(newLevel) { if (this._autoLevelCapping !== newLevel) { this.logger.log(`set autoLevelCapping:${newLevel}`); this._autoLevelCapping = newLevel; this.levelController.checkMaxAutoUpdated(); } } get maxHdcpLevel() { return this._maxHdcpLevel; } set maxHdcpLevel(value) { if (isHdcpLevel(value) && this._maxHdcpLevel !== value) { this._maxHdcpLevel = value; this.levelController.checkMaxAutoUpdated(); } } /** * True when automatic level selection enabled */ get autoLevelEnabled() { return this.levelController.manualLevel === -1; } /** * Level set manually (if any) */ get manualLevel() { return this.levelController.manualLevel; } /** * min level selectable in auto mode according to config.minAutoBitrate */ get minAutoLevel() { const { levels, config: { minAutoBitrate } } = this; if (!levels) return 0; const len = levels.length; for (let i = 0; i < len; i++) { if (levels[i].maxBitrate >= minAutoBitrate) { return i; } } return 0; } /** * max level selectable in auto mode according to autoLevelCapping */ get maxAutoLevel() { const { levels, autoLevelCapping, maxHdcpLevel } = this; let maxAutoLevel; if (autoLevelCapping === -1 && levels != null && levels.length) { maxAutoLevel = levels.length - 1; } else { maxAutoLevel = autoLevelCapping; } if (maxHdcpLevel) { for (let i = maxAutoLevel; i--; ) { const hdcpLevel = levels[i].attrs["HDCP-LEVEL"]; if (hdcpLevel && hdcpLevel <= maxHdcpLevel) { return i; } } } return maxAutoLevel; } get firstAutoLevel() { return this.abrController.firstAutoLevel; } /** * next automatically selected quality level */ get nextAutoLevel() { 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 nextAutoLevel(nextLevel) { this.abrController.nextAutoLevel = nextLevel; } /** * get the datetime value relative to media.currentTime for the active level Program Date Time if present */ get playingDate() { return this.streamController.currentProgramDateTime; } get mainForwardBufferInfo() { return this.streamController.getMainFwdBufferInfo(); } get maxBufferLength() { return this.streamController.maxBufferLength; } /** * 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. */ setAudioOption(audioOption) { var _this$audioTrackContr; return ((_this$audioTrackContr = this.audioTrackController) == null ? void 0 : _this$audioTrackContr.setAudioOption(audioOption)) || null; } /** * 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. */ setSubtitleOption(subtitleOption) { var _this$subtitleTrackCo; return ((_this$subtitleTrackCo = this.subtitleTrackController) == null ? void 0 : _this$subtitleTrackCo.setSubtitleOption(subtitleOption)) || null; } /** * Get the complete list of audio tracks across all media groups */ get allAudioTracks() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.allAudioTracks : []; } /** * Get the list of selectable audio tracks */ get audioTracks() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.audioTracks : []; } /** * index of the selected audio track (index in audio track lists) */ get audioTrack() { const audioTrackController = this.audioTrackController; return audioTrackController ? audioTrackController.audioTrack : -1; } /** * selects an audio track, based on its index in audio track lists */ set audioTrack(audioTrackId) { const audioTrackController = this.audioTrackController; if (audioTrackController) { audioTrackController.audioTrack = audioTrackId; } } /** * get the complete list of subtitle tracks across all media groups */ get allSubtitleTracks() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.allSubtitleTracks : []; } /** * get alternate subtitle tracks list from playlist */ get subtitleTracks() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleTracks : []; } /** * index of the selected subtitle track (index in subtitle track lists) */ get subtitleTrack() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleTrack : -1; } get media() { return this._media; } /** * select an subtitle track, based on its index in subtitle track lists */ set subtitleTrack(subtitleTrackId) { const subtitleTrackController = this.subtitleTrackController; if (subtitleTrackController) { subtitleTrackController.subtitleTrack = subtitleTrackId; } } /** * Whether subtitle display is enabled or not */ get subtitleDisplay() { const subtitleTrackController = this.subtitleTrackController; return subtitleTrackController ? subtitleTrackController.subtitleDisplay : false; } /** * Enable/disable subtitle display rendering */ set subtitleDisplay(value) { const subtitleTrackController = this.subtitleTrackController; if (subtitleTrackController) { subtitleTrackController.subtitleDisplay = value; } } /** * get mode for Low-Latency HLS loading */ get lowLatencyMode() { 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 lowLatencyMode(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 */ get liveSyncPosition() { 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 */ get latency() { 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 */ get maxLatency() { return this.latencyController.maxLatency; } /** * target distance from the edge as calculated by the latency controller */ get targetLatency() { return this.latencyController.targetLatency; } set targetLatency(latency) { this.latencyController.targetLatency = latency; } /** * the rate at which the edge of the current live playlist is advancing or 1 if there is none */ get drift() { return this.latencyController.drift; } /** * set to true when startLoad is called before MANIFEST_PARSED event */ get forceStartLoad() { return this.streamController.forceStartLoad; } /** * ContentSteering pathways getter */ get pathways() { return this.levelController.pathways; } /** * ContentSteering pathwayPriority getter/setter */ get pathwayPriority() { return this.levelController.pathwayPriority; } set pathwayPriority(pathwayPriority) { this.levelController.pathwayPriority = pathwayPriority; } /** * returns true when all SourceBuffers are buffered to the end */ get bufferedToEnd() { var _this$bufferControlle; return !!((_this$bufferControlle = this.bufferController) != null && _this$bufferControlle.bufferedToEnd); } /** * returns Interstitials Program Manager */ get interstitialsManager() { var _this$interstitialsCo; return ((_this$interstitialsCo = this.interstitialsController) == null ? void 0 : _this$interstitialsCo.interstitialsManager) || null; } /** * returns mediaCapabilities.decodingInfo for a variant/rendition */ getMediaDecodingInfo(level, audioTracks = this.allAudioTracks) { const audioTracksByGroup = getAudioTracksByGroup(audioTracks); return getMediaDecodingInfoPromise(level, audioTracksByGroup, navigator.mediaCapabilities); } } Hls.defaultConfig = void 0; function playM3u8(video, url2, art2) { if (Hls.isSupported()) { if (art2.hls) art2.hls.destroy(); const hls = new Hls(); hls.loadSource(url2); hls.attachMedia(video); art2.hls = hls; art2.on("destroy", () => hls.destroy()); } else if (video.canPlayType("application/vnd.apple.mpegurl")) { video.src = url2; } else { art2.notice.show = "Unsupported playback format: m3u8"; } } function init_player(url2, container, poster, data) { let opt = { container, url: url2, poster, // autoplay: true, // muted: true, autoSize: true, fullscreen: true, fullscreenWeb: true, autoOrientation: true, flip: true, playbackRate: true, aspectRatio: true, setting: true, controls: [ { position: "right", html: "上传", click: () => upload_danmu(art2) }, { position: "right", html: "下载", click: () => down_danmu(art2) } ], contextmenu: [ { name: "搜索", html: html_danmu } ] }; if (opt.url.endsWith(".m3u8")) { Object.assign(opt, { type: "m3u8", customType: { m3u8: playM3u8 } }); } let art2 = new Artplayer(opt); return art2; } re_render("artplayer-app"); let web_video_info = get_anime_info(); let { anime_id, episode, title, url } = web_video_info; await( set_db_url_info(web_video_info)); let art = init_player(web_video_info.src_url, ".artplayer-app", ""); init_danmu_player(art); let info = { anime_id, title, src_url: web_video_info.src_url, url, episode }; art.storage.set("info", info); console.log("info: ", info); let db_anime_info = await( db_info.get(anime_id)); if (db_anime_info) ; else { db_anime_info = { animes: [{ animeTitle: title }], anime_idx: 0, episode_dif: 0 }; await( db_info.put(anime_id, db_anime_info)); } console.log("db_anime_info: ", db_anime_info); await( set_anime_name(art)); await( get_anime_list(art)); })(Dexie, CryptoJS, axios, artplayerPluginDanmuku, Artplayer);