refactor(spf): fold getMax/getMinBufferedEnd into one reduce base

Near-identical loops differing only by comparator → a shared
getBufferedEnd(buffers, isEndMatch). getMinBufferedEnd drops its
abort-on-empty→undefined path (unused: recover-end-stall consumes it only
behind an msEnded guard, where every buffer has ranges), so it skips empty
buffers and floors at 0 like getMaxBufferedEnd. Tests updated to match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2026-07-15 13:56:05 -07:00
co-authored by Claude Opus 4.8
parent 6547e321d9
commit d68698fdb7
2 changed files with 24 additions and 29 deletions
+20 -25
View File
@@ -34,25 +34,31 @@ export function canUpdateDuration(
return !!(mediaSource && presentation && hasPresentationDuration(presentation));
}
export function getBufferedEnd(
buffers: SourceBufferIterable,
isEndMatch: (next: number, current: number) => boolean
): number {
return (
([...buffers].reduce((endMatch: number | undefined, buffer) => {
const { buffered } = buffer;
if (!buffered.length) return endMatch;
const end = buffered.end(buffered.length - 1);
if (!endMatch) return end;
return isEndMatch(end, endMatch) ? end : endMatch;
}, undefined) as number) ?? 0
);
}
const isGreaterThan = (x: number, y: number) => x > y;
const isLessThan = (x: number, y: number) => x < y;
/**
* Get the maximum buffered end time across an iterable of SourceBuffers
* (typically `mediaSource.sourceBuffers`). Returns `0` when the collection is
* empty or no buffer has any buffered ranges.
*/
export function getMaxBufferedEnd(buffers: SourceBufferIterable): number {
let maxEnd = 0;
for (const buffer of buffers) {
const { buffered } = buffer;
if (buffered.length > 0) {
const end = buffered.end(buffered.length - 1);
if (end > maxEnd) {
maxEnd = end;
}
}
}
return maxEnd;
return getBufferedEnd(buffers, isGreaterThan);
}
/**
@@ -67,18 +73,7 @@ export function getMaxBufferedEnd(buffers: SourceBufferIterable): number {
* when tracks end at slightly different times (e.g. skewed A/V near end-of-stream).
*/
export function getMinBufferedEnd(buffers: SourceBufferIterable): number | undefined {
let minEnd: number | undefined;
for (const buffer of buffers) {
const { buffered } = buffer;
if (buffered.length === 0) return undefined;
const end = buffered.end(buffered.length - 1);
if (minEnd === undefined || end < minEnd) {
minEnd = end;
}
}
return minEnd;
return getBufferedEnd(buffers, isLessThan);
}
/**
@@ -138,8 +138,8 @@ describe('getMaxBufferedEnd', () => {
});
describe('getMinBufferedEnd', () => {
it('returns undefined when the buffer list is empty', () => {
expect(getMinBufferedEnd([])).toBeUndefined();
it('returns 0 when the buffer list is empty', () => {
expect(getMinBufferedEnd([])).toBe(0);
});
it('returns the min last-range end across buffers (the reachable/intersection end)', () => {
@@ -154,7 +154,7 @@ describe('getMinBufferedEnd', () => {
expect(getMinBufferedEnd([video, audio])).toBe(600.0);
});
it('returns undefined when any buffer has no buffered ranges (no common reachable point)', () => {
it('skips buffers with no ranges (min across the buffers that have ranges)', () => {
const empty = {
buffered: { length: 0, start: () => 0, end: () => 0 } as TimeRanges,
} as unknown as SourceBuffer;
@@ -162,7 +162,7 @@ describe('getMinBufferedEnd', () => {
buffered: { length: 1, start: () => 0, end: () => 30 } as TimeRanges,
} as unknown as SourceBuffer;
expect(getMinBufferedEnd([empty, buffered])).toBeUndefined();
expect(getMinBufferedEnd([empty, buffered])).toBe(30);
});
it('uses the last range end when a buffer has multiple (gapped) ranges', () => {