From 7d441f1165ce5bd68b14b1b4f307edfd9fa3d954 Mon Sep 17 00:00:00 2001 From: srikanthreddybijjam-comcast Date: Tue, 15 Sep 2026 15:22:53 +0530 Subject: [PATCH 1/3] VPAAMP-795: [Timeshift DAI] Stream for HOT CDVR -> COLD CDVR transition Reason for change: Added a fix for UpdateStartTimeOfFirstPTS from HOT CDVR -> COLD CDVR transition Test Procedure: Refer jira ticket VPAAMP-795 Priority: P1 Signed-off-by: srikanthreddybijjam-comcast --- AampMPDParseHelper.cpp | 19 ++----------------- AampMPDParseHelper.h | 10 ++++++++++ fragmentcollector_mpd.cpp | 13 ++++++++++++- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/AampMPDParseHelper.cpp b/AampMPDParseHelper.cpp index 182f85c28c..b00d06e162 100644 --- a/AampMPDParseHelper.cpp +++ b/AampMPDParseHelper.cpp @@ -586,7 +586,7 @@ double AampMPDParseHelper::GetPeriodStartTime(int periodIndex,uint64_t mLastPlay durationTotal += aamp_GetPeriodDuration(idx, mLastPlaylistDownloadTimeMs); } periodStart = ((double)durationTotal / (double)1000); - if(mIsLiveManifest && (periodStart >= 0)) + if(mAvailabilityStartTime > 0 && (periodStart >= 0)) { periodStart += mAvailabilityStartTime; } @@ -668,24 +668,9 @@ double AampMPDParseHelper::GetPeriodEndTime(int periodIndex, uint64_t mLastPlayl if(startTimeStr.empty() || mLiveTimeFragmentSync) { AAMPLOG_INFO("Period startTime is not present in MPD, so calculating start time with previous period durations"); - if(mIsLiveManifest) - { - periodStartMs = GetPeriodStartTime(periodIndex,mLastPlaylistDownloadTimeMs) * 1000 - (mAvailabilityStartTime * 1000); - } - else - { - periodStartMs = GetPeriodStartTime(periodIndex,mLastPlaylistDownloadTimeMs) * 1000; - } - } - else - { - periodStartMs = ParseISO8601Duration(startTimeStr.c_str()) + (aamp_GetPeriodStartTimeDeltaRelativeToPTSOffset(period)* 1000); } + periodStartMs = GetPeriodStartTime(periodIndex,mLastPlaylistDownloadTimeMs) * 1000; periodEndTime = ((double)(periodStartMs + periodDurationMs) /1000); - if(mIsLiveManifest) - { - periodEndTime += mAvailabilityStartTime; - } } AAMPLOG_INFO("StreamAbstractionAAMP_MPD: MPD periodIndex:%d periodId %s periodEndTime %f", periodIndex, period->GetId().c_str(), periodEndTime); } diff --git a/AampMPDParseHelper.h b/AampMPDParseHelper.h index de7294352a..e63bd1f41d 100644 --- a/AampMPDParseHelper.h +++ b/AampMPDParseHelper.h @@ -249,6 +249,16 @@ public : */ double GetAvailabilityStartTime() { return mAvailabilityStartTime;} /** + * @fn SetAvailabilityStartTime + * @brief Sets AvailabilityStartTime in the helper + * @retval None + */ + void SetAvailabilityStartTime(double availabilityStartTime) + { + mAvailabilityStartTime = availabilityStartTime; + mMPDPeriodDetails.clear(); + } + /** * * @fn GetPublishTime * @brief Returns PublishTime from the manifest diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index 285d2827f9..f8c5942da6 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -3395,9 +3395,15 @@ AAMPStatusType StreamAbstractionAAMP_MPD::GetMPDFromManifest( ManifestDownloadRe if (tmpMPD) { + auto previousMPDParseHelper = mMPDParseHelper; this->mpd = tmpMPD; // Parse for generic parameters mMPDParseHelper = mpdDnldResp->GetMPDParseHelper(); + // Preserve availability start time from previous manifest to handle HOT->COLD transitions. + if (!init && previousMPDParseHelper && previousMPDParseHelper->GetAvailabilityStartTime() > 0) + { + mMPDParseHelper->SetAvailabilityStartTime(previousMPDParseHelper->GetAvailabilityStartTime()); + } // this flag for current state of manifest ( Linear to VOD can happen) if((mMPDParseHelper->IsLiveManifest() != mIsLiveManifest) && !init ) @@ -9196,6 +9202,8 @@ void StreamAbstractionAAMP_MPD::UpdateCulledAndDurationFromPeriodInfo(std::vecto } double firstPeriodStart = mMPDParseHelper->GetPeriodStartTime(firstPeriodIdx,mLastPlaylistDownloadTimeMs); double availStartTime = mMPDParseHelper->GetAvailabilityStartTime(); + bool isHotToColdTransition = mUpdateManifestState && !mIsLiveManifest && mIsLiveStream && + mpd->GetAvailabilityStarttime().empty() && availStartTime > 0; if( ( mUpdateManifestState == true )|| (true == aamp->IsIVODContent() && ( firstPeriodStart != mPrevFirstPeriodStart && availStartTime <=0 )) ) { @@ -9215,7 +9223,10 @@ void StreamAbstractionAAMP_MPD::UpdateCulledAndDurationFromPeriodInfo(std::vecto mCulledSeconds = aamp->culledSeconds; aamp->mAbsoluteEndPosition = aamp->culledSeconds; seekPosition = aamp->seek_pos_seconds; - aamp->mProgressReportOffset = -1; + if (!isHotToColdTransition) + { + aamp->mProgressReportOffset = -1; + } aamp->mPrevPositionMilliseconds.Invalidate(); AAMPLOG_WARN("firstPeriodStart: %lf aamp->culledSeconds: %lf aamp->seek_pos_seconds: %lf aamp->mAbsoluteEndPosition: %lf seekPosition: %lf mCulledSeconds: %lf", firstPeriodStart, aamp->culledSeconds, aamp->seek_pos_seconds, aamp->mAbsoluteEndPosition, seekPosition, mCulledSeconds); From 100d197fca9dec96443f742327fdd33650d72e31 Mon Sep 17 00:00:00 2001 From: Nandu Date: Mon, 26 Oct 2026 13:30:11 +0000 Subject: [PATCH 2/3] VPAAMP-1260: Preserve availability start time during manifest transitions Reason for change: Preserve availability start time from previous manifest to handle HOT->COLD transitions. Risks: Low Test Procedure: Test with CDVR streams Priority: P1 Signed-off-by: Nandakishor U M --- AampMPDDownloader.cpp | 24 +++++++ fragmentcollector_mpd.cpp | 13 +--- .../AampMPDDownloader/FunctionalTests.cpp | 67 +++++++++++++++++++ 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/AampMPDDownloader.cpp b/AampMPDDownloader.cpp index addf147b25..a7570fe274 100755 --- a/AampMPDDownloader.cpp +++ b/AampMPDDownloader.cpp @@ -388,6 +388,7 @@ void AampMPDDownloader::downloadMPDThread1() std::string tuneUrl = mMPDDnldCfg->mTuneUrl; bool firstDownload = true; bool downloadFailed = false; // set when a refresh attempt fails; cleared on recovery to log the transition + double lastLiveAvailabilityStartTime = 0.0; // AST of the last dynamic manifest, carried over on dynamic->static transition ManifestDownloadResponsePtr cachedBackupData = nullptr; do { @@ -554,6 +555,29 @@ void AampMPDDownloader::downloadMPDThread1() mManifestRefreshErrorCode.store(errorCode); } + if(mMPDData->mMPDStatus == AAMPStatusType::eAAMPSTATUS_OK && mMPDData->mMPDInstance) + { + AampMPDParseHelperPtr parseHelper = (mCachedMPDData != nullptr) ? mCachedMPDData->GetMPDParseHelper() : mMPDData->GetMPDParseHelper(); + if(mMPDData->mIsLiveManifest) + { + lastLiveAvailabilityStartTime = parseHelper->GetAvailabilityStartTime(); + } + else if(!firstDownload) + { + // dynamic -> static transition (e.g. HOT CDVR -> COLD CDVR): keep the availabilityStartTime + // of the last dynamic manifest, so positions stay on the same timeline. No further refresh + // is done for a static manifest, so the download loop exits after this push. + if(lastLiveAvailabilityStartTime > 0) + { + AAMPLOG_WARN("Manifest changed from dynamic to static, preserving availabilityStartTime %lf (static manifest value %lf)", + lastLiveAvailabilityStartTime, parseHelper->GetAvailabilityStartTime()); + parseHelper->SetAvailabilityStartTime(lastLiveAvailabilityStartTime); + } + // Final manifest; must reach the consumer even if publishTime is unchanged + doPush = true; + } + } + if(doPush) { // Push the output to Queue for Consumer to take diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index f8c5942da6..285d2827f9 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -3395,15 +3395,9 @@ AAMPStatusType StreamAbstractionAAMP_MPD::GetMPDFromManifest( ManifestDownloadRe if (tmpMPD) { - auto previousMPDParseHelper = mMPDParseHelper; this->mpd = tmpMPD; // Parse for generic parameters mMPDParseHelper = mpdDnldResp->GetMPDParseHelper(); - // Preserve availability start time from previous manifest to handle HOT->COLD transitions. - if (!init && previousMPDParseHelper && previousMPDParseHelper->GetAvailabilityStartTime() > 0) - { - mMPDParseHelper->SetAvailabilityStartTime(previousMPDParseHelper->GetAvailabilityStartTime()); - } // this flag for current state of manifest ( Linear to VOD can happen) if((mMPDParseHelper->IsLiveManifest() != mIsLiveManifest) && !init ) @@ -9202,8 +9196,6 @@ void StreamAbstractionAAMP_MPD::UpdateCulledAndDurationFromPeriodInfo(std::vecto } double firstPeriodStart = mMPDParseHelper->GetPeriodStartTime(firstPeriodIdx,mLastPlaylistDownloadTimeMs); double availStartTime = mMPDParseHelper->GetAvailabilityStartTime(); - bool isHotToColdTransition = mUpdateManifestState && !mIsLiveManifest && mIsLiveStream && - mpd->GetAvailabilityStarttime().empty() && availStartTime > 0; if( ( mUpdateManifestState == true )|| (true == aamp->IsIVODContent() && ( firstPeriodStart != mPrevFirstPeriodStart && availStartTime <=0 )) ) { @@ -9223,10 +9215,7 @@ void StreamAbstractionAAMP_MPD::UpdateCulledAndDurationFromPeriodInfo(std::vecto mCulledSeconds = aamp->culledSeconds; aamp->mAbsoluteEndPosition = aamp->culledSeconds; seekPosition = aamp->seek_pos_seconds; - if (!isHotToColdTransition) - { - aamp->mProgressReportOffset = -1; - } + aamp->mProgressReportOffset = -1; aamp->mPrevPositionMilliseconds.Invalidate(); AAMPLOG_WARN("firstPeriodStart: %lf aamp->culledSeconds: %lf aamp->seek_pos_seconds: %lf aamp->mAbsoluteEndPosition: %lf seekPosition: %lf mCulledSeconds: %lf", firstPeriodStart, aamp->culledSeconds, aamp->seek_pos_seconds, aamp->mAbsoluteEndPosition, seekPosition, mCulledSeconds); diff --git a/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp b/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp index 78dfdf9558..b36484fdb9 100644 --- a/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp +++ b/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp @@ -768,6 +768,73 @@ TEST_F(FunctionalTests, RegisterCallback_InvokedWhenManifestPublished) mAampMPDDownloader->Release(); } +// Static (COLD) version of kLongRefreshLiveMpd: no availabilityStartTime and an unchanged publishTime. +static const char *kStaticNoAstMpd = + R"( + + + + + + + + + + + + +)"; + +// On a dynamic->static transition the published static manifest keeps the last dynamic availabilityStartTime, +// and the download loop stops refreshing. +TEST_F(FunctionalTests, DynamicToStatic_PreservesAvailabilityStartTimeAndStopsRefresh) +{ + NotifyContext ctx; + auto calls = std::make_shared>(0); + std::shared_ptr inpData = std::make_shared(-1); + inpData->mTuneUrl = url1; + mAampMPDDownloader->Initialize(inpData, appName, [calls, &ctx]() -> std::pair + { + int n = calls->fetch_add(1) + 1; + ctx.cv.notify_all(); + return { std::string(n == 1 ? kLongRefreshLiveMpd : kStaticNoAstMpd), 200 }; + }); + DownloaderReleaseGuard guard{mAampMPDDownloader}; + mAampMPDDownloader->RegisterCallback(CountingNotifyCallback, &ctx); + mAampMPDDownloader->Start(); + + ManifestDownloadResponsePtr live = mAampMPDDownloader->GetManifest(true, 5000); + ASSERT_TRUE(live != nullptr); + ASSERT_TRUE(live->mIsLiveManifest); + const double liveAst = live->GetMPDParseHelper()->GetAvailabilityStartTime(); + ASSERT_GT(liveAst, 0); + + bool staticPublished; + { + std::unique_lock lock(ctx.mtx); + staticPublished = ctx.cv.wait_for(lock, std::chrono::seconds(10), [&ctx] { return ctx.count >= 2; }); + } + ASSERT_TRUE(staticPublished); + + ManifestDownloadResponsePtr cold = mAampMPDDownloader->GetManifest(true, 5000); + ASSERT_TRUE(cold != nullptr); + EXPECT_FALSE(cold->mIsLiveManifest); + EXPECT_FALSE(cold->GetMPDParseHelper()->IsLiveManifest()); + EXPECT_DOUBLE_EQ(cold->GetMPDParseHelper()->GetAvailabilityStartTime(), liveAst); + + // No refresh must follow the static manifest (a live refresh would come after ~3s). + bool refreshedAgain; + { + std::unique_lock lock(ctx.mtx); + refreshedAgain = ctx.cv.wait_for(lock, std::chrono::seconds(4), [&calls] { return calls->load() > 2; }); + } + EXPECT_FALSE(refreshedAgain); + EXPECT_EQ(calls->load(), 2); + + mAampMPDDownloader->UnRegisterCallback(); + mAampMPDDownloader->Release(); +} + // Waking the notifier thread for unregister/release must not be reported to the callback as a manifest update. TEST_F(FunctionalTests, RegisterCallback_NotInvokedWhenNoManifestPublished) { From 78179491b25f610749240a0a8faa10c890e35aa4 Mon Sep 17 00:00:00 2001 From: Nandu Date: Mon, 26 Oct 2026 13:58:03 +0000 Subject: [PATCH 3/3] Fixing Utest with manifest moving to test scope --- .../tests/AampMPDDownloader/FunctionalTests.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp b/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp index b36484fdb9..d1155320f5 100644 --- a/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp +++ b/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp @@ -768,8 +768,12 @@ TEST_F(FunctionalTests, RegisterCallback_InvokedWhenManifestPublished) mAampMPDDownloader->Release(); } -// Static (COLD) version of kLongRefreshLiveMpd: no availabilityStartTime and an unchanged publishTime. -static const char *kStaticNoAstMpd = +// On a dynamic->static transition the published static manifest keeps the last dynamic availabilityStartTime, +// and the download loop stops refreshing. +TEST_F(FunctionalTests, DynamicToStatic_PreservesAvailabilityStartTimeAndStopsRefresh) +{ + // Static (COLD) version of kLongRefreshLiveMpd: no availabilityStartTime and an unchanged publishTime. + static const char *kStaticNoAstMpd = R"( @@ -785,10 +789,6 @@ static const char *kStaticNoAstMpd = )"; -// On a dynamic->static transition the published static manifest keeps the last dynamic availabilityStartTime, -// and the download loop stops refreshing. -TEST_F(FunctionalTests, DynamicToStatic_PreservesAvailabilityStartTimeAndStopsRefresh) -{ NotifyContext ctx; auto calls = std::make_shared>(0); std::shared_ptr inpData = std::make_shared(-1);