mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
119 lines
5.1 KiB
JavaScript
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
|