fix(spf): bridge media-playlist turnover via PDT, not the target-duration estimate

When a live reload skips the whole window (no media-sequence overlap),
placeOnPreviousTimeline bridged the gap with a target-duration estimate,
which drifts whenever actual segment durations differ from the declared
ceiling. PROGRAM-DATE-TIME is the spec-consistent cross-reload reference and
places the turnover window exactly; use it when present, falling back to the
estimate only when PDT is absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2026-06-25 09:59:45 -07:00
co-authored by Claude Opus 4.8
parent bc617eb1e7
commit 2a1fe85c28
2 changed files with 46 additions and 4 deletions
@@ -110,10 +110,20 @@ function placeOnPreviousTimeline(
anchor = localBase;
} else {
const last = prevSegments[prevSegments.length - 1]!;
anchor = last.startTime + last.duration + (offset - prevSegments.length) * targetDuration;
console.warn(
`[parseMediaPlaylist] full window turnover (offset ${offset} >= ${prevSegments.length}); estimating from previous end.`
);
const newFirst = segments[0]!;
if (last.startDate !== undefined && newFirst.startDate !== undefined) {
// PDT bridges the gap exactly — the spec-consistent cross-reload reference,
// immune to the target-duration over-estimate when actual segment durations
// differ from the declared ceiling. (`startDate` is intrinsic PDT, unaffected
// by the new window's local positioning.)
anchor = last.startTime + (newFirst.startDate - last.startDate);
} else {
anchor = last.startTime + last.duration + (offset - prevSegments.length) * targetDuration;
console.warn(
`[parseMediaPlaylist] full window turnover (offset ${offset} >= ${prevSegments.length}); ` +
'no PDT to bridge — estimating from previous end.'
);
}
}
const shift = anchor - localBase;
@@ -546,6 +546,38 @@ segment11.m4s`;
// anchor = previous end (18) + (offset 10 3) × 6 = 60
expect(next.segments.map((s) => s.startTime)).toEqual([60, 66]);
});
it('bridges a full window turnover exactly via PDT, not the target-duration estimate', () => {
// Actual segment duration (5s) is below the declared TARGETDURATION (6s), so
// the target-duration estimate over-shoots — PDT (the spec-consistent
// cross-reload reference) places the turnover window exactly.
const first = `#EXTM3U
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PROGRAM-DATE-TIME:2024-01-01T00:00:00.000Z
#EXTINF:5.0,
segment0.m4s
#EXTINF:5.0,
segment1.m4s
#EXTINF:5.0,
segment2.m4s`;
const previous = parseMediaPlaylist(first, unresolvedVideo); // [0, 5, 10], seg2 PDT = origin+10
// Turnover (offset 10 ≥ 3), 50s of real elapsed (10 × 5s) — PDT says so.
const reload = `#EXTM3U
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:10
#EXT-X-PROGRAM-DATE-TIME:2024-01-01T00:00:50.000Z
#EXTINF:5.0,
segment10.m4s
#EXTINF:5.0,
segment11.m4s`;
const next = parseMediaPlaylist(reload, previous);
// PDT-exact: seg2 sits at 10 with PDT origin+10; seg10 is origin+50 → 10 + 40 = 50.
// (The target-duration estimate would over-shoot to 15 + (103)×6 = 57.)
expect(next.segments.map((s) => s.startTime)).toEqual([50, 55]);
});
});
describe('EXT-X-PROGRAM-DATE-TIME', () => {