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/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/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp b/test/utests/tests/AampMPDDownloader/FunctionalTests.cpp
index 78dfdf9558..d1155320f5 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();
}
+// 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"(
+
+
+
+
+
+
+
+
+
+
+
+
+)";
+
+ 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)
{