Skip to content

Seek fixes and improvements#1991

Merged
CastagnaIT merged 3 commits intoxbmc:Piersfrom
CastagnaIT:fix_seek_end
Feb 16, 2026
Merged

Seek fixes and improvements#1991
CastagnaIT merged 3 commits intoxbmc:Piersfrom
CastagnaIT:fix_seek_end

Conversation

@CastagnaIT
Copy link
Copy Markdown
Collaborator

@CastagnaIT CastagnaIT commented Feb 3, 2026

Description

Seeking to end can makes video stall and audio continue from previous position, or both a/v return to previous position as if the seek had not occurred,
this can be caused from:

  • PosTime called by VP with a time that exceeds the total media time. For some reason that i dont have understood yet when you seek to the end (e.g. with keyboard) the VP call PosTime by requesting a ms time that can go beyond the total time of duration of media. This can cause some mess such as video segment that cannot be found with FindByPTSOrNext where instead the audio is found due to possible segment misalignment, so video stall and audio continue to play
  • In case of some sort of failing of Session::SeekTime can trigger m_failedSeekTime behavior
  • The stream readers TimeSeek methods was returning wrongly "true" when fails for EOS causing bad behaviors

i reworked a bit things also by removing m_failedSeekTime so now a possible fallback is handled always from the PosTime method and not from DemuxRead callback


The last commit: [DashTree] Allow generate segment beyond period boudaries
concern segment generated from DASH manifest SegmentTemplate,
where in case the last segment have a partial duration it was previously not created
this was causing video seek stall/freeze when you do a video seek that falls into the last missing segment

an complete example to reproduce the video seek freeze problem is on Issue: xbmc/xbmc#27816

Motivation and context

fix #1989

How has this been tested?

Screenshots (if appropriate):

Types of change

  • Bug fix (non-breaking change which fixes an issue)
  • Clean up (non-breaking change which removes non-working, unmaintained functionality)
  • Improvement (non-breaking change which improves existing functionality)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that will cause existing functionality to change)
  • Cosmetic change (non-breaking change that doesn't touch code)
  • None of the above (please explain below)

Checklist:

  • I have read the Contributing document
  • My code follows the Code Guidelines of this project
  • My change requires a change to the Wiki documentation
  • I have updated the documentation accordingly

@CastagnaIT CastagnaIT added Type: Fix non-breaking change which fixes an issue Type: Improvement non-breaking change which improves existing functionality v22 Piers labels Feb 3, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 3, 2026

Code Review

Critical

Potential integer overflow (AdaptiveStream.cpp:1143)
m_endPts * timescale_ext_ can overflow before division. uint64_t * uint32_t can exceed uint64_t max. Use checked arithmetic or reorder: (m_endPts / timescale_int_) * timescale_ext_ to reduce overflow risk.

Potential underflow in getMaxTimeMs (AdaptiveStream.cpp:1145)
If absolutePTSOffset_ > maxPts, subtraction wraps around to large uint64_t value. Add check: if (maxPts < absolutePTSOffset_) return 0; before subtraction.

Must Fix

Uninitialized isError parameter (Session.cpp:1094)
isError is declared but not initialized before passing to SeekTime. If SeekTime returns true without setting it, undefined behavior occurs. Initialize: bool isError{false};

Unused variable ret (Session.cpp:916)
Variable ret is initialized but never used after refactoring. Remove the unused variable declaration.

Medium

Missing m_endPts validation (AdaptiveStream.cpp:1143)
If lastSeg->m_endPts == NO_PTS_VALUE, arithmetic will use max uint64_t. Add check after line 1140: if (lastSeg->m_endPts == NO_PTS_VALUE) return 0;

Low

Inconsistent error logging levels (FragmentedSampleReader.cpp:314)
Changed from LOGERROR to LOGDEBUG for seek failures. While intentional, EOS is expected but other errors aren't. Consider distinguishing: log EOS at DEBUG, other failures at ERROR.

Comment thread src/common/AdaptiveStream.cpp
Comment thread src/common/AdaptiveStream.cpp
Comment thread src/Session.cpp Outdated
Comment thread src/Session.cpp Outdated
Comment thread src/common/AdaptiveStream.cpp
@CastagnaIT CastagnaIT changed the title Fix seek end Seek fixes and improvements Feb 3, 2026
@CastagnaIT
Copy link
Copy Markdown
Collaborator Author

@claude please review

@claude
Copy link
Copy Markdown

claude Bot commented Feb 4, 2026

Claude finished @CastagnaIT's task in 3m 15s —— View job


Code Review

I've reviewed the PR changes. Overall, the implementation successfully addresses the seek-to-end issues by improving error handling and fallback logic. The removal of m_failedSeekTime in favor of direct handling in PosTime is a good architectural improvement. Here are my findings:


Critical Issues

1. Potential Integer Overflow in getMaxTimeMs() (AdaptiveStream.cpp:1144)

Location: src/common/AdaptiveStream.cpp:1144

const uint64_t maxPts =
    (lastSeg->m_endPts * current_rep_->timescale_ext_) / current_rep_->timescale_int_;

The multiplication lastSeg->m_endPts * current_rep_->timescale_ext_ occurs before division. Since m_endPts is uint64_t and timescale_ext_ is likely uint32_t, this can overflow when m_endPts is large.

Impact: Can cause incorrect max time calculations, potentially leading to seek failures.

Note: You've indicated you'll add an assert check. I recommend going further:

  • Better fix: Reorder to (lastSeg->m_endPts / current_rep_->timescale_int_) * current_rep_->timescale_ext_ if precision allows, OR
  • Use 128-bit arithmetic for the intermediate multiplication, OR
  • At minimum, add a runtime check (not just assert, which is debug-only):
    if (lastSeg->m_endPts > UINT64_MAX / current_rep_->timescale_ext_) {
        LOG::LogF(LOGERROR, "PTS overflow detected");
        return 0;
    }

2. Potential Underflow After Overflow (AdaptiveStream.cpp:1148)

Location: src/common/AdaptiveStream.cpp:1148

return (maxPts - absolutePTSOffset_) / 1000;

If absolutePTSOffset_ > maxPts (which could happen due to the overflow above or other reasons), this wraps to a huge uint64_t value.

Impact: Returns incorrect max time, causing unpredictable seek behavior.

Recommendation: The assert at line 1146 helps in debug builds, but add a runtime guard:

if (absolutePTSOffset_ > maxPts) {
    LOG::LogF(LOGWARNING, "Absolute PTS offset (%llu) exceeds max PTS (%llu)", 
              absolutePTSOffset_, maxPts);
    return 0;
}

Must Fix

3. Uninitialized isError Parameter (Session.cpp:1092) - Author Disagrees

Location: src/Session.cpp:1092

bool isError;
if (!SeekTime(GetChapterSeekTime(), isError))

Your response: "not needed"

My analysis: While SeekTime should set isError before returning, the variable is uninitialized. If there's a code path in SeekTime that returns without setting isError, this is undefined behavior.

Recommendation: Even if you're confident all paths set it, initializing to false is defensive programming with zero cost:

bool isError{false};

However, I see that isError is only used inside the if block after SeekTime returns false, and in that case SeekTime does set isError = true at line 1060. So your assessment appears correct for the current code. This is safe as written, though defensive initialization is still a best practice.


Medium Severity

4. m_endPts Validation (AdaptiveStream.cpp:1141) - Author Disagrees

Location: src/common/AdaptiveStream.cpp:1140-1141

const CSegment* lastSeg = current_rep_->Timeline().GetBack();
if (!lastSeg)
    return 0;

Your response: "all segments must have the PTS end set, in case a check should be performed on places where the segments are created"

My analysis: You're right that this should be enforced at segment creation. However, defensive validation here would prevent crashes if that invariant is ever violated. The assert you're adding (line 1146) helps in debug builds but won't catch this in production.

Recommendation: Add a production-safe check:

if (!lastSeg || lastSeg->m_endPts == NO_PTS_VALUE) {
    LOG::LogF(LOGERROR, "Invalid segment state in getMaxTimeMs");
    return 0;
}

5. Early Return vs Continue Logic Change (Session.cpp:1045-1050)

Location: src/Session.cpp:1045-1050

The change from continue to return false is significant:

Old behavior: If one stream failed to seek, skip it and try other streams
New behavior: If any stream fails to seek, abort the entire seek operation

if (!SeekAdStream(*stream, seekSecs, preceeding))
    return false;  // Was: continue

if (!CheckReaderRunning(*stream))
    return false;  // Was: continue

Question: Is this intentional? This makes seeking stricter (all-or-nothing), which could fix the "video stalls, audio continues" issue mentioned in the PR description. However, it also means if one stream fails (e.g., subtitle stream), the entire seek fails.

Impact: This aligns with the new error handling in PosTime that attempts fallback to previous position. This seems intentional and correct for the stated goal.


Minor Issues & Observations

6. FindByNearestPTS Implementation (Segment.cpp:152-162)

The new FindByNearestPTS is well-designed and handles edge cases properly:

  • Finds segment containing PTS
  • Falls back to next segment if PTS is before segment
  • Falls back to last segment if PTS is beyond all segments

Observation: Line 162 returns the last segment even if PTS is way beyond the timeline. This is the fallback behavior for seek-to-end, which matches the PR's intent.

7. Log Level Change is Appropriate (FragmentedSampleReader.cpp:314)

Changing from LOGERROR to LOGDEBUG for seek failures is correct since:

  • EOS (end of stream) is an expected condition
  • The error is now handled gracefully at higher levels
  • Reduces log noise during normal operation

8. Sample Reader Return Value Fix (Multiple files)

Changing from return AP4_ERROR_EOS (which is a non-zero error code, truthy in bool context) to return false is critical and fixes the bug mentioned in the PR description:

The stream readers TimeSeek methods was returning wrongly "true" when fails for EOS causing bad behaviors

This was a significant bug - the methods were returning an error code as a boolean, which evaluated to true, making the caller think the seek succeeded when it actually hit EOS.

9. Unused Variable (Session.cpp:916) - Caught but minor

The bot correctly identified that bool ret is no longer used after refactoring. This should be removed for code cleanliness.


Architectural Improvements

10. Improved Error Handling Flow

The new PosTime implementation (main.cpp:419-451) is much better:

  1. Attempts the requested seek
  2. If it fails without error, attempts to restore previous position
  3. If restoration succeeds, returns false (seek failed but playback continues)
  4. If restoration fails or error occurred, stops playback

This is a significant improvement over the old m_failedSeekTime approach.

11. isError Parameter Design

The new isError output parameter in SeekTime allows callers to distinguish:

  • Success: Returns true
  • Failure (recoverable): Returns false, isError = false
  • Failure (error/EOS): Returns false, isError = true

This three-way distinction enables the smart fallback logic in PosTime.


Testing Recommendations

  1. Seek to end: Verify video and audio both stop/seek correctly when seeking to the very end
  2. Seek beyond end: Test with PTS values exceeding total duration (as mentioned in comments)
  3. Overflow scenarios: Test with very large PTS values to verify the overflow mitigations
  4. Stream misalignment: Test with video/audio segments that are misaligned
  5. Live streams: Verify the live delay calculation with GetMediaDurationMs()

Summary

Strengths:

  • ✅ Addresses the root cause of seek-to-end issues
  • ✅ Removes architectural debt (m_failedSeekTime)
  • ✅ Fixes critical boolean return bug in sample readers
  • ✅ Adds intelligent fallback logic in PosTime
  • ✅ New FindByNearestPTS method handles edge cases well

Required Changes:

  1. Add overflow protection for the multiplication in getMaxTimeMs() (you agreed to assert, but consider runtime check)
  2. Add underflow protection for the subtraction in getMaxTimeMs()
  3. Consider defensive checks for m_endPts == NO_PTS_VALUE (you prefer to enforce at creation, which is also valid)
  4. Remove unused ret variable

Nice to Have:

  • Initialize isError to false for defensive programming (though current code is safe)

Overall, this is a solid improvement to seek handling. The main concerns are around integer overflow edge cases in getMaxTimeMs().


@CastagnaIT
Copy link
Copy Markdown
Collaborator Author

  1. Potential Integer Overflow in getMaxTimeMs() (AdaptiveStream.cpp:1144)

same pattern is used on many other places thats quite safe
no changes will be done on this PR

  1. Potential Underflow After Overflow (AdaptiveStream.cpp:1148)

addressed

  1. Uninitialized isError Parameter (Session.cpp:1092) - Author Disagrees

addressed

  1. m_endPts Validation (AdaptiveStream.cpp:1141) - Author Disagrees

any PTS checks can be implemented when creating segments and not in each place where they are used
this on separate PR not here

  1. Early Return vs Continue Logic Change (Session.cpp:1045-1050)

intentional, PosTime allow a possible seek fallback

-7 is true in boolean type, should be returned false when fails
@CastagnaIT
Copy link
Copy Markdown
Collaborator Author

@kodiai i update PR code please review

@kodiai
Copy link
Copy Markdown

kodiai Bot commented Feb 14, 2026

kodiai response

Reviewed the updated code. The previously reported issues have been properly addressed:

Confirmed fixes:

(1) Underflow protection (AdaptiveStream.cpp:1145-1150) - Now properly checks if absolutePTSOffset_ > maxPts and returns 0 before the subtraction, preventing underflow.

(2) isError initialization (Session.cpp:1116) - The isError variable is now initialized with {false}, preventing undefined behavior.

(3) TimeSeek EOS handling (FragmentedSampleReader.cpp:319) - Now returns false for all failures including EOS, fixing the incorrect "true" return that was causing bad seek behavior.

Code looks good. All critical issues from the previous review have been resolved.

@CastagnaIT CastagnaIT removed the WIP label Feb 14, 2026
@CastagnaIT
Copy link
Copy Markdown
Collaborator Author

@kodiai review last commit added

@kodiai
Copy link
Copy Markdown

kodiai Bot commented Feb 14, 2026

kodiai response

Reviewed commit 5f4bc11 "[DashTree] Allow template segment with partial duration".

Issue found:

(1) Inconsistent segment duration assignment (DASHTree.cpp:1832-1840)

The code calculates a partial duration for the last segment (line 1836) but then assigns seg.m_endPts = time + segDuration using the original segDuration instead of the adjusted duration (line 1840).

// Line 1835-1836: Calculate partial duration
if (remainingDur < duration && !m_isLive)
  duration = remainingDur;

// Line 1838-1840: BUT uses segDuration instead of duration
CSegment seg;
seg.startPTS_ = time;
seg.m_endPts = time + segDuration;  // ← Should be: time + duration

This defeats the purpose of the commit - the last segment will still have the full segDuration in m_endPts, potentially exceeding timeEnd and causing the same "missing last segment" issue the commit is meant to fix.

Fix: Change line 1840 to:

seg.m_endPts = time + duration;

@CastagnaIT CastagnaIT merged commit 5af793c into xbmc:Piers Feb 16, 2026
11 checks passed
@CastagnaIT CastagnaIT deleted the fix_seek_end branch February 16, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Fix non-breaking change which fixes an issue Type: Improvement non-breaking change which improves existing functionality v22 Piers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Seeking to end makes video stall and audio continue from previous position

1 participant