jessibuca
Version:
a h5 live stream player
1,256 lines • 92.8 kB
JavaScript
var K = Object.defineProperty;
var Z = (r, e, t) => e in r ? K(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
var w = (r, e, t) => Z(r, typeof e != "symbol" ? e + "" : e, t);
import { defineComponent as N, ref as L, watch as z, onMounted as W, onBeforeUnmount as V, h as S, render as ee, computed as _ } from "vue";
function te(r) {
return r && r.__esModule && Object.prototype.hasOwnProperty.call(r, "default") ? r.default : r;
}
var Q = { exports: {} };
(function(r) {
var e = Object.prototype.hasOwnProperty, t = "~";
function i() {
}
Object.create && (i.prototype = /* @__PURE__ */ Object.create(null), new i().__proto__ || (t = !1));
function n(d, l, g) {
this.fn = d, this.context = l, this.once = g || !1;
}
function s(d, l, g, c, u) {
if (typeof g != "function")
throw new TypeError("The listener must be a function");
var v = new n(g, c || d, u), m = t ? t + l : l;
return d._events[m] ? d._events[m].fn ? d._events[m] = [d._events[m], v] : d._events[m].push(v) : (d._events[m] = v, d._eventsCount++), d;
}
function o(d, l) {
--d._eventsCount === 0 ? d._events = new i() : delete d._events[l];
}
function a() {
this._events = new i(), this._eventsCount = 0;
}
a.prototype.eventNames = function() {
var l = [], g, c;
if (this._eventsCount === 0) return l;
for (c in g = this._events)
e.call(g, c) && l.push(t ? c.slice(1) : c);
return Object.getOwnPropertySymbols ? l.concat(Object.getOwnPropertySymbols(g)) : l;
}, a.prototype.listeners = function(l) {
var g = t ? t + l : l, c = this._events[g];
if (!c) return [];
if (c.fn) return [c.fn];
for (var u = 0, v = c.length, m = new Array(v); u < v; u++)
m[u] = c[u].fn;
return m;
}, a.prototype.listenerCount = function(l) {
var g = t ? t + l : l, c = this._events[g];
return c ? c.fn ? 1 : c.length : 0;
}, a.prototype.emit = function(l, g, c, u, v, m) {
var f = t ? t + l : l;
if (!this._events[f]) return !1;
var h = this._events[f], x = arguments.length, E, T;
if (h.fn) {
switch (h.once && this.removeListener(l, h.fn, void 0, !0), x) {
case 1:
return h.fn.call(h.context), !0;
case 2:
return h.fn.call(h.context, g), !0;
case 3:
return h.fn.call(h.context, g, c), !0;
case 4:
return h.fn.call(h.context, g, c, u), !0;
case 5:
return h.fn.call(h.context, g, c, u, v), !0;
case 6:
return h.fn.call(h.context, g, c, u, v, m), !0;
}
for (T = 1, E = new Array(x - 1); T < x; T++)
E[T - 1] = arguments[T];
h.fn.apply(h.context, E);
} else {
var M = h.length, P;
for (T = 0; T < M; T++)
switch (h[T].once && this.removeListener(l, h[T].fn, void 0, !0), x) {
case 1:
h[T].fn.call(h[T].context);
break;
case 2:
h[T].fn.call(h[T].context, g);
break;
case 3:
h[T].fn.call(h[T].context, g, c);
break;
case 4:
h[T].fn.call(h[T].context, g, c, u);
break;
default:
if (!E) for (P = 1, E = new Array(x - 1); P < x; P++)
E[P - 1] = arguments[P];
h[T].fn.apply(h[T].context, E);
}
}
return !0;
}, a.prototype.on = function(l, g, c) {
return s(this, l, g, c, !1);
}, a.prototype.once = function(l, g, c) {
return s(this, l, g, c, !0);
}, a.prototype.removeListener = function(l, g, c, u) {
var v = t ? t + l : l;
if (!this._events[v]) return this;
if (!g)
return o(this, v), this;
var m = this._events[v];
if (m.fn)
m.fn === g && (!u || m.once) && (!c || m.context === c) && o(this, v);
else {
for (var f = 0, h = [], x = m.length; f < x; f++)
(m[f].fn !== g || u && !m[f].once || c && m[f].context !== c) && h.push(m[f]);
h.length ? this._events[v] = h.length === 1 ? h[0] : h : o(this, v);
}
return this;
}, a.prototype.removeAllListeners = function(l) {
var g;
return l ? (g = t ? t + l : l, this._events[g] && o(this, g)) : (this._events = new i(), this._eventsCount = 0), this;
}, a.prototype.off = a.prototype.removeListener, a.prototype.addListener = a.prototype.on, a.prefixed = t, a.EventEmitter = a, r.exports = a;
})(Q);
var q = Q.exports;
const j = /* @__PURE__ */ te(q), ie = /#EXTINF:(\d+\.\d+),(.*?),(.*?)\s*$/;
class ne extends j {
constructor() {
super(), this.segments = [], this.currentSegmentIndex = -1, this.virtualTotalDuration = 0, this.isPlaying = !1, this.currentVirtualTime = 0, this.currentPhysicalTime = 0, this.bufferedRanges = [], this.loadingDelay = 500, this.playbackRate = 1, this.playbackTimer = null, this.setupEventEmitter();
}
/**
* 设置事件发射器
*/
setupEventEmitter() {
}
/**
* 加载播放列表
* @param playlist 播放列表信息
*/
loadPlaylist(e) {
this.segments = e.segments, this.virtualTotalDuration = e.totalDuration, this.currentSegmentIndex = -1, this.currentVirtualTime = 0, this.currentPhysicalTime = 0, this.bufferedRanges = [], this.emit("debug", `加载了播放列表,共${this.segments.length}个片段,总时长${this.virtualTotalDuration}秒`), this.emit("bufferUpdate", this.bufferedRanges);
}
/**
* 根据m3u8内容创建播放列表
* @param m3u8Content m3u8文件内容
* @param baseUrl 基础URL
*/
createPlaylistFromM3U8(e, t) {
const i = e.split(`
`), n = [];
let s = 0, o = null, a = null, d = 0, l = 0, g = 0;
for (let u = 0; u < i.length; u++) {
const v = i[u].trim(), m = ie.exec(v);
if (m) {
s = parseFloat(m[1]), o = m[2] || null, a = m[3] || null, this.emit("debug", `解析EXTINF: 时长=${s}秒, 物理时间=${o || "N/A"}, 编解码器=${a || "N/A"}`);
continue;
}
if (v && !v.startsWith("#")) {
const f = this.resolveUrl(t, v), h = {
index: d,
url: f,
duration: s,
virtualStartTime: l,
virtualEndTime: l + s,
physicalStartTime: g,
physicalEndTime: g + s,
physicalTime: o ? new Date(o) : null,
codec: a,
isLoaded: !1,
isLoading: !1,
isBuffered: !1
};
n.push(h), l += s, g += s, d++, s = 0, o = null, a = null;
}
}
const c = n.reduce((u, v) => u + v.duration, 0);
return this.emit("debug", `从M3U8解析出${n.length}个片段,总时长: ${c}秒`), {
segments: n,
totalDuration: c
};
}
/**
* 解析URL
* @param baseUrl 基础URL
* @param relativeUrl 相对URL
*/
resolveUrl(e, t) {
return t.startsWith("http://") || t.startsWith("https://") ? t : new URL(t, e).href;
}
/**
* 查找包含指定虚拟时间的片段
* @param virtualTime 虚拟时间
* @returns 片段索引和开始时间
*/
findSegmentForVirtualTime(e) {
for (const t of this.segments)
if (e >= t.virtualStartTime && e < t.virtualEndTime)
return {
segment: t,
offsetInSegment: e - t.virtualStartTime
};
return { segment: null, offsetInSegment: 0 };
}
/**
* 将虚拟时间转换为物理时间
* @param virtualTime 虚拟时间
*/
virtualToPhysicalTime(e) {
const { segment: t, offsetInSegment: i } = this.findSegmentForVirtualTime(e);
return t ? t.physicalStartTime + i : 0;
}
/**
* 将物理时间转换为虚拟时间
* @param physicalTime 物理时间
*/
physicalToVirtualTime(e) {
for (const t of this.segments)
if (e >= t.physicalStartTime && e < t.physicalEndTime) {
const i = e - t.physicalStartTime;
return t.virtualStartTime + i;
}
return 0;
}
/**
* 获取当前播放时间(虚拟时间)
*/
getCurrentVirtualTime() {
return this.currentVirtualTime;
}
/**
* 获取总时长(虚拟时间)
*/
getTotalDuration() {
return this.virtualTotalDuration;
}
/**
* 获取当前播放进度(百分比)
*/
getProgress() {
return this.virtualTotalDuration <= 0 ? 0 : this.currentVirtualTime / this.virtualTotalDuration * 100;
}
/**
* 开始播放
*/
play() {
this.isPlaying || (this.isPlaying = !0, this.emit("playbackStateChange", !0), this.loadSegmentsForTime(this.currentVirtualTime), this.startPlaybackTimer());
}
/**
* 暂停播放
*/
pause() {
this.isPlaying && (this.isPlaying = !1, this.emit("playbackStateChange", !1), this.stopPlaybackTimer());
}
/**
* 跳转到指定时间(虚拟时间)
* @param virtualTime 虚拟时间
*/
seek(e) {
e < 0 ? e = 0 : e > this.virtualTotalDuration && (e = this.virtualTotalDuration);
const t = this.isPlaying;
t && this.pause(), this.currentVirtualTime = e, this.currentPhysicalTime = this.virtualToPhysicalTime(e), this.emit("seeking", this.currentVirtualTime, this.currentPhysicalTime), this.loadSegmentsForTime(e).then(() => {
this.emit("seeked", this.currentVirtualTime, this.currentPhysicalTime), t && this.play();
});
}
/**
* 加载指定时间所需的片段
* @param virtualTime 虚拟时间
*/
async loadSegmentsForTime(e) {
const { segment: t } = this.findSegmentForVirtualTime(e);
if (!t) {
this.emit("debug", `找不到时间 ${e} 对应的片段`);
return;
}
const i = [t.index];
t.virtualEndTime - e < 3 && t.index < this.segments.length - 1 && i.push(t.index + 1), await Promise.all(i.map((s) => this.loadSegmentByIndex(s))), this.currentSegmentIndex = t.index;
}
/**
* 加载指定索引的片段
* @param index 片段索引
*/
async loadSegmentByIndex(e) {
if (e < 0 || e >= this.segments.length)
return;
const t = this.segments[e];
if (!(t.isLoaded || t.isLoading)) {
t.isLoading = !0, this.emit("segmentLoadStart", t), this.emit("debug", `开始加载片段 ${e}: ${t.url}`);
try {
await new Promise((i) => setTimeout(i, this.loadingDelay)), t.isLoaded = !0, t.isLoading = !1, t.isBuffered = !0, this.updateBufferRanges(), this.emit("segmentLoaded", t), this.emit("debug", `片段 ${e} 加载完成: ${t.url}`);
} catch (i) {
t.isLoading = !1, this.emit("segmentLoadError", t, i), this.emit("debug", `片段 ${e} 加载失败: ${i}`);
}
}
}
/**
* 更新缓冲区范围
*/
updateBufferRanges() {
const e = [];
let t = null;
const i = [...this.segments].sort((n, s) => n.index - s.index);
for (const n of i)
n.isBuffered && (t ? n.virtualStartTime <= t.end ? t.end = Math.max(t.end, n.virtualEndTime) : (e.push(t), t = {
start: n.virtualStartTime,
end: n.virtualEndTime
}) : t = {
start: n.virtualStartTime,
end: n.virtualEndTime
});
t && e.push(t), this.bufferedRanges = e, this.emit("bufferUpdate", this.bufferedRanges);
}
/**
* 预加载下一个片段
*/
preloadNextSegment() {
if (this.currentSegmentIndex < 0 || this.currentSegmentIndex >= this.segments.length - 1)
return;
const e = this.currentSegmentIndex + 1;
this.loadSegmentByIndex(e);
}
/**
* 启动播放计时器
*/
startPlaybackTimer() {
if (this.playbackTimer !== null)
return;
const e = 50;
this.playbackTimer = window.setInterval(() => {
if (!this.isPlaying)
return;
this.currentVirtualTime += e / 1e3 * this.playbackRate, this.currentPhysicalTime = this.virtualToPhysicalTime(this.currentVirtualTime);
const { segment: t } = this.findSegmentForVirtualTime(this.currentVirtualTime);
t && t.index !== this.currentSegmentIndex && (this.currentSegmentIndex = t.index, this.preloadNextSegment()), this.currentVirtualTime >= this.virtualTotalDuration && (this.currentVirtualTime = this.virtualTotalDuration, this.pause()), this.emit("timeUpdate", this.currentVirtualTime, this.currentPhysicalTime);
}, e);
}
/**
* 停止播放计时器
*/
stopPlaybackTimer() {
this.playbackTimer !== null && (window.clearInterval(this.playbackTimer), this.playbackTimer = null);
}
/**
* 设置播放速率
* @param rate 播放速率
*/
setPlaybackRate(e) {
this.playbackRate = e;
}
/**
* 销毁实例
*/
destroy() {
this.stopPlaybackTimer(), this.removeAllListeners(), this.segments = [];
}
}
class se extends j {
/**
* Constructor
*/
constructor() {
super(), this.slidingWindowConfig = {
forward: 2,
backward: 1,
enabled: !0
}, this.segments = [], this.currentSegmentIndex = -1, this.segmentQueue = [], this.pendingSegmentLoads = [], this.isProcessingQueue = !1, this.mediaSource = null, this.sourceBuffer = null, this.isMediaSourceReady = !1, this.videoElement = null, this.fmp4InitSegmentUrl = null, this.fmp4InitSegmentLoaded = !1, this.bufferRanges = [];
}
/**
* Initialize the SegmentLoader with required components
* @param mediaSource MediaSource instance
* @param videoElement HTML video element
* @param segments Array of media segments
* @param codec MIME type and codec information for creating SourceBuffer
* @param fmp4InitSegmentUrl Optional URL for initialization segment
*/
initialize(e, t, i, n, s = null) {
if (this.mediaSource = e, this.sourceBuffer = null, this.videoElement = t, this.segments = i, this.fmp4InitSegmentUrl = s, this.fmp4InitSegmentLoaded = !1, this.currentSegmentIndex = -1, this.segmentQueue = [], this.pendingSegmentLoads = [], this.isProcessingQueue = !1, this.isMediaSourceReady = (e == null ? void 0 : e.readyState) === "open" || !1, this.bufferRanges = [], e) {
const o = () => {
this.log("MediaSource 'sourceopen' event received in SegmentLoader", "info"), this.isMediaSourceReady = !0, this.setMediaSourceReady(!0);
try {
e.readyState === "open" && (this.sourceBuffer = e.addSourceBuffer(n), this.log(`SourceBuffer created successfully with codec: ${n}`, "info"), this.setupSourceBufferEvents(), this.processPendingSegmentLoads());
} catch (a) {
this.log(`Failed to create SourceBuffer: ${a}`, "error"), this.emit("segmentLoadError", -1, new Error(`Failed to create SourceBuffer: ${a}`));
}
e.removeEventListener("sourceopen", o);
};
if (e.addEventListener("sourceopen", o), e.readyState === "open") {
this.log("MediaSource already in 'open' state during initialization", "info"), this.isMediaSourceReady = !0, this.setMediaSourceReady(!0);
try {
this.sourceBuffer = e.addSourceBuffer(n), this.log(`SourceBuffer created successfully with codec: ${n}`, "info"), this.setupSourceBufferEvents();
} catch (a) {
this.log(`Failed to create SourceBuffer: ${a}`, "error"), this.emit("segmentLoadError", -1, new Error(`Failed to create SourceBuffer: ${a}`));
}
} else
this.log(`MediaSource in '${e.readyState}' state during initialization, waiting for 'sourceopen' event`, "info");
}
this.sourceBuffer && this.setupSourceBufferEvents(), this.log(`SegmentLoader initialized with ${i.length} segments`, "info");
}
/**
* Setup source buffer event listeners
*/
setupSourceBufferEvents() {
this.sourceBuffer && (this.sourceBuffer.addEventListener("updateend", () => {
this.fmp4InitSegmentUrl && !this.fmp4InitSegmentLoaded ? this.loadInitSegment() : this.processNextSegment(), this.updateBufferRanges();
}), this.sourceBuffer.addEventListener("error", (e) => {
this.log(`SourceBuffer error: ${e}`, "error");
}));
}
/**
* Set the sliding window configuration
* @param config Configuration object
*/
setSlidingWindowConfig(e) {
e.forward !== void 0 && (this.slidingWindowConfig.forward = e.forward), e.backward !== void 0 && (this.slidingWindowConfig.backward = e.backward), e.enabled !== void 0 && (this.slidingWindowConfig.enabled = e.enabled), this.log(
`Sliding window configuration updated: forward=${this.slidingWindowConfig.forward}, backward=${this.slidingWindowConfig.backward}, enabled=${this.slidingWindowConfig.enabled}`,
"info"
), this.currentSegmentIndex !== -1 && this.slidingWindowConfig.enabled && this.applySegmentSlidingWindow(this.currentSegmentIndex);
}
/**
* Get the current sliding window configuration
*/
getSlidingWindowConfig() {
return { ...this.slidingWindowConfig };
}
/**
* Update the current segment index
* @param index New segment index
*/
setCurrentSegmentIndex(e) {
e !== this.currentSegmentIndex && (this.currentSegmentIndex = e, this.log(`Current segment index updated to #${e}`, "info"), this.slidingWindowConfig.enabled && this.applySegmentSlidingWindow(e));
}
/**
* Load a segment with MSE
* @param segmentIndex Segment index to load
*/
loadSegmentWithMSE(e) {
if (e === void 0 || e < 0 || e >= this.segments.length) {
this.log(`Invalid segment index: ${e}`, "error");
return;
}
if (this.slidingWindowConfig.enabled && this.currentSegmentIndex !== -1 && !this.isSegmentInSlidingWindow(e, this.currentSegmentIndex)) {
this.log(`Segment #${e} is outside sliding window, skipping load`, "info");
return;
}
const t = this.segments[e];
if (t.isLoading) {
this.log(`Segment #${e} is already loading, skipping duplicate request`, "info");
return;
}
if (t.isLoaded && t.isBuffered) {
this.log(`Segment #${e} is already loaded, skipping`, "info");
return;
}
if (!this.mediaSource || !this.sourceBuffer || this.mediaSource.readyState !== "open") {
this.pendingSegmentLoads.includes(e) || (this.pendingSegmentLoads.push(e), this.log(`MediaSource not ready, adding segment #${e} to pending queue`, "info"));
return;
}
this.enqueueSegment(e);
}
/**
* Load all segments
*/
loadAllSegments() {
this.log("Starting to load all segments", "info"), this.segmentQueue = [];
for (let e = 0; e < this.segments.length; e++)
this.enqueueSegment(e);
}
/**
* Clear all segments from the buffer
*/
clearAllSegments() {
if (this.log("Clearing all loaded segments", "info"), this.sourceBuffer && !this.sourceBuffer.updating)
try {
this.sourceBuffer.remove(0, 1 / 0), this.segments.forEach((e) => {
e.isLoaded = !1, e.isBuffered = !1;
}), this.bufferRanges = [], this.emit("bufferUpdate", this.bufferRanges);
} catch (e) {
this.log(`Failed to clear segments: ${e}`, "error");
}
}
/**
* Check if a segment is within the sliding window
* @param segmentIndex Segment index to check
* @param currentIndex Current playback segment index
*/
isSegmentInSlidingWindow(e, t) {
if (!this.slidingWindowConfig.enabled) return !0;
const i = Math.max(0, t - this.slidingWindowConfig.backward), n = Math.min(
this.segments.length - 1,
t + this.slidingWindowConfig.forward
);
return e >= i && e <= n;
}
/**
* Apply sliding window logic - load needed segments and clean up unneeded ones
* @param currentIndex Current playback segment index
*/
applySegmentSlidingWindow(e) {
if (!this.slidingWindowConfig.enabled) return;
this.log(`Applying sliding window, current segment index: ${e}`, "debug");
const t = Math.max(0, e - this.slidingWindowConfig.backward), i = Math.min(
this.segments.length - 1,
e + this.slidingWindowConfig.forward
);
this.log(`Sliding window range: ${t} to ${i}`, "debug");
for (let n = e; n <= i; n++) {
const s = this.segments[n];
!s.isLoaded && !s.isLoading && (this.log(`Sliding window loading segment #${n}`, "info"), this.loadSegmentWithMSE(n));
}
if (this.sourceBuffer && !this.sourceBuffer.updating && t > 1)
try {
const n = this.segments[t];
if (n && n.virtualStartTime !== void 0) {
const s = n.virtualStartTime;
if (s > 0.5) {
this.log(`Trying to clean up segments outside window, time range: 0 to ${s.toFixed(2)}`, "info"), this.sourceBuffer.remove(0, s);
for (let o = 0; o < t; o++)
this.segments[o].isBuffered && (this.segments[o].isBuffered = !1, this.log(`Marked segment #${o} as not buffered`, "debug"), this.emit("segmentRemoved", o));
}
}
} catch (n) {
this.log(`Failed to clean up old segments: ${n}`, "error");
}
}
/**
* Add a segment to the processing queue
* @param segmentIndex Segment index to add
*/
enqueueSegment(e) {
if (e === void 0 || e < 0 || e >= this.segments.length) {
this.log(`Invalid segment index: ${e}`, "error");
return;
}
if (this.segmentQueue.some((t) => t === e)) {
this.log(`Segment #${e} is already in queue, skipping duplicate add`, "info");
return;
}
this.segmentQueue.push(e), this.log(`Added segment #${e} to queue`, "info"), this.emit("segmentQueued", e), this.isProcessingQueue || this.processNextSegment();
}
/**
* Process the next segment in the queue
*/
processNextSegment() {
if (this.segmentQueue.length === 0 || this.isProcessingQueue)
return;
if (!this.mediaSource || !this.sourceBuffer || this.mediaSource.readyState !== "open") {
this.log("MediaSource not ready, cannot process queue", "warning");
return;
}
if (this.sourceBuffer.updating) {
this.log("SourceBuffer is updating, waiting for it to complete before processing queue", "info"), this.sourceBuffer.addEventListener("updateend", () => {
this.sourceBuffer && this.sourceBuffer.removeEventListener("updateend", () => {
}), this.processNextSegment();
});
return;
}
this.isProcessingQueue = !0;
const e = this.segmentQueue.shift();
if (e === void 0) {
this.log("Queue item undefined, skipping processing", "error"), this.isProcessingQueue = !1, this.processNextSegment();
return;
}
const t = this.segments[e];
if (!t) {
this.log(`Segment #${e} does not exist`, "error"), this.isProcessingQueue = !1, this.processNextSegment();
return;
}
if (t.isBuffered) {
this.log(`Segment #${e} is already buffered, skipping load`, "info"), this.isProcessingQueue = !1, this.processNextSegment();
return;
}
t.isLoading = !0, this.emit("segmentLoadStart", e);
const i = t.url;
this.log(`Starting to load segment #${e}: ${i}`, "info"), fetch(i).then((n) => {
if (!n.ok)
throw new Error(`HTTP error! status: ${n.status}`);
return n.arrayBuffer();
}).then((n) => {
if (!this.sourceBuffer)
throw new Error("SourceBuffer not available");
return this.sourceBuffer.updating ? new Promise((s) => {
const o = () => {
this.sourceBuffer && this.sourceBuffer.removeEventListener("updateend", o), s(n);
};
this.sourceBuffer && this.sourceBuffer.addEventListener("updateend", o);
}) : n;
}).then((n) => {
if (this.sourceBuffer && !this.sourceBuffer.updating)
try {
this.sourceBuffer.appendBuffer(n);
const s = () => {
this.sourceBuffer && this.sourceBuffer.removeEventListener("updateend", s), t.isLoading = !1, t.isLoaded = !0, t.isBuffered = !0, this.log(`Segment #${e} loaded successfully`, "success"), this.emit("segmentLoaded", e), this.updateBufferRanges(), this.isProcessingQueue = !1, this.emit("segmentLoadCompleted", e), this.processNextSegment();
};
this.sourceBuffer && this.sourceBuffer.addEventListener("updateend", s);
} catch (s) {
this.log(`Error appending segment #${e} to buffer: ${s}`, "error"), t.isLoading = !1, this.isProcessingQueue = !1, this.emit("segmentLoadError", e, s), this.processNextSegment();
}
else
this.log("SourceBuffer not ready or updating when trying to append", "error"), t.isLoading = !1, this.isProcessingQueue = !1, this.processNextSegment();
}).catch((n) => {
this.log(`Failed to load segment #${e}: ${n}`, "error"), t.isLoading = !1, this.isProcessingQueue = !1, this.emit("segmentLoadError", e, n), this.processNextSegment();
});
}
/**
* Process pending segment loads when MediaSource becomes ready
*/
processPendingSegmentLoads() {
if (this.pendingSegmentLoads.length === 0)
return;
this.log(`Processing ${this.pendingSegmentLoads.length} pending segment load requests`, "info");
const e = [...this.pendingSegmentLoads];
this.pendingSegmentLoads = [];
for (const t of e)
this.log(`Loading segment #${t} from pending queue`, "info"), this.enqueueSegment(t);
}
/**
* Set MediaSource readiness state
* @param isReady Whether the MediaSource is ready
*/
setMediaSourceReady(e) {
this.isMediaSourceReady = e, e ? (this.log("MediaSource is now ready", "info"), this.processPendingSegmentLoads()) : this.log("MediaSource is no longer ready", "warning");
}
/**
* Load the initialization segment
*/
loadInitSegment() {
!this.fmp4InitSegmentUrl || this.fmp4InitSegmentLoaded || (this.log(`Loading FMP4 initialization segment: ${this.fmp4InitSegmentUrl}`, "info"), fetch(this.fmp4InitSegmentUrl).then((e) => {
if (!e.ok)
throw new Error(`HTTP error! status: ${e.status}`);
return e.arrayBuffer();
}).then((e) => {
if (!this.sourceBuffer || this.sourceBuffer.updating) {
if (this.sourceBuffer) {
const i = () => {
this.sourceBuffer && this.sourceBuffer.removeEventListener("updateend", i), this.loadInitSegment();
};
this.sourceBuffer.addEventListener("updateend", i);
}
return;
}
this.sourceBuffer.appendBuffer(e);
const t = () => {
this.sourceBuffer && this.sourceBuffer.removeEventListener("updateend", t), this.fmp4InitSegmentLoaded = !0, this.log("FMP4 initialization segment loaded successfully", "success"), this.processNextSegment();
};
this.sourceBuffer && this.sourceBuffer.addEventListener("updateend", t);
}).catch((e) => {
this.log(`Failed to load FMP4 initialization segment: ${e}`, "error");
}));
}
/**
* Update buffer ranges from the video element
*/
updateBufferRanges() {
if (!this.videoElement) return;
const e = this.videoElement.buffered, t = [];
for (let i = 0; i < e.length; i++) {
const n = e.start(i), s = e.end(i);
t.push({
start: n,
end: s
});
}
this.bufferRanges = t, this.emit("bufferUpdate", t);
}
/**
* Log a message and emit a log event
* @param message Log message
* @param type Log type
*/
log(e, t) {
switch (this.emit("log", e, t), t) {
case "info":
console.info(`[SegmentLoader] ${e}`);
break;
case "success":
console.log(`[SegmentLoader] %c${e}`, "color: green");
break;
case "warning":
console.warn(`[SegmentLoader] ${e}`);
break;
case "error":
console.error(`[SegmentLoader] ${e}`);
break;
case "debug":
console.debug(`[SegmentLoader] ${e}`);
break;
}
}
}
class A extends q.EventEmitter {
/**
* 构造函数
* @param videoElement HTML视频元素
* @param options 解复用器选项
*/
constructor(e, t = {}) {
super(), this.isDestroyed = !1, this.totalDuration = 0, this.currentSegmentIndex = 0, this.mediaSource = null, this.lastTimeUpdateValue = 0, this.lastTimeUpdateTimestamp = 0, this.stuckDuration = 0, this.stuckDetectionThreshold = 2e3, this.handleVideoError = (i) => {
const n = i.target;
this.handleError("Video error", n.error);
}, this.handleSeeking = () => {
this.log(`Video seeking to ${this.videoElement.currentTime}`, "debug");
}, this.handleTimeUpdate = () => {
const i = this.videoElement.currentTime, n = this.virtualTimeline.physicalToVirtualTime(i), s = this.findSegmentForTime(n);
if (s && s.index !== void 0) {
const a = s.index;
this.currentSegmentIndex !== a && (this.log(`Playback entered segment #${a}`, "debug"), this.currentSegmentIndex = a, this.segmentLoader.setCurrentSegmentIndex(a), this.segmentLoader.applySegmentSlidingWindow(a), a < this.getTotalSegments() - 1 && this.segmentLoader.loadSegmentWithMSE(a + 1));
}
const o = Date.now();
Math.abs(this.lastTimeUpdateValue - n) < 0.01 ? (this.stuckDuration += o - this.lastTimeUpdateTimestamp, this.stuckDuration > this.stuckDetectionThreshold && this.recoverFromStuck(n)) : this.stuckDuration = 0, this.lastTimeUpdateValue = n, this.lastTimeUpdateTimestamp = o;
}, this.videoElement = e, this.codec = t.codec || 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', this.maxBufferLength = t.maxBufferLength || 30, this.logLevel = t.logLevel || "debug", this.virtualTimeline = new ne(), this.setupVirtualTimelineListeners(), this.segmentLoader = new se(), t.slidingWindow && this.segmentLoader.setSlidingWindowConfig(t.slidingWindow), this.setupSegmentLoaderListeners();
}
/**
* 设置片段加载器事件监听
*/
setupSegmentLoaderListeners() {
this.segmentLoader.on("segmentLoaded", (e) => {
this.emit("segmentLoaded", e), this.log(`Segment ${e} loaded`, "debug");
}), this.segmentLoader.on("bufferUpdate", (e) => {
const t = e.map((i) => ({ start: i.start, end: i.end }));
this.emit("bufferUpdate", t);
}), this.segmentLoader.on("log", (e, t) => {
this.log(e, t === "error" ? "error" : "debug");
}), this.segmentLoader.on("segmentLoadError", (e, t) => {
this.handleError(`Failed to load segment ${e}`, t);
});
}
/**
* 设置虚拟时间线事件监听
*/
setupVirtualTimelineListeners() {
this.virtualTimeline.on("segmentLoaded", (e) => {
this.emit("segmentLoaded", e.index), this.log(`Segment ${e.index} loaded (virtual time: ${e.virtualStartTime.toFixed(2)}s-${e.virtualEndTime.toFixed(2)}s)`, "debug");
}), this.virtualTimeline.on("bufferUpdate", (e) => {
const t = e.map((i) => ({ start: i.start, end: i.end }));
this.emit("bufferUpdate", t);
}), this.virtualTimeline.on("debug", (e) => this.log(e, "debug")), this.virtualTimeline.on("timeUpdate", (e) => {
this.lastTimeUpdateValue = e, this.lastTimeUpdateTimestamp = Date.now();
}), this.virtualTimeline.on("seeking", (e, t) => this.log(`Seeking to virtual time: ${e.toFixed(2)}s (physical time: ${t.toFixed(2)}s)`, "debug")), this.virtualTimeline.on("seeked", (e, t) => {
this.log(`Seeked to virtual time: ${e.toFixed(2)}s (physical time: ${t.toFixed(2)}s)`, "debug"), this.tryResumePlayback();
});
}
// ===== 公共 API =====
/**
* 初始化解复用器
* @param url M3U8播放列表URL
*/
async init(e) {
try {
this.log(`Initializing HLSv7Demuxer with URL: ${e}`, "debug");
const t = await this.fetchPlaylist(e);
this.virtualTimeline.loadPlaylist(t), this.totalDuration = t.totalDuration, await this.prepareSegmentLoader(t), this.log(`Playlist loaded with ${t.segments.length} segments, total duration: ${this.totalDuration.toFixed(2)}s`, "debug"), this.log("HLSv7Demuxer initialized successfully", "debug");
} catch (t) {
throw this.handleError("Failed to initialize HLSv7Demuxer", t), t;
}
}
/**
* 获取当前播放时间
* @returns 当前播放时间(秒)
*/
getCurrentTime() {
return this.checkDestroyed(), this.virtualTimeline.getCurrentVirtualTime();
}
/**
* 获取总时长
* @returns 总时长(秒)
*/
getTotalDuration() {
return this.checkDestroyed(), this.totalDuration;
}
/**
* 获取播放进度
* @returns 播放进度(0-1)
*/
getProgress() {
this.checkDestroyed();
const e = this.getCurrentTime(), t = this.getTotalDuration();
return t > 0 ? Math.min(1, Math.max(0, e / t)) : 0;
}
/**
* 播放视频
*/
async play() {
this.checkDestroyed();
try {
this.virtualTimeline.play();
const e = this.getCurrentTime(), t = this.findSegmentForTime(e);
t && t.index !== void 0 && (this.currentSegmentIndex = t.index, this.segmentLoader.loadSegmentWithMSE(t.index)), await this.videoElement.play();
} catch (e) {
throw this.handleError("Failed to play video", e), e;
}
}
/**
* 暂停视频
*/
pause() {
this.checkDestroyed(), this.videoElement.pause(), this.virtualTimeline.pause();
}
/**
* 跳转到指定时间
* @param time 目标时间(秒)
*/
async seek(e) {
this.checkDestroyed();
try {
this.log(`Seeking to ${e.toFixed(2)}s`, "debug");
const t = this.findSegmentForTime(e);
if (!t || t.index === void 0)
throw new Error(`Cannot find segment for time ${e}`);
const i = this.virtualTimeline.virtualToPhysicalTime(e);
this.currentSegmentIndex = t.index, this.segmentLoader.setCurrentSegmentIndex(t.index), await this.clearBuffer(), this.segmentLoader.loadSegmentWithMSE(t.index), t.index < this.getTotalSegments() - 1 && this.segmentLoader.loadSegmentWithMSE(t.index + 1), this.videoElement.currentTime = i, this.virtualTimeline.seek(e), this.log(`Completed seek to ${e.toFixed(2)}s (physical: ${i.toFixed(2)}s)`, "debug");
} catch (t) {
throw this.handleError(`Failed to seek to ${e}`, t), t;
}
}
/**
* 设置播放速率
* @param rate 播放速率
*/
setPlaybackRate(e) {
this.checkDestroyed(), this.videoElement.playbackRate = e, this.virtualTimeline.setPlaybackRate(e), this.log(`Set playback rate to ${e}`, "debug");
}
/**
* 清除缓冲区
*/
async clearBuffer() {
this.checkDestroyed();
try {
this.segmentLoader.clearAllSegments(), this.log("Buffer cleared", "debug");
} catch (e) {
throw this.handleError("Failed to clear buffer", e), e;
}
}
/**
* 获取总片段数
* @returns 总片段数
*/
getTotalSegments() {
var t;
let e = 0;
for (; (t = this.virtualTimeline.findSegmentForVirtualTime(e * 10)) != null && t.segment && (e++, !(e > 1e3)); )
;
return e || 0;
}
/**
* 设置滑动窗口配置
* @param config 滑动窗口配置
*/
setSlidingWindowConfig(e) {
this.segmentLoader.setSlidingWindowConfig(e);
}
/**
* 获取滑动窗口配置
* @returns 滑动窗口配置
*/
getSlidingWindowConfig() {
return this.segmentLoader.getSlidingWindowConfig();
}
/**
* 销毁解复用器
*/
destroy() {
this.isDestroyed || (this.log("Destroying HLSv7Demuxer", "debug"), this.removeEventListeners(), this.virtualTimeline.destroy(), this.segmentLoader.clearAllSegments(), this.videoElement && (this.videoElement.src = "", this.videoElement.load()), this.isDestroyed = !0);
}
/**
* 获取MediaSource实例
* @returns MediaSource实例或null
*/
getMediaSource() {
return this.mediaSource;
}
// ===== 私有方法 =====
/**
* 设置事件监听器
*/
setupEventListeners() {
this.videoElement.addEventListener("error", this.handleVideoError), this.videoElement.addEventListener("seeking", this.handleSeeking), this.videoElement.addEventListener("timeupdate", this.handleTimeUpdate);
}
/**
* 移除事件监听器
*/
removeEventListeners() {
this.videoElement.removeEventListener("error", this.handleVideoError), this.videoElement.removeEventListener("seeking", this.handleSeeking), this.videoElement.removeEventListener("timeupdate", this.handleTimeUpdate);
}
/**
* 获取播放列表
* @param url 播放列表URL
* @returns 播放列表信息
*/
async fetchPlaylist(e) {
try {
this.log(`Fetching playlist: ${e}`, "debug");
const t = await this.parseM3U8Playlist(e), i = t.segments.map((n) => ({
index: n.index,
url: n.url,
duration: n.duration,
time: n.physicalTime || /* @__PURE__ */ new Date()
}));
return this.emit("playlistUpdate", i), t;
} catch (t) {
throw this.handleError(`Failed to fetch playlist: ${e}`, t), t;
}
}
/**
* 准备片段加载器
* @param playlistInfo 播放列表信息
*/
async prepareSegmentLoader(e) {
try {
const t = new MediaSource();
this.mediaSource = t;
const i = URL.createObjectURL(t);
this.log(`MediaSource created with readyState: ${t.readyState}`, "debug"), this.videoElement.src = i, this.log(`Set video.src to MediaSource URL: ${i}`, "debug"), await new Promise((s, o) => {
const a = () => {
this.log(`MediaSource 'sourceopen' event fired, readyState: ${t.readyState}`, "debug"), t.removeEventListener("sourceopen", a), s();
}, d = () => {
this.log(`MediaSource 'sourceclose' event fired unexpectedly, readyState: ${t.readyState}`, "warning");
};
t.addEventListener("sourceopen", a), t.addEventListener("sourceclose", d);
const l = setTimeout(() => {
this.log(`MediaSource 'sourceopen' event timed out after 5s, readyState: ${t.readyState}`, "error"), t.removeEventListener("sourceopen", a), this.log(`Video element readyState: ${this.videoElement.readyState}, networkState: ${this.videoElement.networkState}`, "debug"), this.videoElement.error && this.log(`Video element error: ${this.videoElement.error.message}`, "error"), s();
}, 5e3);
t.readyState === "open" && (this.log("MediaSource already in 'open' state", "debug"), clearTimeout(l), s());
}), t.readyState !== "open" && this.log(`MediaSource is still not 'open' (current state: ${t.readyState}). This may cause playback issues.`, "warning");
const n = e.initSegmentUrl || null;
this.segmentLoader.initialize(
t,
this.videoElement,
e.segments,
this.codec,
// 传递codec信息,让SegmentLoader内部创建SourceBuffer
n
), e.segments.length > 0 && (this.segmentLoader.loadSegmentWithMSE(0), e.segments.length > 1 && this.segmentLoader.loadSegmentWithMSE(1));
} catch (t) {
throw this.log(`Error during prepareSegmentLoader: ${t}`, "error"), this.handleError("Failed to prepare segment loader", t), t;
}
}
/**
* 查找特定时间对应的片段信息
* @param time 查询时间(秒)
* @returns 片段信息 {index, startTime}
*/
findSegmentForTime(e) {
const t = this.virtualTimeline.findSegmentForVirtualTime(e);
return t && t.segment ? {
index: t.segment.index,
startTime: t.segment.virtualStartTime
} : { index: 0, startTime: 0 };
}
/**
* 从卡住状态恢复
* @param currentTime 当前时间
*/
recoverFromStuck(e) {
this.log(`Playback appears stuck at ${e.toFixed(2)}s, attempting recovery`, "debug");
const t = this.findSegmentForTime(e);
if (t && t.index !== void 0) {
const i = t.index;
i < this.getTotalSegments() - 1 ? (this.log("Stuck at segment boundary, attempting to load next segment", "debug"), this.segmentLoader.loadSegmentWithMSE(i + 1)) : (this.log("Stuck within segment, attempting to reload current segment", "debug"), this.segmentLoader.loadSegmentWithMSE(i));
}
this.stuckDuration = 0;
}
/**
* 尝试恢复播放
*/
tryResumePlayback() {
this.videoElement.paused && !this.videoElement.ended && (this.videoElement.readyState >= 2 ? (this.log("Attempting to resume playback after seek", "debug"), this.videoElement.play().catch((e) => {
this.log(`Failed to resume playback: ${e}`, "error");
})) : this.log("Video not ready to play, waiting for more data", "debug"));
}
/**
* 检查解复用器是否已销毁
*/
checkDestroyed() {
if (this.isDestroyed)
throw new Error("HLSv7Demuxer has been destroyed");
}
/**
* 记录日志
* @param message 日志消息
* @param level 日志级别
*/
log(e, t = "debug") {
(t === "error" || this.logLevel === t || this.logLevel === "debug") && (t === "error" ? console.error(`[HLSv7Demuxer] ${e}`) : console.log(`[HLSv7Demuxer] ${e}`), t === "debug" && this.emit("debug", e));
}
/**
* 处理错误
* @param message 错误消息
* @param error 错误对象
*/
handleError(e, t) {
this.log(`${e}: ${t}`, "error"), this.emit("error", new Error(`${e}: ${t}`));
}
/**
* 解析M3U8播放列表
* @param url M3U8播放列表URL
* @returns 播放列表信息
*/
async parseM3U8Playlist(e) {
try {
const t = await fetch(e);
if (!t.ok)
throw new Error(`HTTP error! status: ${t.status}`);
const n = (await t.text()).split(`
`), s = [];
let o = 0, a = 0, d = 0, l = null, g = null, c = /* @__PURE__ */ new Set();
for (let u = 0; u < n.length; u++) {
const v = n[u].trim();
if (v.startsWith("#EXTINF:")) {
const m = v.match(/#EXTINF:([\d.]+)/);
m && (d = parseFloat(m[1]));
const f = v.substring(v.indexOf(":") + 1).split(",");
if (f.length >= 2)
try {
l = new Date(f[1]);
} catch {
}
if (f.length >= 3 ? (g = f[2].trim(), c.add(g), this.log(`解析EXTINF: 时长=${d}秒, 物理时间=${(l == null ? void 0 : l.toISOString()) || "unknown"}, 编解码器=${g}`, "debug")) : this.log(`解析EXTINF: 时长=${d}秒, 物理时间=${(l == null ? void 0 : l.toISOString()) || "unknown"}`, "debug"), !g) {
const h = v.match(/CODECS="([^"]+)"/);
h && (g = h[1], c.add(g));
}
}
if (!v.startsWith("#") && v.length > 0) {
const m = new URL(v, e).href;
s.push({
index: s.length,
url: m,
duration: d,
virtualStartTime: a,
virtualEndTime: a + d,
physicalStartTime: 0,
physicalEndTime: d,
physicalTime: l,
codec: g,
isLoaded: !1,
isLoading: !1,
isBuffered: !1
}), a += d, o += d, l = null, g = null;
}
}
if (this.log(`从M3U8解析出${s.length}个片段,总时长: ${o}秒`, "debug"), c.size > 0) {
const u = Array.from(c);
let v = u.some((f) => f.startsWith("mp4a"));
u.some((f) => f.startsWith("avc1") || f.startsWith("hvc1") || f.startsWith("vp9")) ? (this.codec = `video/mp4; codecs="${u.join(", ")}"`, this.log(`从M3U8中检测到编解码器,使用视频编解码器: ${this.codec}`, "info")) : this.log(`未检测到视频编解码器,使用默认值: ${this.codec}`, "warning");
} else
this.log(`未从M3U8中检测到编解码器信息,使用默认值: ${this.codec}`, "warning");
return this.totalDuration = o, { segments: s, totalDuration: o };
} catch (t) {
throw this.handleError(`Failed to parse M3U8 playlist: ${e}`, t), t;
}
}
}
class oe {
constructor(e = {}) {
w(this, "options");
w(this, "currentTime", 0);
w(this, "currentMediaTime", 0);
this.options = {
timeRanges: [],
...e
};
}
setTimeRanges(e) {
this.options.timeRanges = e, this.initializeTimeRanges(e);
}
initializeTimeRanges(e) {
let t = 0;
e.forEach((i) => {
i.mediaStart = t, i.mediaDuration = i.end - i.start, t += i.mediaDuration;
});
}
findTimeRangeForTime(e) {
var t;
return (t = this.options.timeRanges) == null ? void 0 : t.find(
(i) => e >= i.start && e <= i.end
);
}
originalToMediaTime(e) {
var i;
const t = this.findTimeRangeForTime(e);
if (!t) {
const n = (i = this.options.timeRanges) == null ? void 0 : i.find((o) => e < o.start);
if (n) return n.mediaStart;
const s = [...this.options.timeRanges || []].reverse().find((o) => e > o.end);
return s ? s.mediaStart + s.mediaDuration : 0;
}
return t.mediaStart + (e - t.start);
}
mediaToOriginalTime(e) {
var t;
if (!((t = this.options.timeRanges) != null && t.length)) return e;
for (const i of this.options.timeRanges)
if (e >= i.mediaStart && e < i.mediaStart + i.mediaDuration)
return i.start + (e - i.mediaStart);
return this.options.timeRanges[0].start;
}
getTotalDuration() {
var e;
return ((e = this.options.timeRanges) == null ? void 0 : e.reduce((t, i) => t + i.mediaDuration, 0)) || 0;
}
getTimeRangeExtent() {
var e;
return (e = this.options.timeRanges) != null && e.length ? {
start: Math.min(...this.options.timeRanges.map((t) => t.start)),
end: Math.max(...this.options.timeRanges.map((t) => t.end))
} : { start: 0, end: 0 };
}
setCurrentTime(e, t = !1) {
var i, n;
t ? (this.currentMediaTime = e, this.currentTime = this.mediaToOriginalTime(e)) : (this.currentTime = e, this.currentMediaTime = this.originalToMediaTime(e)), (n = (i = this.options).onTimeUpdate) == null || n.call(i, this.currentTime);
}
getCurrentTime() {
return this.currentTime;
}
getCurrentMediaTime() {
return this.currentMediaTime;
}
seek(e, t = !1) {
var i, n;
this.setCurrentTime(e, t), (n = (i = this.options).onSeek) == null || n.call(i, this.currentTime);
}
getTimeRanges() {
return this.options.timeRanges || [];
}
getGaps() {
const e = [], t = this.options.timeRanges || [];
for (let i = 0; i < t.length - 1; i++)
t[i + 1].start - t[i].end > 0 && e.push({
start: t[i].end,
end: t[i + 1].start
});
return e;
}
}
const re = /* @__PURE__ */ N({
name: "Timeline",
props: {
timeRanges: {
type: Array,
default: () => []
},
showOriginalTimeline: {
type: Boolean,
default: !0
},
showMediaTimeline: {
type: Boolean,
default: !1
},
height: {
type: Number,
default: 20
},
backgroundColor: {
type: String,
default: "#f5f5f5"
},
segmentColor: {
type: String,
default: "rgba(0, 160, 255, 0.3)"
},
gapColor: {
type: String,
default: "rgba(0, 0, 0, 0.1)"
},
cursorColor: {
type: String,
default: "#18a058"
},
onTimeUpdate: Function,
onSeek: Function
},
setup(r) {
const e = L(), t = L(), i = L(), n = L(!1), s = L();
let o;
const a = (y) => new Date(y * 1e3).toLocaleString("zh-CN", {
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: !1
}), d = (y) => {
const p = Math.floor(y / 3600), b = Math.floor(y % 3600 / 60), C = Math.floor(y % 60);
return `${p.toString().padStart(2, "0")}:${b.toString().padStart(2, "0")}:${C.toString().padStart(2, "0")}`;
}, l = (y) => {
const p = Math.floor(y / 60), b = Math.floor(y % 60);
return `${p}:${b.toString().padStart(2, "0")}`;
}, g = () => {
e.value = new oe({
timeRanges: r.timeRanges,
onTimeUpdate: r.onTimeUpdate,
onSeek: r.onSeek
});
}, c = (y, p, b, C, R, k = !1) => {
y.save(), y.fillStyle = "#666", y.font = "10px Arial";
const $ = k ? l(p) : a(p);
y.textAlign = "left", y.fillText($, 8, R - 3);
const D = k ? l(b) : a(b);
y.textAlign = "right", y.fillText(D, C - 8, R - 3), y.restore();
}, u = (y) => {
if (!e.value) return;
const p = e.value.getTimeRanges(), b = p.find((C) => C.start > y);
return b || p[p.length - 1];
}, v = (y) => {
if (!e.value) return;
const p = u(y);
p && e.value.seek(p.start);
}, m = () => {
if (!t.value || !e.value) return;
const y = t.value, p = y.getContext("2d");
if (!p) return;
y.width = y.offsetWidth * window.devicePixelRatio, y.height = r.height * window.devicePixelRatio, p.scale(window.devicePixelRatio, window.devicePixelRatio), p.clearRect(0, 0, y.width, y.height);
const {
start: b,
end: C
} = e.value.getTimeRangeExtent(), R = C - b;
if (R <= 0) return;
const k = y.offsetWidth, $ = y.offsetHeight;
p.fillStyle = r.backgroundColor, p.fillRect(0, 0, k, $), p.fillStyle = r.gapColor, e.value.getGaps().forEach((B) => {
const I = (B.start - b) / R * k, U = (B.end - B.start) / R * k, H = 8, Y = Math.ceil(U / H);
for (let X = 0; X < Y; X++) {
const J = I + X * H;
p.fillRect(J, 0, H / 2, $);
}
}), e.value.getTimeRanges().forEach((B) => {
const I = (B.start - b) / R * k, U = (B.end - B.start) / R * k;
p.fillStyle = r.segmentColor, p.fillRect(I, 0, U, $), p.strokeStyle = r.segmentColor.replace("0.3)", "0.6)"), p.lineWidth = 1, p.strokeRect(I, 0, U, $);
}), c(p, b, C, k, $);
const D = (e.value.getCurrentTime() - b) / R * k;
p.shadowColor = r.cursorColor, p.shadowBlur = 4, p.fillStyle = r.cursorColor, p.fillRect(D - 1, 0, 2, $), p.beginPath(), p.moveTo(D - 2, $ - 4), p.lineTo(D + 2, $ - 4), p.lineTo(D, $), p.closePath(), p.fill(), p.shadowBlur = 0;
}, f = () => {
if (!i.value || !e.value) return;
const y = i.value, p = y.getContext("2d");
if (!p) return;
y.width = y.offsetWidth * window.devicePixelRatio, y.height = r.height * window.devicePixelRatio, p.scale(window.devicePixelRatio, window.devicePixelRatio), p.clearRect(0, 0, y.width, y.height);
const b = e.value.getTotalDuration();
if (b <= 0) return;
const C = y.offsetWidth, R = y.offsetHeight;
p.fillStyle = r.backgroundColor, p.fillRect(0, 0, C, R), e.value.getTimeRanges().forEach(($) => {
const D = $.mediaStart / b * C, B = $.mediaDuration / b * C;
p.fillStyle = r.segmentColor, p.fillRect(D, 0, B, R), p.strokeStyle = r.segmentColor.replace("0.3)", "0.6)"), p.lineWidth = 1, p.strokeRect(D, 0, B, R);
}), c(p, 0, b, C, R, !0);
const k = e.value.getCurrentMediaTime() / b * C;
p.shadowColor = r.cursorColor, p.shadowBlur = 4, p.fillStyle = r.cursorColor, p.fillRect(k - 1, 0, 2, R), p.beginPath(), p.moveTo(k - 2, R - 4), p.lineTo(k + 2, R - 4), p.lineTo(k, R), p.closePath(), p.fill(), p.shadowBlur = 0;
}, h = () => {
r.showOriginalTimeline && m(), r.showMediaTimeline && f(), o = requestAnimationFrame(h);
}, x = (y, p) => {
if (!e.value || !s.value) return;
const b = p.getBoundingClientRect(), C = y.clientX - b.left;
s.value.style.left = `${y.clientX}px`, s.value.style.top = `${y.clientY - 25}px`;
const {
start: R,
end: k
} = e.value.getTimeRangeExtent(), $ = R + C / b.width * (k - R);
s.value.textContent = a($), s.value.style.display = "block", n.value && (e.value.findTimeRangeForTime($) ? e.value.seek($) : v($));
}, E = (y) => {
n.value = !0, document.body.style.cursor = "grabbing";
}, T = () => {
n.value = !1, document.body.style.cursor = "default", s.value && (s.value.style.display = "none");
}, M = () => {
s.value && (s.value.style.display = "none"), n.value || (document.body.style.cursor = "default");
}, P = () => {
document.body.style.cursor = "grab";
}, G = (y, p) => {
if (!e.value || !s.value) return;
const b = p.getBoundingClientRect(), C = y.clientX - b.left;
s.value.style.left = `${y.clientX}px`, s.value.style.top = `${y.clientY - 25}px`;
const R = C / b.width * e.value.getTotalDuration();
s.value.textContent = d(R), s.value.style.displa