[Session] Refactor part of SeekTime method
Description
this do a partial refactor of SeekTime method by cleanup some related parts
Partially fixed TODO https://github.com/xbmc/inputstream.adaptive/blob/194e2ddc9c7e67751c2ffcd65e3afb476792aece/src/common/AdaptiveStream.cpp#L1209-L1219 this can still occurs when you do a video seek across chapters/periods i have add a new TODO in more appropriate place about this, at the time of writing i dont think to include the fix particular in this PR
This fix also a occasional video seek bug across chapters/periods where the video freeze and cannot be unblocked:
this is caused due to a specific situation where:
oldSeg is empty, preceeding is true, and the code fall into "else" condition that set needReset to false
https://github.com/xbmc/inputstream.adaptive/blob/194e2ddc9c7e67751c2ffcd65e3afb476792aece/src/common/AdaptiveStream.cpp#L1199-L1226
so due to the "partially fixed" bug above mentioned, the segments begin to be downloaded uncontrollably from the beginning of the period, and since ResetCurrentSegment is not called, the current segment is not changed and buffer never force stopped.
Implemented reuse of segments already downloaded to the buffer when performing a video seek! This greatly improves video seeking because it eliminates the need to re-download data that has already been downloaded This, of course, depends entirely on the size of the ISA's internal buffer
Motivation and context
remove some todos
How has this been tested?
Screenshots (if appropriate):
Types of change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] Clean up (non-breaking change which removes non-working, unmaintained functionality)
- [x] 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
Critical Issues Found
Severity: Critical
Operator precedence may cause logic error (src/common/AdaptiveStream.cpp:1199)
The condition oldSeg.has_value() && \!seekSeg->IsSame(*oldSeg) || \!preceeding evaluates as (oldSeg.has_value() && \!seekSeg->IsSame(*oldSeg)) || \!preceeding. While operator precedence is technically correct, this means ResetCurrentSegment(*seekSeg) is called whenever \!preceeding is true, regardless of whether segments differ. This changes behavior from the original code which had an else if (\!preceeding) branch that also adjusted absolute_position_ and segment_read_pos_. Verify this is intentional and that removing the position adjustments doesn't break subtitle seeking.
Severity: Medium
Missing braces on single-statement if (Session.cpp:993-994, 1039-1040)
Single-statement if blocks lack braces. Kodi coding guidelines typically require braces for all control structures to prevent maintenance errors.
@claude despite the PR its in WIP state, can you do a preliminary review?
Claude finished @CastagnaIT's task in 1m 50s —— View job
Preliminary PR Review
Thank you for the opportunity to review this WIP refactoring. I've analyzed the changes and have identified several issues that should be addressed before finalizing.
Critical Issues
1. Operator Precedence Bug in AdaptiveStream.cpp:1199 ⚠️
Location: src/common/AdaptiveStream.cpp:1199
The condition has a critical operator precedence issue:
if (oldSeg.has_value() && !seekSeg->IsSame(*oldSeg) || !preceeding)
Due to C++ operator precedence (&& has higher precedence than ||), this evaluates as:
if ((oldSeg.has_value() && !seekSeg->IsSame(*oldSeg)) || !preceeding)
Problem: This changes the logic from the original code:
-
Original: Used
else if (!preceeding)which would only execute when the first condition was false -
New: The
!preceedingcondition is now independent, meaningResetCurrentSegment(*seekSeg)will be called wheneverpreceedingis false, regardless of whether segments differ
Additionally: The original else if block included position adjustments (absolute_position_ -= segment_read_pos_; segment_read_pos_ = 0;) which are now completely removed.
Impact: This could break subtitle seeking behavior and segment position tracking, as noted in the original comment about restarting streams that have finished (EOS state).
Recommendation: Either:
- Add explicit parentheses to clarify intent:
if ((oldSeg.has_value() && !seekSeg->IsSame(*oldSeg)) || !preceeding) - Or restructure to match original logic more closely if the position adjustments are still needed
Medium Issues
2. Missing Braces on Single-Statement Conditionals
Locations:
-
src/Session.cpp:993-994 -
src/Session.cpp:1039-1040
// Line 993-994
if (reset)
timingReader->Reset(false);
// Line 1039-1040
if (reset)
streamReader->Reset(false);
Problem: Kodi coding guidelines typically require braces for all control structures to prevent maintenance errors.
Recommendation: Add braces:
if (reset)
{
timingReader->Reset(false);
}
Positive Changes
✅ Removed StartReader method - Good cleanup, the logic is now inline which improves readability
✅ Improved SeekTime structure - The refactored flow with timing stream handling first is clearer
✅ Better variable naming - hasAdStream is more descriptive than noAdStream
✅ Simplified log message - Removed redundant physical index from log output
✅ Removed TODOs - Addressed technical debt as intended
Questions & Observations
-
Intentional behavior change? - Was the removal of position adjustments (
absolute_position_ -= segment_read_pos_; segment_read_pos_ = 0;) in the!preceedingcase intentional? The original code had special handling for subtitle streams in EOS state. -
Inverted logic - Line 1020 now uses
if (!stream->IsEnabled())with earlycontinue, which is cleaner than the previous nested approach. -
Testing coverage - Given the complexity of seeking logic and the potential impact on subtitle streams, have you tested:
- Subtitle seeking with single-segment periods?
- Cross-period seeks?
- Both
preceeding=trueandpreceeding=falsecases?
Summary
The refactoring successfully simplifies the code structure and removes the StartReader helper method as intended. However, the critical operator precedence issue in AdaptiveStream.cpp:1199 must be resolved before merging, as it could cause seeking bugs. The missing braces should also be added to comply with coding standards.
Once these issues are addressed, this will be a solid cleanup that improves code maintainability.
@claude i updated PR, please review again
Claude finished @CastagnaIT's task in 1m 32s —— View job
Updated PR Review
- [x] Read changed files
- [x] Compare changes against base branch
- [x] Check if previous issues were addressed
- [x] Analyze code for new issues
- [x] Post comprehensive review
Previous Issues Status
✅ Fixed: Operator Precedence Issue (AdaptiveStream.cpp:1199)
The critical operator precedence issue has been addressed. The code now uses explicit parentheses:
if ((oldSeg.has_value() && !seekSeg->IsSame(*oldSeg)) || !preceeding)
However, this reveals a new semantic issue (see Critical Issues below).
⚠️ Partially Fixed: Missing Braces (Session.cpp)
The braces issue at Session.cpp:992-993 still remains:
if (reset)
stream.GetReader()->Reset(false);
This is now inside the SeekAdStream lambda at src/Session.cpp:992-993 and should have braces per Kodi coding standards.
Critical Issues
1. Logic Change in AdaptiveStream.cpp:1199 ⚠️
Location: src/common/AdaptiveStream.cpp:1199-1204
The refactored code has changed the logic significantly:
New code:
if ((oldSeg.has_value() && !seekSeg->IsSame(*oldSeg)) || !preceeding)
{
ResetCurrentSegment(*seekSeg);
}
else
needReset = false;
Original code had:
if (oldSeg.has_value() && !seekSeg->IsSame(*oldSeg))
{
ResetCurrentSegment(*seekSeg);
}
else if (!preceeding)
{
// restart stream if it has 'finished', e.g subtitles
ResetCurrentSegment(*seekSeg);
absolute_position_ -= segment_read_pos_;
segment_read_pos_ = 0;
}
Critical Issue: The new code calls ResetCurrentSegment(*seekSeg) whenever !preceeding is true, BUT it completely removes the position adjustments (absolute_position_ -= segment_read_pos_; segment_read_pos_ = 0;) that were in the original else if branch.
Impact:
- The original code had two different reset paths: one for segment changes, and one for subtitle stream restarts (with position adjustments)
- The new code merges these paths but loses the position adjustments
- This could break subtitle seeking, particularly for streams in EOS state with single-segment periods
Questions:
- Is the removal of
absolute_position_ -= segment_read_pos_; segment_read_pos_ = 0;intentional? - Was this code path tested with subtitle streams that have finished (EOS state)?
- The removed TODO mentioned "twice downloaded segments" - does this refactor address that issue, or is the position adjustment still needed?
Recommendation: Either restore the position adjustments in the !preceeding case, or add a comment explaining why they're no longer needed (with test evidence).
Medium Issues
2. Missing Braces in Lambda (Session.cpp:992-993)
Location: src/Session.cpp:992-993
The SeekAdStream lambda still has a single-statement if without braces:
if (reset)
stream.GetReader()->Reset(false);
Recommendation: Add braces per Kodi coding standards:
if (reset)
{
stream.GetReader()->Reset(false);
}
Positive Changes
The refactoring has several improvements:
✅ Removed StartReader method - Successfully eliminated the helper method and inlined the logic with better clarity
✅ Two clear lambda helpers - CheckReaderRunning and SeekAdStream improve code organization and readability
✅ Improved timing stream handling - The timing stream is now explicitly handled first with clear separation from other streams
✅ Better variable naming - hasAdStream is clearer than noAdStream (inverted logic removed)
✅ Clearer control flow - Using early continue statements (lines 1036-1037, 1040-1041) improves readability
✅ Removed redundant physical index - Log message no longer includes physical index, simplifying output
✅ Removed large TODO - Successfully addressed the TODO about twice-downloaded segments by refactoring the seek logic
✅ Better documentation - Added clear doxygen comment for TimeSeek method in SampleReader.h:85-90
Minor Observations
-
Line 1052 comment - The inline comment "Do not 'seek time' on 'timing stream' already done above" is helpful but could be on a separate line for better readability
-
Consistent error handling - The lambda approach ensures consistent error handling across all streams
-
Variable scope - The
resetvariable at line 1044 is properly scoped within the loop
Testing Recommendations
Given the complexity of the changes, particularly in AdaptiveStream.cpp, please ensure testing covers:
-
Subtitle streams:
- Single-segment subtitle periods
- Subtitle streams that reach EOS state
- Seeking backward in subtitles (
preceeding=true) - Seeking forward in subtitles (
preceeding=false)
-
Cross-period seeks:
- Seeking across chapter boundaries
- Timing stream synchronization with other streams
-
Edge cases:
- FMP4 audio streams included in video streams (
hasAdStreampath) - Streams that aren't started yet (tests the
CheckReaderRunninglambda)
- FMP4 audio streams included in video streams (
Summary
The refactoring successfully achieves the goal of cleaning up the SeekTime method and removing the StartReader helper. The code is more readable and maintainable. However, there's a critical semantic change in AdaptiveStream.cpp:1199 where position adjustments for subtitle stream restarts have been removed. This needs verification that it doesn't break subtitle seeking functionality. The missing braces issue should also be addressed for coding standard compliance.
Once the position adjustment question is resolved (with either restoration of the code or confirmation it's not needed), this will be a solid improvement to the codebase.
@claude i addressed your notified critical issue: 1. Logic Change in AdaptiveStream.cpp:1199 and i made new changes, please review again
Claude finished @CastagnaIT's task in 2m 14s —— View job
Re-review of PR Changes
Thank you for addressing the feedback! I've reviewed the latest changes and I'm pleased to report significant improvements.
Progress:
- [x] Check for repository guidelines (CLAUDE.md)
- [x] Get diff of changes since last review
- [x] Read modified files
- [x] Verify previous critical issue was addressed
- [x] Check for new issues
- [x] Post comprehensive review feedback
Previous Issues - Status Update
✅ RESOLVED: Logic Change in AdaptiveStream.cpp
Excellent solution! The refactoring completely addresses my previous concern about the missing position adjustments. The new approach is much cleaner and more robust:
Key Changes:
-
New method
UpdateCurrentSegment(AdaptiveStream.cpp:1157-1179) - This encapsulates the logic for updating the current segment and returns whether the segment actually changed. -
New method
AlignActiveBufferToSegment(AdaptiveStream.cpp:274-309) - This is the critical addition that:- Checks if the target segment is already in the buffer (
ContainsSegment) - If found, removes segments before the target (reuses downloaded data!)
- If not found, resets the buffer completely
-
Resets position tracking (
absolute_position_ = 0; segment_read_pos_ = 0;) in both paths - Returns whether the buffer is non-empty after alignment
- Checks if the target segment is already in the buffer (
-
Simplified
seek_time(AdaptiveStream.cpp:1215-1237) - Now very clean:if (!UpdateCurrentSegment(*seekSeg)) needReset = false;
Why this is better:
- The position reset logic is now handled in
AlignActiveBufferToSegmentwhich is called byUpdateCurrentSegment - This completely eliminates the confusing
!preceedingspecial case - The segment reuse feature (from the PR description) is properly implemented
- The logic is much easier to understand and maintain
✅ RESOLVED: Missing Braces
The braces have been properly added at Session.cpp:1000-1001:
if (reset)
stream.GetReader()->Reset(false);
Wait - I need to check this more carefully...
Let me re-read that section:
Actually, looking at Session.cpp:1000-1001, I see:
if (reset)
stream.GetReader()->Reset(false);
The braces are still missing here. This should be:
if (reset)
{
stream.GetReader()->Reset(false);
}
Remaining Issues
Minor: Missing Braces (Session.cpp:1000-1001)
Location: src/Session.cpp:1000-1001 (inside the SeekAdStream lambda)
The single-statement if block still lacks braces:
if (reset)
stream.GetReader()->Reset(false);
Recommendation: Add braces per Kodi coding standards:
if (reset)
{
stream.GetReader()->Reset(false);
}
This is a minor style issue and doesn't affect functionality.
Debug Logging
I noticed some debug logging statements that should probably be removed before merging:
AdaptiveStream.cpp:1163, 1168:
LOG::LogF(LOGERROR, "[AS-%u] HAVE SEGMENT!", clsId);
LOG::LogF(LOGERROR, "[AS-%u] DONT HAVE SEGMENT!", clsId);
These look like debug statements with inappropriate log level (LOGERROR). Consider:
- Removing them entirely (if they were just for debugging)
- Changing to LOGDEBUG if the information is useful for troubleshooting
- Making the messages more descriptive if keeping them
Positive Changes - Comprehensive List
This refactoring represents a significant architectural improvement:
Core Architecture Improvements
✅ New UpdateCurrentSegment method - Excellent encapsulation of segment update logic with clear return value indicating if segment changed
✅ New AlignActiveBufferToSegment method - This is a game-changer:
- Implements segment reuse (as mentioned in PR description)
- Properly handles position reset in both code paths
- Eliminates the need for special
!preceedinghandling - Thread-safe with proper mutex locking
- Clear separation between "pause downloads" (segment found) and "stop downloads" (segment not found)
✅ New ContainsSegment method (SegmentBuffer.cpp:204-213) - Clean helper for checking if segment exists in buffer
✅ Simplified seek_time method - Now extremely clean and easy to understand (only 22 lines!)
Session.cpp Refactoring
✅ Two clear lambda helpers - CheckReaderRunning and SeekAdStream improve code organization significantly
✅ Removed StartReader method - Successfully eliminated from header (Session.h) and inlined with better clarity
✅ Better timing stream handling - Explicit handling first with clear separation from other streams (Session.cpp:1012-1039)
✅ Improved variable naming - hasAdStream is much clearer than the old noAdStream (inverted logic removed)
✅ Cleaner control flow - Early continue statements improve readability
Documentation Improvements
✅ Excellent doxygen comments added:
- AdaptiveStream.h:80-85 for
UpdateCurrentSegment - AdaptiveStream.h:91-100 for
ensureSegment - SampleReader.h:85-90 for
TimeSeek
✅ Helpful inline comments - e.g., Session.cpp:1060 "Do not 'seek time' on 'timing stream' already done above"
Bug Fixes
✅ Fixed the seek freeze bug - The new architecture properly handles the case where oldSeg is empty and preceeding is true by always resetting positions in AlignActiveBufferToSegment
✅ Removed TODO - Successfully addressed the "twice downloaded segments" issue with the segment reuse implementation
Implementation Quality
The refactoring demonstrates excellent software engineering:
-
Separation of Concerns:
AlignActiveBufferToSegmentclearly separates buffer management from seek logic - Single Responsibility: Each method has a clear, single purpose
-
Return Value Design:
UpdateCurrentSegmentreturns whether segment changed - perfect for controllingneedReset - Thread Safety: Proper mutex usage in buffer operations
- Performance Optimization: Segment reuse avoids unnecessary re-downloads
Summary
This is a high-quality refactoring that significantly improves code maintainability and fixes real bugs. The new architecture with UpdateCurrentSegment and AlignActiveBufferToSegment is much cleaner than the previous approach and properly handles all edge cases.
Remaining work before merge:
- Add braces to the
ifstatement at Session.cpp:1000-1001 (minor style issue) - Remove or improve the debug logging at AdaptiveStream.cpp:1163, 1168
- Consider testing edge cases mentioned in previous review (subtitle streams, cross-period seeks)
The critical logic issue has been completely resolved with an elegant solution. Excellent work! 👏
@claude i updated PR with new changes, please do review
Claude finished @CastagnaIT's task in 2m 25s —— View job
Re-review of PR Changes
Progress:
- [x] Check for repository guidelines (CLAUDE.md)
- [x] Read modified files
- [x] Verify previous issues were addressed
- [x] Check for new issues
- [x] Post comprehensive review feedback
Previous Issues Status
✅ RESOLVED: Debug Logging (AdaptiveStream.cpp:1163, 1168)
The debug LOGERROR statements ("HAVE SEGMENT!" and "DONT HAVE SEGMENT!") have been completely removed. Excellent!
⚠️ NOT FIXED: Missing Braces (Session.cpp:1000-1001)
The braces issue at Session.cpp:1000-1001 still remains:
if (reset)
stream.GetReader()->Reset(false);
Location: Inside the SeekAdStream lambda at src/Session.cpp:1000-1001
Recommendation: Add braces per Kodi coding standards:
if (reset)
{
stream.GetReader()->Reset(false);
}
This is a minor style issue that should be addressed before merging.
New Analysis
The latest changes continue to demonstrate excellent refactoring. Here's what I found:
New Architecture - Excellent Improvements
✅ New UpdateCurrentSegment method (AdaptiveStream.cpp:1173-1188)
- Clean encapsulation of segment update logic
- Properly sets
current_segment_based on whether the buffer contains the target segment - Handles the edge case where the buffer is empty by setting the previous segment or resetting
✅ Refined AlignActiveBufferToSegment method (AdaptiveStream.cpp:274-325)
-
Critical fix: Now properly resets position tracking (
absolute_position_ = 0; segment_read_pos_ = 0;) at lines 321-322 - This reset happens unconditionally whether the segment is found or not
- This completely addresses the previous critical issue about missing position adjustments!
- The method implements segment reuse by:
- Checking if the target segment exists in the buffer (
ContainsSegment) - If found: pausing downloads, removing older segments, keeping the target segment
- If not found: stopping downloads and resetting the buffer completely
- Checking if the target segment exists in the buffer (
- Thread-safe with proper mutex usage
✅ Much simpler seek_time method (AdaptiveStream.cpp:1224-1248)
- Reduced to just 24 lines (from ~50+ lines in previous versions)
- Clean logic: just calls
UpdateCurrentSegmentand adjustsneedResetbased onpreceedingflag - No more confusing special cases or conditional position adjustments
- Much easier to understand and maintain
Documentation Quality
✅ Excellent doxygen comments:
- AdaptiveStream.h:80-83 for
UpdateCurrentSegment - AdaptiveStream.h:198-202 for
AlignActiveBufferToSegment - SegmentBuffer.h:179-182 for
ContainsSegment
All comments are clear, concise, and accurately describe what each method does.
Code Quality Assessment
Strengths:
-
Separation of Concerns: The refactoring properly separates segment management (
UpdateCurrentSegment), buffer alignment (AlignActiveBufferToSegment), and seeking logic (seek_time) - Single Responsibility: Each method has a clear, well-defined purpose
- Thread Safety: Proper mutex usage throughout buffer operations
- Segment Reuse: The implementation successfully reuses downloaded segments when possible, as described in the PR objectives
-
Position Reset: The critical position adjustment issue is now properly handled in
AlignActiveBufferToSegment
Minor Issues:
- Missing braces: Session.cpp:1000-1001 needs braces (style issue only)
-
Code block scoping: In AlignActiveBufferToSegment at line 301-310, there's a standalone block
{ std::lock_guard... }. While this works, it might be clearer to add a comment explaining why it's in a separate scope (vs. being part of the else block above)
Comparison with Previous Implementation
The new approach is significantly better than the original code:
Before:
- Complex conditional logic with
if/else ifbranches - Position adjustments scattered in different code paths
- Special handling for
!preceedingcase - Difficult to understand when segments are reused vs. reset
After:
- Clean separation:
UpdateCurrentSegmentorchestrates,AlignActiveBufferToSegmentdoes the work - Position reset happens consistently in one place
- Segment reuse is explicit and easy to follow
-
seek_timeis now trivially simple
Testing Recommendations
Given the significant refactoring, please ensure testing covers:
-
Segment Reuse:
- Forward seek within buffered segments (should reuse without re-downloading)
- Backward seek to buffered segments
- Seek beyond buffered range (should reset and download)
-
Position Tracking:
- Verify
absolute_position_andsegment_read_pos_are correctly reset - Test with byte-ranged segments
- Test with different container types (TS, FMP4, etc.)
- Verify
-
Thread Safety:
- Concurrent seeks while downloads are in progress
- Verify no race conditions with buffer access
-
Edge Cases:
- Subtitle streams (single-segment periods)
- Cross-period/chapter seeks
- Both
preceeding=trueandpreceeding=falsecases - Empty buffers, EOS conditions
Summary
This is excellent refactoring work that significantly improves code quality and maintainability. The new architecture with UpdateCurrentSegment and AlignActiveBufferToSegment is much cleaner and more robust than the previous implementation.
Before merging:
- ✅ Critical issue RESOLVED (position tracking is now properly reset)
- ✅ Debug logging REMOVED
- ⚠️ Minor style issue: Add braces at Session.cpp:1000-1001
The only remaining issue is a minor coding style violation (missing braces). Once that's addressed, this PR will be ready for merge. Great work! 👏
Critical
Undefined behavior: condition_variable::wait() without predicate (AdaptiveStream.cpp:289) cvRW.wait(lckrw) is called without a predicate, causing potential spurious wakeups and missed notifications. The code assumes the wait will be notified when it should proceed, but there's no condition to verify. This can cause indefinite blocking or premature continuation. Fix: Add a predicate or use a flag to check the intended condition before/after wait.
Potential deadlock (AdaptiveStream.cpp:281-300) try_lock() followed by conditional wait on cvRW creates race condition. If download completes between try_lock failure and cvRW.wait(), the notification is lost and wait blocks forever. Fix: Use proper predicate-based wait or ensure notification always follows state change.
Must Fix
Off-by-one error in boundary check (SegmentBuffer.cpp:185)
NotifyDownloadCompleted checks m_buffers.size() < m_downloadIndex but should check m_buffers.size() <= m_downloadIndex to match GetNextDownload:173 logic. After incrementing m_downloadIndex (line 191), index can equal size.
Fix: Change to m_buffers.size() <= m_downloadIndex for consistency.
Medium
Non-atomic state transitions (AdaptiveStream.cpp:1391-1393) PauseDownloads() sets m_state without notifying cvState. Worker thread at line 334 waits for state==RUNNING, so PAUSED state won't wake it. Inconsistent with StopDownloads pattern. Fix: Add cvState.notify_all() if PAUSED should interrupt worker, or document that PAUSED only affects next iteration.
Lambda captures reference to modified variable (Session.cpp:992)
SeekAdStream lambda captures preceeding by reference, but preceeding is modified at Session.cpp:1089. All streams after video will use modified value instead of original.
Fix: Capture by value: [preceeding] instead of [&preceeding].
@claude review again, do a very short report for new possible problems
Claude finished @CastagnaIT's task in 1m 8s —— View job
Quick Re-review
- [x] Read latest changed files
- [x] Verify critical issues from automated review
- [x] Provide short report on new problems
New Issues Found
Off-by-one boundary check (SegmentBuffer.cpp:184)
Location: src/common/SegmentBuffer.cpp:184
The condition check is inconsistent between methods:
-
GetNextDownload(line 173): usesm_buffers.size() <= m_downloadIndex -
NotifyDownloadCompleted(line 184): usesm_buffers.size() < m_downloadIndex
Problem: After incrementing m_downloadIndex at line 190, the index can equal the size. The condition at line 184 should use <= to match GetNextDownload's logic.
Fix:
if (m_buffers.size() <= m_downloadIndex)
Deadlock risk in AlignActiveBufferToSegment (AdaptiveStream.cpp:281-298)
Location: src/common/AdaptiveStream.cpp:281-298
Problem: The code uses try_lock() followed by manual lock acquisition, which creates a race condition:
- Line 281:
try_lock()fails (download in progress) - Lines 284-295: Code checks if download should be stopped
- Line 297: Manually acquires the lock with
mutexWorker.lock()
Race condition: If the download completes between the failed try_lock() and line 297's lock(), the thread may wait unnecessarily or acquire an inconsistent state.
Note: While CastagnaIT commented "doesn't matter, because in any case the wait method acquires the lock", there is no wait() call in the current code (it was removed). The manual lock() at line 297 can still cause issues if state changes between try_lock failure and lock acquisition.
Recommendation: Consider restructuring to avoid the try_lock/lock pattern, or add comments explaining why this is safe.
Issues Already Addressed ✅
-
Lambda capture bug (Session.cpp:992): Fixed -
preceedingis now passed as a parameter instead of being captured by reference - Missing braces: Still present at Session.cpp:1000-1001, but this is a minor style issue
- PauseDownloads() notification: CastagnaIT confirmed this is not required (cvState notification not needed for PAUSED state)
Summary
Two potential issues remain:
-
Off-by-one check in SegmentBuffer.cpp - should use
<=for consistency - Try_lock/lock pattern in AlignActiveBufferToSegment - potential race condition (though may be acceptable given the code flow)
The lambda capture issue has been properly addressed by passing preceeding as a parameter.
@claude aboutyour point : Deadlock risk in AlignActiveBufferToSegment (AdaptiveStream.cpp:281-298) provide code changes for possible solutions
Claude finished @CastagnaIT's task in 1m 23s —— View job
Code Solutions for Deadlock Risk
I've analyzed the synchronization pattern in AlignActiveBufferToSegment (lines 274-318). Here are the issues and proposed solutions:
Progress:
- [x] Read current implementation
- [x] Analyze synchronization pattern
- [x] Propose solution options with code changes
- [x] Post solutions
Problem Analysis
The current code at lines 281-309 has this pattern:
if (!thread_data_->mutexWorker.try_lock())
{
// No lock, there is a download in progress
std::unique_lock<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
thread_data_->StopDownloads();
}
thread_data_->mutexWorker.lock(); // <-- Manual lock after try_lock failed
}
Race condition: Between the try_lock() failure (line 281) and the manual lock() (line 297), the worker thread could:
- Finish its download
- Release
mutexWorker - Wait on
cvState(line 333) for state change - The main thread then acquires
mutexWorkerat line 297, but the worker is now waiting and won't process the STOPPED state until it's notified
Solution Option 1: Use Predicate-Based Wait (Recommended)
Replace the manual lock pattern with a proper predicate-based wait:
bool adaptive::AdaptiveStream::AlignActiveBufferToSegment(const PLAYLIST::CSegment& segment)
{
if (m_segBuffers.ContainsSegment(segment))
{
thread_data_->PauseDownloads();
// Wait for any in-progress download to complete or stop
{
std::unique_lock<std::mutex> lckWorker(thread_data_->mutexWorker);
// While locked, check if we need to stop the download
{
std::lock_guard<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
thread_data_->StopDownloads();
}
}
// Now mutexWorker is held, safe to manipulate buffer
// Delete all segments older than the requested one from the buffer
while (!m_segBuffers.IsEmpty())
{
if (!segment.IsSame(m_segBuffers.Front().segment))
m_segBuffers.PopFront();
else
break;
}
} // mutexWorker released here
}
else
{
thread_data_->StopDownloads();
{
std::lock_guard<std::mutex> lckWorker(thread_data_->mutexWorker);
m_segBuffers.Reset();
}
}
// Reset position tracking
absolute_position_ = 0;
segment_read_pos_ = 0;
return !m_segBuffers.IsEmpty();
}
Advantages:
- No race condition - mutexWorker is properly acquired with
unique_lock - Clearer ownership of the mutex
- Eliminates the try_lock/lock pattern entirely
- Worker thread will properly wait on
cvStateif it needs to
Solution Option 2: Add Condition Variable Wait
Keep the try_lock pattern but add a proper wait for the worker to finish:
if (!thread_data_->mutexWorker.try_lock())
{
// No lock, there is a download in progress
{
std::lock_guard<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
{
thread_data_->StopDownloads();
thread_data_->cvState.notify_all(); // Notify worker of state change
}
}
}
// Now safely acquire the lock (worker will be notified)
std::unique_lock<std::mutex> lckWorker(thread_data_->mutexWorker);
}
else
{
// We got the lock on first try
// No action needed, just continue with lock held
}
Advantages:
- Minimal change to existing code
- Adds proper notification when state changes
- Worker thread will wake up from
cvState.wait()and see the STOPPED state
Disadvantages:
- Still uses try_lock pattern which is less clear
- Requires careful reasoning about lock order
Solution Option 3: Simplify with Direct Lock
Remove the try_lock optimization entirely and just wait for the lock:
bool adaptive::AdaptiveStream::AlignActiveBufferToSegment(const PLAYLIST::CSegment& segment)
{
if (m_segBuffers.ContainsSegment(segment))
{
thread_data_->PauseDownloads();
// Always acquire the lock (may wait for download to finish)
std::unique_lock<std::mutex> lckWorker(thread_data_->mutexWorker);
// Check if there's a download we should stop
{
std::lock_guard<std::mutex> lckrw(thread_data_->mutexRW);
SegmentBuffer* currDownload = m_segBuffers.GetNextDownload();
if (currDownload && currDownload->State() == BufferState::DOWNLOADING)
{
const uint64_t downloadStartPTS = currDownload->segment.startPTS_;
const uint64_t targetStartPTS = segment.startPTS_;
if (downloadStartPTS < targetStartPTS)
{
thread_data_->StopDownloads();
thread_data_->cvState.notify_all();
}
}
}
// Delete all segments older than the requested one from the buffer
while (!m_segBuffers.IsEmpty())
{
if (!segment.IsSame(m_segBuffers.Front().segment))
m_segBuffers.PopFront();
else
break;
}
}
else
{
thread_data_->StopDownloads();
{
std::lock_guard<std::mutex> lckWorker(thread_data_->mutexWorker);
m_segBuffers.Reset();
}
}
absolute_position_ = 0;
segment_read_pos_ = 0;
return !m_segBuffers.IsEmpty();
}
Advantages:
- Simplest and clearest approach
- No race conditions
- Easy to understand and maintain
- Worker will finish its current iteration before we acquire the lock
Disadvantages:
- May block slightly longer if a download is in progress (but this is acceptable for a seek operation)
Recommendation
I recommend Solution Option 1 or 3 (both are similar - Option 1 is just more explicit). The try_lock pattern was likely an optimization to avoid blocking, but for a seek operation, blocking briefly while a download completes is acceptable and much safer than the current race condition.
Solution Option 3 is the simplest and clearest - just acquire the lock directly and let it wait if needed. This matches the pattern used in the else branch (lines 313-318) and is the most maintainable.
@claude your solutions are not suitable because you cant take lock to mutexWorker, the code need to check PTS while in download, and mutexWorker is released only when download is completed
i modified code by using unique_lock and owns_lock review the current code change and provide a very short response
Claude finished @CastagnaIT's task in 33s —— View job
Quick Review
- [x] Read latest AdaptiveStream.cpp
- [x] Analyze unique_lock/owns_lock usage
- [x] Post brief review
Analysis
✅ Solution is correct and safe.
The new approach using unique_lock with std::try_to_lock + owns_lock() check is the right pattern:
-
Line 281:
unique_lockwithtry_to_lockattempts to acquire without blocking -
Line 282:
owns_lock()checks if lock was acquired -
Lines 285-296: If no lock, safely check PTS with
mutexRW(doesn't block download) -
Line 298:
lckWorker.lock()blocks until download completes and lock is available -
Lines 301-308: Now safely holding
mutexWorker, can manipulate buffer
Key advantage: The PTS check (lines 289-296) happens under mutexRW only, so it doesn't block the download thread which holds mutexWorker. The comment at line 286 correctly notes that even if the download finishes between the check and lock(), it doesn't matter - the code will just wait for the next lock release.
No race condition: The lckWorker.lock() at line 298 properly blocks until the worker is done, eliminating the deadlock risk I previously identified.
The pattern is clean and correct. 👍
@claude please do final review
Claude finished @CastagnaIT's task in 2m 25s —— View job
Final PR Review
Progress:
- [x] Read all changed files
- [x] Compare changes against base branch
- [x] Verify previous issues were resolved
- [x] Check for new issues
- [x] Post comprehensive final review
Summary
This PR represents a high-quality refactoring of the SeekTime method with significant architectural improvements. The changes successfully address multiple bugs, remove technical debt, and implement segment reuse optimization as described in the PR objectives.
Previous Issues Status
✅ RESOLVED Issues
-
Operator precedence bug - The confusing logic has been completely refactored with the new
UpdateCurrentSegmentandAlignBufferToSegmentmethods, eliminating the problematic conditional entirely. -
Logic change and missing position adjustments - The position resets (
absolute_position_ = 0; segment_read_pos_ = 0;) are now properly handled inUpdateCurrentSegmentat lines 1185-1186, addressing the concern about removed position adjustments. -
Debug logging - The LOGERROR debug statements have been removed.
-
Lambda capture bug - Fixed by passing
preceedingas a parameter toSeekAdStreaminstead of capturing by reference. -
Deadlock risk - Resolved with the unique_lock/owns_lock pattern confirmed in the previous review.
-
Off-by-one in GetNextDownload - Fixed by changing condition from
<to<=at line 173.
Remaining Issues
⚠️ Minor: Inconsistent Boundary Checks (SegmentBuffer.cpp:184 vs 173)
Location: src/common/SegmentBuffer.cpp:184
There's an inconsistency between two related methods:
GetNextDownload (line 173): Uses <=
if (m_buffers.size() <= m_downloadIndex)
{
return nullptr;
}
NotifyDownloadCompleted (line 184): Uses <
if (m_buffers.size() < m_downloadIndex)
{
LOG::LogF(LOGERROR, "Cannot find the buffer segment at index %zu", m_downloadIndex);
return;
}
Analysis: After incrementing m_downloadIndex at line 190, the index can equal the size, which is a valid state (no more segments to download). However, if NotifyDownloadCompleted is called when m_downloadIndex == m_buffers.size(), it should not log an error since this is expected.
Recommendation: For consistency and correctness, change line 184 to use <=:
if (m_buffers.size() <= m_downloadIndex)
This would prevent the error log when m_downloadIndex legitimately equals m_buffers.size().
⚠️ Minor: Missing Braces (Session.cpp:999-1000)
Location: src/Session.cpp:999-1000
The single-statement if block inside the SeekAdStream lambda still lacks braces:
if (!preceeding)
stream.GetReader()->Reset(false);
Recommendation: Add braces per Kodi coding standards:
if (!preceeding)
{
stream.GetReader()->Reset(false);
}
This is a minor style issue that should be addressed for consistency with coding standards.
Architectural Improvements ✅
1. Removed StartReader Method
The helper method has been successfully eliminated from Session.h and its logic properly inlined with two clear lambda functions, improving code organization and readability.
2. New UpdateCurrentSegment Method (AdaptiveStream.cpp:1169-1187)
Excellent encapsulation that:
- Calls
AlignBufferToSegmentto handle buffer management - Sets
current_segment_based on buffer state - Properly resets position tracking (
absolute_position_ = 0; segment_read_pos_ = 0;) - Clear single responsibility
3. New AlignBufferToSegment Method (AdaptiveStream.cpp:274-322)
This is the star of the refactoring:
- Implements segment reuse as described in PR objectives (point 3)
- Uses
ContainsSegmentto check if target segment is already buffered - If found: pauses downloads, removes older segments, keeps target segment (reuses downloaded data!)
- If not found: stops downloads and resets buffer completely
- Excellent thread safety with unique_lock/owns_lock pattern for checking download progress
- Handles the edge case where a download is in progress and its PTS is older than target
- The try_to_lock/owns_lock pattern safely checks download state without blocking the worker thread
4. Simplified seek_time Method (AdaptiveStream.cpp:1223-1242)
Reduced from ~50 lines with complex conditional logic to just 19 lines:
bool adaptive::AdaptiveStream::seek_time(double seek_seconds)
{
if (!current_rep_)
return false;
if (current_rep_->IsSubtitleFileStream())
return true;
std::lock_guard<adaptive::AdaptiveTree::TreeUpdateThread> lckUpdTree(m_tree->GetTreeUpdMutex());
const uint64_t pts = static_cast<uint64_t>(seek_seconds * current_rep_->GetTimescale());
const CSegment* seekSeg = current_rep_->Timeline().FindByPTSOrNext(pts);
if (!seekSeg)
return false;
UpdateCurrentSegment(*seekSeg);
return true;
}
Benefits:
- Crystal clear logic flow
- No more confusing
needResetoutput parameter - No more special
!preceedinghandling - Much easier to understand and maintain
5. Improved SeekTime in Session.cpp (Session.cpp:971-1089)
Two excellent lambda helpers:
CheckReaderRunning (lines 975-989): Ensures reader is started after cross-chapter/period seeks
SeekAdStream (lines 992-1003): Performs adaptive stream seek with appropriate reset
Better structure:
- Explicit timing stream handling first (lines 1011-1037)
- Clear separation of concerns
- Early
continuestatements for better readability - Removed confusing
noAdStreaminverted logic (nowhasAdStream)
Bug Fixes ✅
1. Fixed Seek Freeze Bug Across Chapters/Periods
Original issue: When oldSeg was empty and preceeding was true, the code would fall into an else condition setting needReset to false. Combined with the partially fixed "twice downloaded segments" bug, this caused uncontrolled segment downloads from the beginning of the period without resetting the current segment, resulting in frozen playback.
Solution: The new architecture always properly handles buffer alignment and position resets through UpdateCurrentSegment, eliminating this edge case entirely.
2. Fixed Off-by-One in GetNextDownload (SegmentBuffer.cpp:173)
Changed from < to <= to correctly handle the case where m_downloadIndex equals m_buffers.size(), preventing spurious error logs.
3. Fixed TS HEVC Seek Bug (TSReader.cpp:223)
Changed condition from thisFrameStart == m_startPos to thisFrameStart >= m_startPos, fixing the endless download issue introduced in commit 94f6e40c.
4. Implemented Segment Reuse (PR Objective #3)
The AlignBufferToSegment method now checks if the target segment is already in the buffer and reuses it, eliminating unnecessary re-downloads during video seeks. This is a significant performance improvement.
Documentation Quality ✅
Excellent doxygen comments added:
-
UpdateCurrentSegment(AdaptiveStream.h:80-84) -
AlignBufferToSegment(AdaptiveStream.h:198-202) -
ContainsSegment(SegmentBuffer.h:179-182) -
TimeSeek(SampleReader.h:85-90) - New TODO comment explaining chapter/period seek inefficiency (Session.cpp:720-726)
All comments are clear, concise, and accurately describe functionality.
Code Quality Assessment
Strengths:
- ✅ Separation of Concerns - Clear separation between segment management, buffer alignment, and seek logic
- ✅ Single Responsibility - Each method has a well-defined purpose
- ✅ Thread Safety - Proper mutex usage throughout with sophisticated unique_lock/owns_lock pattern
- ✅ Performance - Segment reuse optimization avoids unnecessary downloads
- ✅ Maintainability - Much clearer code structure that's easier to understand and modify
- ✅ Error Handling - Proper checks for edge cases (empty buffers, subtitle file streams, etc.)
Minor Issues:
- ⚠️ Inconsistent boundary check in SegmentBuffer.cpp (low priority)
- ⚠️ Missing braces in Session.cpp (style issue only)
Testing Recommendations
Given the scope of changes, ensure testing covers:
-
Segment Reuse:
- Forward seek within buffered range (should reuse without re-downloading)
- Backward seek to buffered segments
- Seek beyond buffered range (should reset and download)
-
Cross-Period/Chapter Seeks:
- Verify no uncontrolled downloads from period start
- Confirm playback doesn't freeze
- Check timing stream synchronization
-
Edge Cases:
- Subtitle streams (single-segment periods, EOS state)
- Both
preceeding=trueandpreceeding=false - TS HEVC streams (verify the TSReader fix)
- FMP4 audio streams included in video streams
-
Thread Safety:
- Concurrent seeks during downloads
- Verify no race conditions
Final Verdict
This PR is ready for merge with only minor cosmetic issues remaining. The architectural improvements are substantial and the bug fixes address real problems. The two remaining issues (boundary check inconsistency and missing braces) are low priority and don't affect functionality.
Recommendation: The PR can be merged as-is, with the minor issues addressed in a follow-up if desired, or fixed quickly before merge.
Excellent refactoring work! 👏