MSE: B-frame tail PTS overshoot in coded-frame processing
MSE Coded Frame Processing is the spec algorithm that determines how incoming encoded frames interact with already-buffered content. Steps 1.14/1.15 handle overlap removal: when a new frame's presentation range overlaps existing buffered content, the overlapping content is erased. B-frames (bidirectional predictive frames) have PTS > DTS because they decode before they display; at the tail of an fMP4 segment, the last sample's trun.sample_duration is a decode-grid placeholder rather than a real presentation duration, so frameEndTime = pts + duration can slightly exceed the next buffered sample's PTS without representing a true editorial overlap. This commit adds a per-track "B-frame tail + within-timeFudgeFactor" heuristic that redirects to a forward-shift path, atomically mutating three coupled structures: the SampleMap's presentation-order submap, its decode-order submap, and TrackBuffer::m_decodeQueue. New helpers createCopyWithAdjustedStartTime, adjustSampleStartTime, and replaceSample implement the shift.
fMP4 tail B-frame: dts=50 pts=80 dur=30 => frameEnd=110
Next buffered content sync: pts=100
Before: erase [50,110) removes pts=100 sync => buffered gap, playback stalls
After: overshoot=10ms < fudge, B-frame tail => shift sync pts 100->110, no gap
Significance
The fix prevents live-stream stalls at ad splice points, but the new code atomically mutates three coupled sample-bookkeeping structures — each a potential source of subtle state and lifetime bugs.
Audit directions
-
SampleMap::replaceSamplekey-ordering validation. The helper performs erase+hint-insert in both submaps without validating that the adjusted sample's key is strictly ordered relative to its neighbors. A zero-duration sample clamping the offset to zero leaves the erase+insert a no-op whilem_bufferedis still mutated — the two structures fall out of sync. -
TrackBuffer::adjustSampleStartTimebuffered-range bookkeeping. Subtracting the original[pts, presentationEndTime)fromm_bufferedthen re-adding the adjusted range assumes the original range is present. If a concurrent step-1.15 cascade has already removed it, the subtraction is a no-op and the re-add inserts a spurious range that shifts the HTMLMediaElement's seekable range. -
Per-track
isPresentationTailflag. Computed before the append loop and consumed inside it. Multi-track segments with interleaved audio/video samples could flag the wrong sample, enabling the shift path to fire on a non-tail video sample and silently corrupt timing for a sample the spec mandates removal of. -
MediaSampleAVFObjC::createCopyWithAdjustedStartTimefor multi-sample CMSampleBuffers. The offset is applied to each sub-sample. If any sub-sample's adjusted start time becomes negative or durations underflow after the offset, AVFoundation may accept the buffer while WebCore'sTimeRangesbookkeeping computes a different range — a split-brain state between the platform decoder and the MSE buffered attribute. Fuzzing crafted fMP4 segments at the B-frame tail acrosschangeType,appendWindowStart, andtimestampOffsetboundaries is the most productive entry point.