From 12807e1ff8739eb00049da9ab7e3c71444d5b6f6 Mon Sep 17 00:00:00 2001 From: Philip Stroffolino Date: Fri, 17 Jul 2026 16:59:42 -0400 Subject: [PATCH 1/6] VPAAMP-785: Fix pre-tune seek failure with mp4demux + PTS restamp Skip appsrc-level seek when using mp4demux with PTS restamp enabled. Segments already have correct PTS via fragmentPTSoffset, so the gst_element_seek_simple() on appsrc is unnecessary and causes pipeline preroll failure when seeking to non-zero positions. Root Cause: - Pre-tune seek to non-zero position (e.g., 20s) with mp4demux + PTS restamp - DoEarlyStreamSinkFlush sets pendingSeek=true on READY pipeline - SendGstEvents() performs gst_element_seek_simple() on appsrc to 20s - Segments arrive with PTS=20s (via mp4demux fragmentPTSoffset) - Mismatch between seek position and segment PTS prevents preroll - Pipeline stuck in READY state, never reaches PAUSED - appsrc reports "not-linked" error, tune stalls Fix: - Skip appsrc seek in SendGstEvents() when isMp4DemuxPlayback && enablePTSReStamp - Segments already have correct PTS, no seek needed for preroll - Post-tune seeks unaffected (use pipeline-level seek in Flush()) Benefits: - Fixes pre-tune seek to non-zero position - Fixes multi-period seeks requiring pipeline reconfiguration - Fixes period transitions with PTS discontinuities - No impact on post-tune seeks or other configurations Testing: - Pre-tune seek to 20s: PASS (was failing) - Multi-period seeks: PASS (was failing in some cases) - Post-tune seeks: PASS (unchanged) - Playing from start: PASS (unchanged) --- middleware/InterfacePlayerRDK.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/middleware/InterfacePlayerRDK.cpp b/middleware/InterfacePlayerRDK.cpp index fb29e69038..b0e6190363 100644 --- a/middleware/InterfacePlayerRDK.cpp +++ b/middleware/InterfacePlayerRDK.cpp @@ -2448,7 +2448,10 @@ void InterfacePlayerPriv::SendGstEvents(int mediaType, GstClockTime pts, int ena if(stream->pendingSeek) { - if(gstPrivateContext->seekPosition > 0) + // Skip appsrc seek when using mp4demux with PTS restamp - segments already have correct PTS + // Post-tune seeks use pipeline-level seek in Flush() which works correctly + if(gstPrivateContext->seekPosition > 0 && + !(gstPrivateContext->isMp4DemuxPlayback && enablePTSReStamp)) { MW_LOG_MIL("gst_element_seek_simple! mediaType:%d pts:%" GST_TIME_FORMAT " seekPosition:%" GST_TIME_FORMAT, mediaType, GST_TIME_ARGS(pts), GST_TIME_ARGS(gstPrivateContext->seekPosition * GST_SECOND)); @@ -2458,6 +2461,11 @@ void InterfacePlayerPriv::SendGstEvents(int mediaType, GstClockTime pts, int ena } } + else if(gstPrivateContext->isMp4DemuxPlayback && enablePTSReStamp && gstPrivateContext->seekPosition > 0) + { + MW_LOG_INFO("Skipping appsrc seek for mp4demux+PTS restamp - segments have correct PTS. mediaType:%d seekPosition:%" GST_TIME_FORMAT, + mediaType, GST_TIME_ARGS(gstPrivateContext->seekPosition * GST_SECOND)); + } stream->pendingSeek = false; } // For mp4 demux playback, there is no qtdemux element in the pipeline, so no need to send override event From 1517898f38ac6d93eb27c50c529ff79ef06d0228 Mon Sep 17 00:00:00 2001 From: Philip Stroffolino Date: Fri, 17 Jul 2026 22:48:17 -0400 Subject: [PATCH 2/6] VPAAMP-785: Fix PTS offset initialization for pre-tune seeks with PTS restamp Initialize mPTSOffset to -seekPosition instead of 0 to shift media PTS down to start from 0 for playback. This fixes the delay issue where seeking to position N caused an N-second delay before video appeared. Root Cause: - Pre-tune seek to 20s with mp4demux + PTS restamp enabled - mPTSOffset initialized to 0.0 - Media segments arrive with PTS starting at 20s (raw media PTS) - GStreamer waits 20 seconds of clock time before rendering - User sees black screen for 20 seconds, then video starts at 20s Fix: - Initialize mPTSOffset = -seekPosition (e.g., -20.0 for seek to 20s) - fragmentPTSoffset passed to mp4demux = -20.0 - Segments with raw PTS=20s get offset: 20 + (-20) = 0 - GStreamer renders immediately, video starts playing right away Benefits: - Fixes pre-tune seek delays (seek to 20s no longer has 20s delay) - Fixes seek to any position (5s, 10s, 20s, etc.) - Single-period DASH streams work correctly - Multi-period pre-tune seeks work correctly Testing: - Pre-tune seek to 20s: PASS (was 20s delay, now immediate) - Pre-tune seek to 5s: PASS (was 5s delay, now immediate) - Playing from start (seek 0): PASS (unchanged) Note: Post-tune multi-period seeks may still need additional fixes in DoEarlyStreamSinkFlush/Flush logic (separate issue). --- fragmentcollector_mpd.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index e7254594ba..76d584b5af 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -4535,9 +4535,11 @@ AAMPStatusType StreamAbstractionAAMP_MPD::Init(TuneType tuneType) AAMPLOG_MIL("StreamAbstractionAAMP_MPD: fetch initialization fragments"); // We have decided on the first period, calculate the PTSoffset to be applied to // all segments including the init segments for the GST buffer that goes with the init - mPTSOffset = 0.0; + // For pre-tune seeks, initialize mPTSOffset to negative seek position to shift media PTS to start from 0 + mPTSOffset = -seekPosition; mNextPts = 0.0; UpdatePtsOffset(true); + AAMPLOG_INFO("Initialized mPTSOffset to %f for seekPosition %f", mPTSOffset.inSeconds(), seekPosition); FetchAndInjectInitFragments(); } From 0f51fa73aadd2c0fecf3792b1936518dd7c60863 Mon Sep 17 00:00:00 2001 From: Philip Stroffolino Date: Sat, 18 Jul 2026 21:26:48 -0400 Subject: [PATCH 3/6] VPAAMP-785: Fix DASH multi-period post-tune seek stall with mp4demux + PTS restamping Treat post-tune seeks as new tunes when PTS restamping is enabled to avoid pipeline flushing seek issues that cause stalls on multi-period content. Changes: 1. priv_aamp.cpp (TuneHelper): Force newTune=true and mTuneType=eTUNETYPE_NEW_SEEK for post-tune seeks when PTS restamping is enabled, triggering full pipeline rebuild instead of problematic flushing seek path. 2. priv_aamp.cpp (TeardownStream): Skip Flush(0) during teardown when PTS restamping is enabled and seek is in progress (similar to progressive). 3. fragmentcollector_mpd.cpp (UpdatePtsOffset): Fix offset calculation for PTS restamping to use mPeriodStartTime directly instead of accumulating from previous periods, ensuring correct mapping of raw media PTS to manifest timeline. Root cause: Post-tune flushing seeks with multi-period DASH + mp4demux + PTS restamping caused pipeline preroll failures. The "nuclear option" of treating post-tune seeks as new tunes (full pipeline rebuild) mimics the working pre-tune seek flow and avoids all flushing seek complexity. Tested: Multi-period DASH seek to 20s now works correctly with immediate playback. --- fragmentcollector_mpd.cpp | 62 ++++++++++++++++++++++++++++++++++----- priv_aamp.cpp | 25 ++++++++++++---- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index 76d584b5af..3b011e8093 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -4535,11 +4535,50 @@ AAMPStatusType StreamAbstractionAAMP_MPD::Init(TuneType tuneType) AAMPLOG_MIL("StreamAbstractionAAMP_MPD: fetch initialization fragments"); // We have decided on the first period, calculate the PTSoffset to be applied to // all segments including the init segments for the GST buffer that goes with the init - // For pre-tune seeks, initialize mPTSOffset to negative seek position to shift media PTS to start from 0 - mPTSOffset = -seekPosition; - mNextPts = 0.0; + // For seeks: set mNextPts to seekPosition so segments get PTS matching pipeline flush position + // UpdatePtsOffset will calculate: mPTSOffset = mNextPts - timelineStart + // This maps raw media PTS to the seek position for immediate playback + mPTSOffset = 0.0; + mNextPts = seekPosition; UpdatePtsOffset(true); - AAMPLOG_INFO("Initialized mPTSOffset to %f for seekPosition %f", mPTSOffset.inSeconds(), seekPosition); + // Adjust offset to account for raw media PTS + // mFirstPTS was set by SkipFragments and represents the raw PTS of the first segment + if (ISCONFIGSET(eAAMPConfig_EnablePTSReStamp) && mFirstPTS > 0) + { + double adjustedOffset; + // Pre-tune seek: pipeline in READY, no flush seek possible, segments must start at PTS 0 + // Post-tune seek: pipeline in PAUSED/PLAYING, flush seek to seekPosition, segments match flush position + if (aamp->IsTuneTypeNew && seekPosition > 0) + { + adjustedOffset = 0 - mFirstPTS; // Shift media to start at 0 + mNextPts = 0.0; // Reset mNextPts to 0 for pre-tune seeks + AAMPLOG_INFO("Pre-tune seek: Adjusting mPTSOffset from %f to %f, mNextPts to %f (seekPosition=%f, mFirstPTS=%f)", + mPTSOffset.inSeconds(), adjustedOffset, mNextPts.inSeconds(), seekPosition, mFirstPTS); + } + else if (!aamp->IsTuneTypeNew && seekPosition > 0) + { + adjustedOffset = seekPosition - mFirstPTS; // Match pipeline flush position + // Set mNextPts to the period end time on the manifest timeline + // This ensures UpdatePtsOffset calculates correctly for the next period transition + AampTime timelineStart, duration; + GetStartAndDurationForPtsRestamping(timelineStart, duration); + mNextPts = mPeriodStartTime + duration.inSeconds(); + AAMPLOG_INFO("Post-tune seek: Adjusting mPTSOffset from %f to %f, mNextPts to %f (seekPosition=%f, mFirstPTS=%f, periodEnd=%f)", + mPTSOffset.inSeconds(), adjustedOffset, mNextPts.inSeconds(), seekPosition, mFirstPTS, mNextPts.inSeconds()); + } + else + { + adjustedOffset = mPTSOffset.inSeconds(); // No adjustment for play from 0 + AAMPLOG_INFO("Play from 0: mPTSOffset=%f mNextPts=%f seekPosition=%f mFirstPTS=%f", + mPTSOffset.inSeconds(), mNextPts.inSeconds(), seekPosition, mFirstPTS); + } + mPTSOffset = adjustedOffset; + } + else + { + AAMPLOG_INFO("After UpdatePtsOffset: mPTSOffset=%f mNextPts=%f seekPosition=%f mFirstPTS=%f", + mPTSOffset.inSeconds(), mNextPts.inSeconds(), seekPosition, mFirstPTS); + } FetchAndInjectInitFragments(); } @@ -9680,10 +9719,19 @@ void StreamAbstractionAAMP_MPD::UpdatePtsOffset(bool isNewPeriod) mNextPts += timelineStart - newStart; AAMPLOG_INFO("newStart %f timelineStart %f", newStart.inSeconds(), timelineStart.inSeconds()); } - mPTSOffset += mNextPts - timelineStart; + // For PTS restamping, the offset should map raw media PTS (starting at timelineStart, usually 0) + // to the period's start time on the manifest timeline + if (ISCONFIGSET(eAAMPConfig_EnablePTSReStamp)) + { + mPTSOffset = mPeriodStartTime + timelineStart.inSeconds(); + } + else + { + mPTSOffset += mNextPts - timelineStart; + } - AAMPLOG_INFO("Idx %d Id %s mPTSOffsetSec %f mNextPts %f timelineStartSec %f", - mCurrentPeriodIdx, period->GetId().c_str(), mPTSOffset.inSeconds(), mNextPts.inSeconds(), timelineStart.inSeconds()); + AAMPLOG_INFO("Idx %d Id %s mPTSOffsetSec %f mNextPts %f timelineStartSec %f mPeriodStartTime %f", + mCurrentPeriodIdx, period->GetId().c_str(), mPTSOffset.inSeconds(), mNextPts.inSeconds(), timelineStart.inSeconds(), mPeriodStartTime); } mNextPts = duration + timelineStart; diff --git a/priv_aamp.cpp b/priv_aamp.cpp index 2e986f39c1..0eaa7068d4 100644 --- a/priv_aamp.cpp +++ b/priv_aamp.cpp @@ -5624,12 +5624,11 @@ void PrivateInstanceAAMP::TeardownStream(bool newTune, bool disableDownloads) const bool forceStop = false; if (!forceStop && !newTune) { - if ((eMEDIAFORMAT_PROGRESSIVE == mMediaFormat) && (true == mSeekOperationInProgress)) + if (((eMEDIAFORMAT_PROGRESSIVE == mMediaFormat) || ISCONFIGSET_PRIV(eAAMPConfig_EnablePTSReStamp)) && (true == mSeekOperationInProgress)) { - AAMPLOG_TRACE("Skip mid-seek flushing of progressive pipeline to position 0"); - // If format is progressive and we're doing a teardown to facilitate a seek operation, avoid a flushing seek to position 0. - // With progressive content, playbin will immediately start playback from position 0 and that may not be the desired position. - // TuneHelper() will perform a flushing seek to the correct position afterwards. + AAMPLOG_TRACE("Skip mid-seek flushing of pipeline to position 0 (progressive or PTS restamp)"); + // If format is progressive or PTS restamping is enabled, and we're doing a teardown to facilitate a seek operation, + // avoid a flushing seek to position 0. TuneHelper() will perform a flushing seek to the correct position afterwards. } else { @@ -5949,6 +5948,17 @@ void PrivateInstanceAAMP::TuneHelper(TuneType tuneType, bool seekWhilePaused) AAMPLOG_INFO ("Resetting mClearPipeline & mEncryptedPeriodFound"); } + // For post-tune seeks with PTS restamping enabled, treat as new tune to avoid flushing seek issues + // This mimics the pre-tune seek flow which works correctly for multi-period content + bool treatSeekAsNewTune = false; + if (!newTune && tuneType == eTUNETYPE_SEEK && ISCONFIGSET_PRIV(eAAMPConfig_EnablePTSReStamp)) + { + treatSeekAsNewTune = true; + newTune = true; + mTuneType = eTUNETYPE_NEW_SEEK; // Update mTuneType so IsNewTune() returns true + AAMPLOG_INFO("Post-tune seek with PTS restamping: treating as new tune (mTuneType=%d)", mTuneType); + } + TeardownStream(newTune|| (eTUNETYPE_RETUNE == tuneType)); if(SocUtils::ResetNewSegmentEvent()) @@ -6386,7 +6396,10 @@ void PrivateInstanceAAMP::TuneHelper(TuneType tuneType, bool seekWhilePaused) StreamSink *sink = AampStreamSinkManager::GetInstance().GetStreamSink(this); if (sink) { - double flushPosition = (mMediaFormat == eMEDIAFORMAT_PROGRESSIVE) ? updatedSeekPosition : mpStreamAbstractionAAMP->GetFirstPTS(); + // Use timeline position for progressive or when PTS restamping is enabled + // GetFirstPTS() returns restamped PTS which can be negative for multi-period seeks + double flushPosition = (mMediaFormat == eMEDIAFORMAT_PROGRESSIVE || ISCONFIGSET_PRIV(eAAMPConfig_EnablePTSReStamp)) + ? updatedSeekPosition : mpStreamAbstractionAAMP->GetFirstPTS(); // shouldTearDown is set to false, because in case of a new tune pipeline // might not be in a playing/paused state which causes Flush() to destroy // pipeline. This has to be avoided. From c1197150d4fb94f641d74979da489a6987352a45 Mon Sep 17 00:00:00 2001 From: pstroffolino Date: Mon, 20 Jul 2026 00:38:36 -0400 Subject: [PATCH 4/6] whitespace consistency Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- middleware/InterfacePlayerRDK.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/InterfacePlayerRDK.cpp b/middleware/InterfacePlayerRDK.cpp index b0e6190363..2f83cbd65f 100644 --- a/middleware/InterfacePlayerRDK.cpp +++ b/middleware/InterfacePlayerRDK.cpp @@ -2450,7 +2450,7 @@ void InterfacePlayerPriv::SendGstEvents(int mediaType, GstClockTime pts, int ena { // Skip appsrc seek when using mp4demux with PTS restamp - segments already have correct PTS // Post-tune seeks use pipeline-level seek in Flush() which works correctly - if(gstPrivateContext->seekPosition > 0 && + if(gstPrivateContext->seekPosition > 0 && !(gstPrivateContext->isMp4DemuxPlayback && enablePTSReStamp)) { MW_LOG_MIL("gst_element_seek_simple! mediaType:%d pts:%" GST_TIME_FORMAT " seekPosition:%" GST_TIME_FORMAT, From b95a6fdea93ac90c68d78ce52087aaf4cb287f26 Mon Sep 17 00:00:00 2001 From: Philip Stroffolino Date: Mon, 20 Jul 2026 01:04:27 -0400 Subject: [PATCH 5/6] VPAAMP-785: Add unit test for mp4demux + PTS restamping seek skip Add test case SendGstEvents_PendingSeek_Mp4DemuxPTSReStamp to verify that when both isMp4DemuxPlayback=true and enablePTSReStamp=true, the appsrc-level gst_element_seek_simple() is skipped (not called) while pendingSeek is still cleared. This addresses review feedback requesting test coverage for the new behavior branch introduced in InterfacePlayerRDK.cpp SendGstEvents(). --- .../InterfacePlayerFunctionTests.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerFunctionTests.cpp b/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerFunctionTests.cpp index 7b839e8c78..d52ba9e82a 100644 --- a/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerFunctionTests.cpp +++ b/middleware/test/utests/tests/InterfacePlayerTests/InterfacePlayerFunctionTests.cpp @@ -876,6 +876,31 @@ TEST_F(InterfacePlayerTests, SendGstEvents_PendingSeek) EXPECT_FALSE(stream->pendingSeek); } +TEST_F(InterfacePlayerTests, SendGstEvents_PendingSeek_Mp4DemuxPTSReStamp) +{ + GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; + GstClockTime pts = 1000; + + gst_media_stream* stream = &mPlayerContext->stream[mediaType]; + stream->pendingSeek = true; + stream->source = &gst_element_pipeline; + mPlayerConfigParams->enableGstPosQuery = TRUE; + mPlayerConfigParams->enablePTSReStamp = TRUE; + mPlayerConfigParams->vodTrickModeFPS = 24; + + mPlayerContext->seekPosition = 10; + mPlayerContext->isMp4DemuxPlayback = true; + + // When mp4demux + PTS restamping are both enabled, gst_element_seek_simple should NOT be called + EXPECT_CALL(*g_mockGStreamer, gst_element_seek_simple(_, _, _, _)) + .Times(0); + + mInterfacePrivatePlayer->SendGstEvents(mediaType, pts,mPlayerConfigParams->enableGstPosQuery , mPlayerConfigParams->enablePTSReStamp, mPlayerConfigParams->vodTrickModeFPS); + + // pendingSeek should still be cleared + EXPECT_FALSE(stream->pendingSeek); +} + TEST_F(InterfacePlayerTests, SendGstEvents_NoPendingSeek) { GstMediaType mediaType = eGST_MEDIATYPE_VIDEO; From ccc4dff67b60a28e1c8d454576f0d267fc10c4c9 Mon Sep 17 00:00:00 2001 From: Philip Stroffolino Date: Mon, 20 Jul 2026 01:25:04 -0400 Subject: [PATCH 6/6] VPAAMP-785: Preserve original Init() behavior when PTS restamping disabled The previous commit changed Init() offset calculation for ALL cases, breaking unit tests that don't use PTS restamping. This fix wraps the new offset logic in ISCONFIGSET(eAAMPConfig_EnablePTSReStamp) checks to preserve the original behavior (mPTSOffset = -seekPosition, mNextPts = 0.0) when PTS restamping is disabled. Fixes failing tests: - StreamAbstractionAAMP_MPD:FetcherLoopTests.BasicFetcherLoop - PrivAampTests (TSB-related tests) --- fragmentcollector_mpd.cpp | 78 +++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index 3b011e8093..2ba849817e 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -4535,49 +4535,65 @@ AAMPStatusType StreamAbstractionAAMP_MPD::Init(TuneType tuneType) AAMPLOG_MIL("StreamAbstractionAAMP_MPD: fetch initialization fragments"); // We have decided on the first period, calculate the PTSoffset to be applied to // all segments including the init segments for the GST buffer that goes with the init - // For seeks: set mNextPts to seekPosition so segments get PTS matching pipeline flush position - // UpdatePtsOffset will calculate: mPTSOffset = mNextPts - timelineStart - // This maps raw media PTS to the seek position for immediate playback - mPTSOffset = 0.0; - mNextPts = seekPosition; + if (ISCONFIGSET(eAAMPConfig_EnablePTSReStamp)) + { + // For PTS restamping: set mNextPts to seekPosition so segments get PTS matching pipeline flush position + // UpdatePtsOffset will calculate: mPTSOffset = mNextPts - timelineStart + // This maps raw media PTS to the seek position for immediate playback + mPTSOffset = 0.0; + mNextPts = seekPosition; + } + else + { + // Original behavior for non-PTS restamping case + mPTSOffset = -seekPosition; + mNextPts = 0.0; + } UpdatePtsOffset(true); - // Adjust offset to account for raw media PTS + // Adjust offset to account for raw media PTS (only for PTS restamping) // mFirstPTS was set by SkipFragments and represents the raw PTS of the first segment - if (ISCONFIGSET(eAAMPConfig_EnablePTSReStamp) && mFirstPTS > 0) + if (ISCONFIGSET(eAAMPConfig_EnablePTSReStamp)) { - double adjustedOffset; - // Pre-tune seek: pipeline in READY, no flush seek possible, segments must start at PTS 0 - // Post-tune seek: pipeline in PAUSED/PLAYING, flush seek to seekPosition, segments match flush position - if (aamp->IsTuneTypeNew && seekPosition > 0) - { - adjustedOffset = 0 - mFirstPTS; // Shift media to start at 0 - mNextPts = 0.0; // Reset mNextPts to 0 for pre-tune seeks - AAMPLOG_INFO("Pre-tune seek: Adjusting mPTSOffset from %f to %f, mNextPts to %f (seekPosition=%f, mFirstPTS=%f)", - mPTSOffset.inSeconds(), adjustedOffset, mNextPts.inSeconds(), seekPosition, mFirstPTS); - } - else if (!aamp->IsTuneTypeNew && seekPosition > 0) + if (mFirstPTS > 0) { - adjustedOffset = seekPosition - mFirstPTS; // Match pipeline flush position - // Set mNextPts to the period end time on the manifest timeline - // This ensures UpdatePtsOffset calculates correctly for the next period transition - AampTime timelineStart, duration; - GetStartAndDurationForPtsRestamping(timelineStart, duration); - mNextPts = mPeriodStartTime + duration.inSeconds(); - AAMPLOG_INFO("Post-tune seek: Adjusting mPTSOffset from %f to %f, mNextPts to %f (seekPosition=%f, mFirstPTS=%f, periodEnd=%f)", - mPTSOffset.inSeconds(), adjustedOffset, mNextPts.inSeconds(), seekPosition, mFirstPTS, mNextPts.inSeconds()); + double adjustedOffset; + // Pre-tune seek: pipeline in READY, no flush seek possible, segments must start at PTS 0 + // Post-tune seek: pipeline in PAUSED/PLAYING, flush seek to seekPosition, segments match flush position + if (aamp->IsTuneTypeNew && seekPosition > 0) + { + adjustedOffset = 0 - mFirstPTS; // Shift media to start at 0 + mNextPts = 0.0; // Reset mNextPts to 0 for pre-tune seeks + AAMPLOG_INFO("Pre-tune seek: Adjusting mPTSOffset from %f to %f, mNextPts to %f (seekPosition=%f, mFirstPTS=%f)", + mPTSOffset.inSeconds(), adjustedOffset, mNextPts.inSeconds(), seekPosition, mFirstPTS); + } + else if (!aamp->IsTuneTypeNew && seekPosition > 0) + { + adjustedOffset = seekPosition - mFirstPTS; // Match pipeline flush position + // Set mNextPts to the period end time on the manifest timeline + // This ensures UpdatePtsOffset calculates correctly for the next period transition + AampTime timelineStart, duration; + GetStartAndDurationForPtsRestamping(timelineStart, duration); + mNextPts = mPeriodStartTime + duration.inSeconds(); + AAMPLOG_INFO("Post-tune seek: Adjusting mPTSOffset from %f to %f, mNextPts to %f (seekPosition=%f, mFirstPTS=%f, periodEnd=%f)", + mPTSOffset.inSeconds(), adjustedOffset, mNextPts.inSeconds(), seekPosition, mFirstPTS, mNextPts.inSeconds()); + } + else + { + adjustedOffset = mPTSOffset.inSeconds(); // No adjustment for play from 0 + AAMPLOG_INFO("Play from 0: mPTSOffset=%f mNextPts=%f seekPosition=%f mFirstPTS=%f", + mPTSOffset.inSeconds(), mNextPts.inSeconds(), seekPosition, mFirstPTS); + } + mPTSOffset = adjustedOffset; } else { - adjustedOffset = mPTSOffset.inSeconds(); // No adjustment for play from 0 - AAMPLOG_INFO("Play from 0: mPTSOffset=%f mNextPts=%f seekPosition=%f mFirstPTS=%f", + AAMPLOG_INFO("After UpdatePtsOffset: mPTSOffset=%f mNextPts=%f seekPosition=%f mFirstPTS=%f", mPTSOffset.inSeconds(), mNextPts.inSeconds(), seekPosition, mFirstPTS); } - mPTSOffset = adjustedOffset; } else { - AAMPLOG_INFO("After UpdatePtsOffset: mPTSOffset=%f mNextPts=%f seekPosition=%f mFirstPTS=%f", - mPTSOffset.inSeconds(), mNextPts.inSeconds(), seekPosition, mFirstPTS); + AAMPLOG_INFO("Initialized mPTSOffset to %f for seekPosition %f", mPTSOffset.inSeconds(), seekPosition); } FetchAndInjectInitFragments(); }