Files
v10/dist/default/media/buffer/forward-buffer.js
T

119 lines
5.1 KiB
JavaScript

import { SEGMENT_TIME_EPSILON } from "../types/index.js";
//#region src/media/buffer/forward-buffer.ts
/**
* Forward Buffer Strategy (Simple)
*
* Determines which segments to load for forward buffer management.
* V1 uses simple fixed-duration strategy (buffer N seconds ahead).
*/
/**
* Merge intervals into sorted, disjoint ranges; touching or overlapping ranges
* (gap ≤ `epsilon`) are joined. Empty/inverted ranges are dropped.
*/
function mergeTimeRanges(ranges, epsilon = SEGMENT_TIME_EPSILON) {
const sorted = ranges.filter((r) => r.end > r.start).sort((a, b) => a.start - b.start);
const merged = [];
for (const r of sorted) {
const last = merged[merged.length - 1];
if (last && r.start <= last.end + epsilon) last.end = Math.max(last.end, r.end);
else merged.push({
start: r.start,
end: r.end
});
}
return merged;
}
/**
* Whether `[start, end)` is fully covered by the union of `merged` ranges,
* tolerating `epsilon` of overhang at each edge. `merged` must be disjoint and
* sorted (as returned by `mergeTimeRanges`), so full coverage means a single
* merged range contains the interval.
*/
function isTimeRangeCovered(start, end, merged, epsilon = SEGMENT_TIME_EPSILON) {
return merged.some((r) => r.start <= start + epsilon && r.end >= end - epsilon);
}
/**
* Default forward buffer configuration.
*/
const DEFAULT_FORWARD_BUFFER_CONFIG = { bufferDuration: 30 };
/**
* Get segments that need to be loaded for forward buffer.
*
* Determines which segments to load to maintain target buffer duration.
* Handles discontiguous buffering (gaps after seeks).
*
* Algorithm:
* 1. Calculate target time: currentTime + bufferDuration
* 2. Find all segments in range [currentTime, targetTime)
* 3. Filter out segments already buffered at that time position
* 4. Return segments to load (fills gaps + extends to target)
*
* @param segments - All available segments from playlist
* @param bufferedSegments - Segments already buffered (ordered by startTime)
* @param currentTime - Current playback position in seconds
* @param config - Optional forward buffer configuration
* @returns Array of segments to load (empty if buffer is sufficient)
*
* @example
* // After seek: buffered [0-12, 18-30], playing at 7s
* const toLoad = getSegmentsToLoad(segments, buffered, 7, { bufferDuration: 24 });
* // Returns [seg-12, seg-30] (fills gap, extends to target 31s)
*/
/**
* Calculate the start time from which to flush forward buffer content.
*
* Content that starts at or beyond `currentTime + bufferDuration` is no
* longer needed for the current playback position and should be removed
* from the SourceBuffer. This prevents unbounded accumulation of scattered
* SourceBuffer content after seeks, which can cause QuotaExceededError on
* long-form content.
*
* Returns `Infinity` when nothing needs flushing (no buffered segments
* exist beyond the threshold).
*
* @param bufferedSegments - Segments currently tracked in the buffer model
* @param currentTime - Current playback position in seconds
* @param config - Optional forward buffer configuration
* @returns Start time to flush from (flush range: [flushStart, Infinity)),
* or Infinity if no flush is needed
*
* @example
* // Playing at 0s, buffered [0,6,12,18,24,30,36], bufferDuration=30
* const flushStart = calculateForwardFlushPoint(segments, 0);
* // Returns 30 — flush [30, Infinity), keep [0, 30)
*/
function calculateForwardFlushPoint(bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
if (bufferedSegments.length === 0) return Infinity;
const threshold = currentTime + config.bufferDuration;
const beyond = bufferedSegments.filter((seg) => seg.startTime >= threshold);
if (beyond.length === 0) return Infinity;
return Math.min(...beyond.map((seg) => seg.startTime));
}
/**
* Find the start time of the segment containing `currentTime` (or the last
* segment if `currentTime` is past the end). Returns `undefined` when
* `currentTime` is undefined or when no segment matches.
*
* Used to detect "meaningful currentTime change" — two times that map to the
* same segment start aren't a load-trigger; crossing a segment boundary is.
*/
function segmentStartForTime(currentTime, segments) {
if (currentTime == null) return void 0;
return segments?.find(({ startTime, duration }, i, all) => currentTime >= startTime && (currentTime < startTime + duration || i === all.length - 1))?.startTime;
}
function getSegmentsToLoad(segments, bufferedSegments, currentTime, config = DEFAULT_FORWARD_BUFFER_CONFIG) {
if (segments.length === 0) return [];
const targetTime = currentTime + config.bufferDuration;
const bufferedStartTimes = new Set(bufferedSegments.map((seg) => seg.startTime));
return segments.filter((seg, i) => {
const segmentEnd = seg.startTime + seg.duration;
const overlapsPlayhead = i === segments.length - 1 || segmentEnd > currentTime;
const isInRange = seg.startTime < targetTime && overlapsPlayhead;
const isNotBuffered = !bufferedStartTimes.has(seg.startTime);
return isInRange && isNotBuffered;
});
}
//#endregion
export { DEFAULT_FORWARD_BUFFER_CONFIG, calculateForwardFlushPoint, getSegmentsToLoad, isTimeRangeCovered, mergeTimeRanges, segmentStartForTime };
//# sourceMappingURL=forward-buffer.js.map