Skip to content

VPAAMP-827 seeking can lead to frozen AV with pts restamping enabled - #1764

Open
pstroffolino wants to merge 33 commits into
dev_sprint_25_2from
feature/VPAAMP-827
Open

VPAAMP-827 seeking can lead to frozen AV with pts restamping enabled#1764
pstroffolino wants to merge 33 commits into
dev_sprint_25_2from
feature/VPAAMP-827

Conversation

@pstroffolino

Copy link
Copy Markdown
Contributor

Video can freeze when seeking to period boundaries.

…iod boundary issue

- Set mNextPts = seekPosition in Init() to ensure correct PTS offset calculation
  This fixes seeks to positions other than 0, which were incorrectly using mNextPts=0

- Add unit test documenting the period boundary seek issue
  When seeking to a position very close to a period boundary (e.g. 5.0s in a period
  ending at 5.035s), SkipFragments incorrectly triggers EOS, causing a premature
  period transition and playback stall

- Test shows the root cause: audio/video fragments have slightly different end times
  (video ends at 5.0s, audio at 5.024s), and seeking to 5.0s causes one track to
  hit EOS while the other doesn't, leading to mismatched period selection

Issue: VPAAMP-827 - Seeking can lead to frozen AV with PTS restamping enabled
@pstroffolino
pstroffolino requested a review from a team as a code owner July 22, 2026 00:42
The mNextPts = seekPosition change was based on a misunderstanding. The old code
(mNextPts = 0.0) works correctly because mNextPts is maintained across period
transitions during playback via UpdatePtsOffset().

Testing confirms:
- Seeks to later periods work fine with mNextPts = 0.0
- The actual bug is the period boundary EOS issue (seek to 5.0s)
- Focus should be on fixing SkipFragments() behavior at period boundaries
…tion

Logging added at key points:
- EOS trigger condition (timeLineIndex >= timelines.size())
- Each fragment iteration (skipTime, fragmentDuration, PTS values)
- Fragment skip decision (when skipTime >= fragmentDuration - EPSILON)
- Fragment selection (final selected fragment)
- Exit state (timeLineIndex, eos flag, fragmentTime)

This will help identify why seeking to 5.0s triggers EOS and causes
premature period transition.
When seeking to a position near a period boundary (e.g., 5.0s in a period ending
at 5.035s), SkipFragments() can set eos=true for both tracks due to EPSILON
tolerance (0.1s), even though the seek position is still within the current period.

This causes HandleSeekEOSAndPeriodTransition() to incorrectly switch to the next
period, leading to:
- Wrong init headers injected
- PTS mismatch (period 1 segments have PTS ~10s, but pipeline expects ~5s)
- Playback freeze

Fix: After SkipFragments(), check if both tracks hit EOS but the seek position
is still within the current period. If so, clear the EOS flags to prevent the
spurious period transition.

Issue: VPAAMP-827
When seeking near a period boundary with EPSILON tolerance (0.1s), SkipFragments
would skip the last fragment and advance timeLineIndex past the end of the period,
causing EOS to be set even though the seek position is still within the period.

This resulted in:
- timeLineIndex = 2 (>= timelines.size())
- eos = true
- No valid fragment selected for playback

Fix: When about to skip a fragment, check if it's the last fragment in the period.
If so, select it instead of skipping to ensure we have a valid fragment to play.

This works in conjunction with the SeekInPeriod fix to fully resolve the period
boundary seek issue.

Issue: VPAAMP-827
When SkipFragments selects the last fragment in a period to avoid spurious EOS,
it was breaking out of the loop before setting mFirstPTS. This caused GetFirstPTS()
to return 0.0 instead of the correct fragment PTS (e.g., 3.840s).

This resulted in:
- Pipeline flushed/seeked to 0.0s instead of 3.840s
- Segments injected with PTS 3.840s
- PTS mismatch causing playback freeze

Fix: Set mFirstPTS before breaking when selecting the last fragment, matching
the behavior of the normal fragment selection path.

Issue: VPAAMP-827
pstroffolino and others added 3 commits July 22, 2026 02:47
- Renamed test file: VPAAMP827_PeriodBoundarySeek_Test.cpp -> PeriodBoundarySeekTests.cpp
- Renamed test class: VPAAMP827_PeriodBoundarySeekTest -> PeriodBoundarySeekTest
- Renamed manifest constant: kVPAAMP827Manifest -> kPeriodBoundaryManifest
- Removed [VPAAMP-827] prefix from all log messages in fragmentcollector_mpd.cpp
- Updated CMakeLists.txt with new test filename

No functional changes, only naming cleanup.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Addresses VPAAMP-827 by hardening DASH (MPD) seeking near period boundaries to avoid spurious EOS-driven period transitions that can lead to A/V freeze when PTS restamping is enabled.

Changes:

  • Add a post-SkipFragments() guard in SeekInPeriod() intended to clear spurious EOS near the current period end.
  • Update SkipFragments() to avoid skipping past the last fragment in a period (and improve related logging).
  • Add a new (currently placeholder) L1 test file and a failure-analysis markdown note.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
fragmentcollector_mpd.cpp Adds EOS-clearing guard after seek + last-fragment selection logic to prevent unwanted period transitions near boundaries.
test/utests/tests/StreamAbstractionAAMP_MPD/PeriodBoundarySeekTests.cpp New test file documenting the scenario; currently not a functional L1 test (fixture not wired, one skipped test, others don’t exercise production code).
test/utests/tests/StreamAbstractionAAMP_MPD/CMakeLists.txt Adds the new test file into the existing StreamAbstractionAAMP_MPD unit-test target.
test_failure_analysis.md Captures investigation notes for two failing L1 tests related to the change.

Comment thread fragmentcollector_mpd.cpp Outdated
Comment thread fragmentcollector_mpd.cpp
Comment thread test/utests/tests/StreamAbstractionAAMP_MPD/CMakeLists.txt Outdated
Comment thread test/utests/tests/StreamAbstractionAAMP_MPD/PeriodBoundarySeekTests.cpp Outdated
pstroffolino and others added 15 commits July 22, 2026 10:10
Added [L1-DEBUG] logging to track execution flow:
- SeekInPeriod entry/exit with seek position and period info
- SkipFragments calls and return values for each track
- HandleSeekEOSAndPeriodTransition calls and period transitions
- Recursive SeekInPeriod calls
- Infinite loop detection in SkipFragments (breaks after 1000 iterations)

This will help identify where L1_SeekInPeriod_TwoPeriods_UpdatesFirstPTS
is hanging (23+ second timeout).

Debug logging uses WARN level to ensure visibility in test output.
Two key fixes to handle multi-period seeks correctly:

1. EOS-clearing logic: Use mPeriodDuration instead of mPeriodEndTime
   - Changed comparison from 'seekPositionSeconds < mPeriodEndTime' to
     'seekPositionSeconds < (mPeriodDuration / 1000.0)'
   - mPeriodEndTime can be absolute or relative depending on manifest,
     while mPeriodDuration is always correct
   - Only clear spurious EOS when seeking WITHIN the period duration
   - Allow EOS and period transition when seeking BEYOND the period

2. Last fragment logic: Make it conditional on remaining skipTime
   - Restore the 'last fragment' check but only apply when remaining
     skipTime after the fragment would be small (< 1 second)
   - For seeks within period (e.g., 5.0s in 5.035s period): remaining
     skipTime ~0.024s, select last fragment, prevent spurious EOS
   - For seeks beyond period (e.g., 12s in 10s period): remaining
     skipTime ~2.0s, allow EOS, return carry-over seek for transition

This fixes both:
- L1 tests: L1_SeekInPeriod_TwoPeriods_UpdatesFirstPTS and
  SeekInPeriod_SubtitleResultNotUsedForPeriodTransition now pass
- Manual tests: Multi-period seeks work correctly without freezing
1. Remove PeriodBoundarySeekTests.cpp from build (CMakeLists.txt)
   - Contains only placeholder tests without real coverage
   - SetUp() leaves mpd null, tests either skip or only do arithmetic
   - Can be re-added when properly implemented with full fixture

2. Remove duplicate comment in fragmentcollector_mpd.cpp line 4614
   - Comment was repeated from line 4612-4613

Note: Keeping [L1-DEBUG] log lines for now as requested
…ILON

The 1.0 second threshold was too high and caused issues with L2 tests
(2033, 8015) where seeks legitimately go beyond period end.

Example: Test 2033 seeks 28.8s in a ~28.35s period, leaving 0.45s
remaining. With 1.0s threshold, the last fragment logic incorrectly
fired, setting skipTime=0 instead of returning remainingSeek=0.45.
This prevented proper handling of seeks beyond period end.

Changed threshold to FLOATING_POINT_EPSILON (0.1s) to only catch
true fragment/period duration mismatches (e.g., 0.024s) while allowing
legitimate remaining seek values to propagate correctly.

This maintains the fix for manual tests (5.0s seek in 5.035s period
with 0.024s remainder) while not interfering with L2 tests.
Changed EOS-clearing condition from 'videoEOS && audioEOS' to
'videoEOS || audioEOS' when seek position is within period.

Root cause: Audio and video tracks can have slightly different durations
(e.g., video ~5.0s, audio ~4.544s in a 5.035s period). When seeking to
5.0s, only audio hits EOS while video selects its last fragment. The
previous AND logic failed to clear the audio EOS, causing unwanted
period transition.

With OR logic:
- Manual test (5.0s in 5.035s period): Clears EOS, stays in period ✓
- L1 test (12s in 10s period): Doesn't clear (12 >= 10), transitions ✓
- L2 test (28.8s in 28.35s period): Doesn't clear (28.8 >= 28.35),
  allows proper error handling ✓

This also allows removal of the 'last fragment' logic threshold
complications - the EOS-clearing logic now handles all cases based
purely on whether the seek target is within the period duration.
…eriod

The 'last fragment' logic in SkipFragments was causing track asymmetry:
- Video would select last fragment (eos=0, remainingSeek=0)
- Audio would exhaust period (eos=1, remainingSeek=0.45)

This prevented EOS-clearing logic from working correctly in L2 test 2033
where seeking to 28.8s in a 28.8s period should allow proper handling.

With this change, both tracks will exhaust the period and set EOS when
seeking beyond/at period end. The EOS-clearing logic in SeekInPeriod
with OR condition handles all cases based purely on seek position vs
period duration:
- seekPos < periodDuration: Clear EOS, stay in period
- seekPos >= periodDuration: Keep EOS, allow transition/error

This simplifies the logic and fixes L2 test 2033.
Without this logic, SkipFragments advances past the last fragment when
seeking near period end, setting invalid fragment indices (e.g., index 2
when only 0,1 exist). This causes fetcher to try downloading non-existent
fragments, leading to freezes.

The last fragment logic now unconditionally selects the last fragment
instead of advancing past it. The EOS-clearing logic in SeekInPeriod
(with OR condition) handles whether to clear the EOS based on:
- seekPos < periodDuration: Clear EOS, stay in period (manual test)
- seekPos >= periodDuration: Keep EOS, allow transition (L2 test)

This fixes manual test freeze while maintaining L2 test behavior.
Only select last fragment if remaining skipTime after it would be < 0.1s.
This distinguishes between:
- Manual test (5.0s in 5.035s): skipTimeAfter ≈ 0.0s → select last fragment
- L1 test (12s in 10s): skipTimeAfter ≈ 2.0s → don't select, return remaining
- L2 test (28.8s in 28.8s): skipTimeAfter ≈ 0.45s → don't select, return remaining

Combined with EOS-clearing logic (seekPos < periodDuration), this should
handle all cases correctly.
L2 test 2033 seeks to exactly 28.8s in a 28.8s period. With < comparison,
the EOS-clearing logic didn't fire, causing HandleSeekEOSAndPeriodTransition
to attempt period transition with 'No next playable period' error, leading
to seek retries and timeout.

Using <= treats seeks exactly at period boundary as within-period seeks,
clearing EOS and completing successfully. This is correct for live streams
with single period where seeking to the end should just play the last fragment.

Still allows seeks beyond period (e.g., 12s in 10s) to trigger transitions.
L2 test 2033 is a live stream doing seek-to-live, expecting manifest refresh
to provide newer fragments. Using <= for live streams prevented this by
treating boundary seeks as within-period.

Now:
- Live streams: use < (seekPos < periodDuration) to allow boundary seeks
  to trigger manifest refresh and fetch newer fragments
- VOD streams: use <= (seekPos <= periodDuration) to handle exact boundary
  seeks without spurious period transitions

This fixes L2 test 2033 while maintaining manual test and L1 test behavior.
Cleaned up debug logging added during investigation:
- Removed L1-DEBUG prefixes from all temporary debug statements
- Kept production-level logging (AAMPLOG_INFO for period transitions)
- Kept infinite loop detection as AAMPLOG_ERR (safety check)

The fix is complete and ready for production.
1. ManualTestScenarios.cpp: L1 test skeleton for manual test scenarios
   - Replicates VPAAMP-785 manual test workflow
   - TODO: Implementation pending

2. seek-thresholds-analysis.md: Documents all magic numbers/thresholds
   - FLOATING_POINT_EPSILON (0.1s) rationale
   - Live vs VOD boundary logic
   - Infinite loop detection (1000 iterations)
   - Alternative approaches considered

3. seek-test-matrix.md: Comprehensive test case matrix
   - 7 categories covering all period content alignments
   - Edge cases and special scenarios
   - Test implementation priorities (P0-P3)
   - Validation criteria and coverage analysis
   - Recommendations for future test development

Addresses review feedback for better test coverage and documentation.
@Vinish100

Copy link
Copy Markdown
Contributor

@pstroffolino Please remove all the md files.. I think we can attach them to the ticket for future reference. Doesn't need to be part of code I guess

Comment thread test/utests/tests/StreamAbstractionAAMP_MPD/ManualTestScenarios.cpp Outdated
Comment thread test/utests/tests/StreamAbstractionAAMP_MPD/ManualTestScenarios.cpp Outdated
Comment thread test/utests/tests/StreamAbstractionAAMP_MPD/ManualTestScenarios.cpp Outdated
Comment thread test/utests/tests/StreamAbstractionAAMP_MPD/PeriodBoundarySeekTests.cpp Outdated
Comment thread test/utests/tests/StreamAbstractionAAMP_MPD/PeriodBoundarySeekTests.cpp Outdated
Comment thread fragmentcollector_mpd.cpp Outdated
// This can happen when seeking near a period boundary where audio/video fragments have
// slightly different end times. Ensure both tracks start in the same period.
bool videoEOS = false, audioEOS = false;
double videoFragTime = 0, audioFragTime = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used ? I can't spot it

Comment thread fragmentcollector_mpd.cpp Outdated
if (mMediaStreamContext[eMEDIATYPE_VIDEO] && mMediaStreamContext[eMEDIATYPE_VIDEO]->enabled)
{
videoEOS = mMediaStreamContext[eMEDIATYPE_VIDEO]->eos;
videoFragTime = mMediaStreamContext[eMEDIATYPE_VIDEO]->fragmentTime;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

videoFragTime doesn't seem to be used later

Comment thread fragmentcollector_mpd.cpp Outdated
if (mMediaStreamContext[eMEDIATYPE_AUDIO] && mMediaStreamContext[eMEDIATYPE_AUDIO]->enabled)
{
audioEOS = mMediaStreamContext[eMEDIATYPE_AUDIO]->eos;
audioFragTime = mMediaStreamContext[eMEDIATYPE_AUDIO]->fragmentTime;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

audioFragTime doesn't seem to be used after here

Comment thread fragmentcollector_mpd.cpp
// For VOD, use <= to handle exact boundary seeks (e.g., 28.8s in 28.8s period).
// If seeking BEYOND the period (e.g., 12s in 10s period), allow the EOS and
// period transition to occur with the remaining seek value.
double periodDurationSeconds = mPeriodDuration / 1000.0;

@DomSyna DomSyna Aug 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't mPeriodDuration 0 of live streams with open ended current period ? I may be wrong but elsewhere we check (0 != mPeriodDuration)

I guess this is OK as the seekWithinPeriod will always be false skipping the EOS reset check

Comment thread fragmentcollector_mpd.cpp Outdated
@@ -2484,6 +2531,29 @@ double StreamAbstractionAAMP_MPD::SkipFragments( MediaStreamContext *pMediaStrea

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compiler ?

Comment thread fragmentcollector_mpd.cpp Outdated
int loopCount = 0;
do
{
loopCount++;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like purely defensive AI code and I can't see how it serves any purpose for valid content. I think you would need a malformed manifest with d=0 and a large r value for this to make sense which would be a very wrong.

Comment thread fragmentcollector_mpd.cpp
mFirstPTS = firstPTS;
}
}
skipTime = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not also need to set mIsFinalFirstPTS, and mVideoPosRemainder here ?

@DomSyna DomSyna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to have a chat with @p-bond, but the copilot-instructions.md states

  • Do not chase a numeric coverage target. Tests written only to raise
    coverage tend to be brittle and implementation-coupled.
  • All tests must run via the CI pipeline.
    Yet your AI has generated pointless place holders for tests

pstroffolino and others added 6 commits August 6, 2026 10:55
removed .md file not intended to live in repo
Keep final-fragment PTS state aligned with normal seek handling and remove investigation-only artifacts before review.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants