mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a3e673bd68
commit
12ae97ddba
@@ -168,10 +168,25 @@ export function getSegmentsToLoad(
|
||||
// Find segments to load:
|
||||
// - Overlaps buffer window [currentTime, targetTime)
|
||||
// - Not already buffered at that time position
|
||||
const toLoad = segments.filter((seg) => {
|
||||
// Segment must overlap the buffer window
|
||||
const toLoad = segments.filter((seg, i) => {
|
||||
const segmentEnd = seg.startTime + seg.duration;
|
||||
const isInRange = seg.startTime < targetTime && segmentEnd > currentTime;
|
||||
const isLast = i === segments.length - 1;
|
||||
// Interior segments use strict `>` so a segment the playhead just finished
|
||||
// (endTime === currentTime at a boundary) isn't reloaded. The terminal
|
||||
// segment has no successor and no reachable position past it — the playhead
|
||||
// is clamped to the presentation's range — so it needs no upper bound: it's
|
||||
// loadable whenever the forward window reaches it. That resolves the
|
||||
// exact-end seek (#1828) and the post-loop re-seek — where
|
||||
// `MediaSource.duration`, clamped by `endOfStream()` to the true buffered
|
||||
// end, has grown a hair past the model's EXTINF-derived end — with no
|
||||
// dependency on any duration value.
|
||||
//
|
||||
// LIVE: for an un-ended live/DVR presentation `isLast` is the sliding edge
|
||||
// segment; loading it eagerly here is benign today (forward-window-bounded,
|
||||
// deduped by the buffered check), but the live effort should confirm the
|
||||
// edge / end-of-stream interaction when it lands.
|
||||
const overlapsPlayhead = isLast || segmentEnd > currentTime;
|
||||
const isInRange = seg.startTime < targetTime && overlapsPlayhead;
|
||||
|
||||
// Must not have a segment buffered at this time position
|
||||
const isNotBuffered = !bufferedStartTimes.has(seg.startTime);
|
||||
|
||||
@@ -279,15 +279,14 @@ describe('getSegmentsToLoad', () => {
|
||||
expect(toLoad).toHaveLength(2); // Load both segments within 30s
|
||||
});
|
||||
|
||||
it('should handle currentTime after all segments', () => {
|
||||
it('past all segments, does not reload interior segments the playhead has passed', () => {
|
||||
// A playhead past every segment is unreachable in practice (clamped to the
|
||||
// presentation's range), but the rule stays well-defined: interior segments
|
||||
// behind the playhead are NOT reloaded. The terminal segment has no
|
||||
// successor, so it stays selectable — see the exact-end / overshoot cases.
|
||||
const segments: Segment[] = [createSegment(0, 6), createSegment(6, 6)];
|
||||
|
||||
const bufferedSegments: Segment[] = [];
|
||||
const currentTime = 100;
|
||||
|
||||
const toLoad = getSegmentsToLoad(segments, bufferedSegments, currentTime);
|
||||
|
||||
expect(toLoad).toHaveLength(0); // No segments in range
|
||||
const toLoad = getSegmentsToLoad(segments, [], 100);
|
||||
expect(toLoad.map((s) => s.id)).toEqual(['seg-6']); // only the terminal segment
|
||||
});
|
||||
|
||||
it('should not load segments beyond target', () => {
|
||||
@@ -401,4 +400,39 @@ describe('getSegmentsToLoad', () => {
|
||||
expect(toLoad[0]?.id).toBe('seg-18');
|
||||
});
|
||||
});
|
||||
|
||||
describe('exact-end boundary (#1828)', () => {
|
||||
it('loads the final segment when currentTime is exactly the total duration', () => {
|
||||
// Regression for #1828: seeking to currentTime === duration must still
|
||||
// select the last segment. A strict `endTime > currentTime` overlap test
|
||||
// drops the final segment (whose endTime === duration), so it never loads
|
||||
// and endOfStream() never fires → the seek stalls forever.
|
||||
const segments = [createSegment(0, 6), createSegment(6, 6), createSegment(12, 6)] as const;
|
||||
// Playhead dragged to the exact end (duration = 18), nothing buffered there.
|
||||
const toLoad = getSegmentsToLoad(segments, [], 18);
|
||||
expect(toLoad.map((s) => s.id)).toEqual(['seg-12']);
|
||||
});
|
||||
|
||||
it('loads the final segment when currentTime slightly exceeds its model end', () => {
|
||||
// Post-loop re-seek variant of #1828. After the first end, endOfStream()
|
||||
// clamps MediaSource.duration to the true buffered end, which can run a
|
||||
// hair past the model's EXTINF-derived last-segment end. A later seek to
|
||||
// that grown duration lands just past the last segment's endTime — its
|
||||
// real media still covers the position, so it must still be selected, or
|
||||
// the loader goes idle and the seek stalls.
|
||||
const segments = [createSegment(0, 6), createSegment(6, 6), createSegment(12, 6)] as const;
|
||||
// Model end = 18; duration grew to 18.03, seek lands past seg-12's endTime.
|
||||
const toLoad = getSegmentsToLoad(segments, [], 18.03);
|
||||
expect(toLoad.map((s) => s.id)).toEqual(['seg-12']);
|
||||
});
|
||||
|
||||
it('does not re-select a finished segment at a mid-stream boundary', () => {
|
||||
// Guard: the fix is scoped to the final segment. At an interior boundary
|
||||
// (currentTime === a non-last segment's endTime) the just-finished segment
|
||||
// must NOT be reloaded — only the segments the playhead is entering.
|
||||
const segments = [createSegment(0, 6), createSegment(6, 6), createSegment(12, 6)] as const;
|
||||
const toLoad = getSegmentsToLoad(segments, [], 6);
|
||||
expect(toLoad.map((s) => s.id)).toEqual(['seg-6', 'seg-12']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user