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) {