diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index e7254594ba..3b011e8093 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -4535,9 +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 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 = 0.0; + mNextPts = seekPosition; UpdatePtsOffset(true); + // 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(); } @@ -9678,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/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 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.