Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions AampMPDDownloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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
Expand Down
19 changes: 2 additions & 17 deletions AampMPDParseHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand Down
10 changes: 10 additions & 0 deletions AampMPDParseHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions test/utests/tests/AampMPDDownloader/FunctionalTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"(<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" profiles="urn:mpeg:dash:profile:isoff-live:2011" minBufferTime="PT2.000S" maxSegmentDuration="PT0H0M1.92S" mediaPresentationDuration="PT0H0M28.8S" publishTime="2024-11-08T12:53:09.725Z">
<Period id="901591170" start="PT416006H37M27.854S">
<AdaptationSet id="2" contentType="video" mimeType="video/mp4" segmentAlignment="true" startWithSAP="1">
<SegmentTemplate initialization="init-$RepresentationID$.mp4" media="seg-$Number$.m4s" timescale="90000" startNumber="901599260" presentationTimeOffset="20213">
<SegmentTimeline>
<S t="1377581813" d="172800" r="14"/>
</SegmentTimeline>
</SegmentTemplate>
<Representation id="root_video4" bandwidth="562800" codecs="hvc1.1.6.L63.90" width="640" height="360" frameRate="25000/1000"/>
</AdaptationSet>
</Period>
</MPD>
)";

NotifyContext ctx;
auto calls = std::make_shared<std::atomic<int>>(0);
std::shared_ptr<ManifestDownloadConfig> inpData = std::make_shared<ManifestDownloadConfig>(-1);
inpData->mTuneUrl = url1;
mAampMPDDownloader->Initialize(inpData, appName, [calls, &ctx]() -> std::pair<std::string,int>
{
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<std::mutex> 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<std::mutex> 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)
{
Expand Down
Loading