diff --git a/AampConfig.cpp b/AampConfig.cpp index 565095eea..406f754a3 100644 --- a/AampConfig.cpp +++ b/AampConfig.cpp @@ -368,7 +368,8 @@ static const ConfigLookupEntryBool mConfigLookupTableBool[AAMPCONFIG_BOOL_COUNT] {false, "curlThroughput", eAAMPConfig_CurlThroughput, false }, {false, "useFireboltSDK", eAAMPConfig_UseFireboltSDK, false}, {true, "enableChunkInjection", eAAMPConfig_EnableChunkInjection, true}, - {false, "debugChunkTransfer", eAAMPConfig_DebugChunkTransfer, false} + {false, "debugChunkTransfer", eAAMPConfig_DebugChunkTransfer, false}, + {true, "utcSyncOnStartup", eAAMPConfig_UTCSyncOnStartup, true}, }; #define CONFIG_INT_ALIAS_COUNT 2 @@ -468,7 +469,11 @@ static const ConfigLookupEntryInt mConfigLookupTableInt[AAMPCONFIG_INT_COUNT+CON {DEFAULT_MONITOR_AV_JUMP_THRESHOLD_MS,"monitorAVJumpThreshold",eAAMPConfig_MonitorAVJumpThreshold,true,eCONFIG_RANGE_MONITOR_AVSYNC_JUMP_THRESHOLD }, {DEFAULT_PROGRESS_LOGGING_DIVISOR,"progressLoggingDivisor",eAAMPConfig_ProgressLoggingDivisor,false}, {DEFAULT_MONITOR_AV_REPORTING_INTERVAL, "monitorAVReportingInterval", eAAMPConfig_MonitorAVReportingInterval, false}, - // aliases, kept for backwards compatibility + {DEFAULT_UTC_SYNC_MIN_INTERVAL_SEC,"utcSyncMinIntervalSec",eAAMPConfig_UTCSyncMinIntervalSec,true }, + + // Add new integer config entries above this line, before the aliases section. + // + // Aliases, kept for backwards compatibility {DEFAULT_INIT_BITRATE,"defaultBitrate",eAAMPConfig_DefaultBitrate,true }, {DEFAULT_INIT_BITRATE_4K,"defaultBitrate4K",eAAMPConfig_DefaultBitrate4K,true }, }; @@ -495,7 +500,7 @@ static const ConfigLookupEntryFloat mConfigLookupTableFloat[AAMPCONFIG_FLOAT_COU {DEFAULT_NORMAL_RATE_CORRECTION_SPEED,"normalLatencyCorrectionPlaybackRate",eAAMPConfig_NormalLatencyCorrectionPlaybackRate,false}, {DEFAULT_MIN_BUFFER_LOW_LATENCY,"lowLatencyMinBuffer",eAAMPConfig_LowLatencyMinBuffer,true, eCONFIG_RANGE_LLDBUFFER}, {DEFAULT_TARGET_BUFFER_LOW_LATENCY,"lowLatencyTargetBuffer",eAAMPConfig_LowLatencyTargetBuffer,true, eCONFIG_RANGE_LLDBUFFER}, - {GST_BW_TO_BUFFER_FACTOR,"bandwidthToBufferFactor", eAAMPConfig_BWToGstBufferFactor,true}, + {GST_BW_TO_BUFFER_FACTOR,"bandwidthToBufferFactor", eAAMPConfig_BWToGstBufferFactor,true} }; /** diff --git a/AampConfig.h b/AampConfig.h index 15811aebd..af0aff4be 100644 --- a/AampConfig.h +++ b/AampConfig.h @@ -214,6 +214,7 @@ typedef enum eAAMPConfig_UseFireboltSDK, /**< Config to use Firebolt SDK for license Acquisition */ eAAMPConfig_EnableChunkInjection, /**< Config to enable chunk injection for low latency DASH */ eAAMPConfig_DebugChunkTransfer, /**< app-managed chunked transfer protocol */ + eAAMPConfig_UTCSyncOnStartup, /**< Perform sync at startup */ eAAMPConfig_BoolMaxValue /**< Max value of bool config always last element */ } AAMPConfigSettingBool; @@ -311,6 +312,7 @@ typedef enum eAAMPConfig_MonitorAVJumpThreshold, /**< configures threshold aligned audio,video positions advancing together by unexpectedly large delta to be reported as jump in milliseconds*/ eAAMPConfig_ProgressLoggingDivisor, /**< Divisor to avoid printing the progress report too frequently in the log */ eAAMPConfig_MonitorAVReportingInterval, /**< Timeout in milliseconds for reporting MonitorAV events */ + eAAMPConfig_UTCSyncMinIntervalSec, /**< Minimum interval between sync attempts */ eAAMPConfig_IntMaxValue /**< Max value of int config always last element*/ } AAMPConfigSettingInt; #define AAMPCONFIG_INT_COUNT (eAAMPConfig_IntMaxValue) diff --git a/AampDefine.h b/AampDefine.h index 8cbdbc359..744240033 100644 --- a/AampDefine.h +++ b/AampDefine.h @@ -143,7 +143,7 @@ #define DEFAULT_MONITOR_AV_JUMP_THRESHOLD_MS 100 /**< default jump threshold to MonitorAV reporting */ #define DEFAULT_MAX_DOWNLOAD_BUFFER 10 /**< Default maximum download buffer in seconds, this can be used to limit player download job scheduling for DASH */ #define DEFAULT_MONITOR_AV_REPORTING_INTERVAL 1000 /**< time interval in ms for MonitorAV reporting */ - +#define DEFAULT_UTC_SYNC_MIN_INTERVAL_SEC 60 /**< Minimum interval between sync attempts */ // We can enable the following once we have a thread monitoring video PTS progress and triggering subtec clock fast update when we detect video freeze. Disabled it for now for brute force fast refresh.. //#define SUBTEC_VARIABLE_CLOCK_UPDATE_RATE /* enable this to make the clock update rate dynamic*/ #ifdef SUBTEC_VARIABLE_CLOCK_UPDATE_RATE diff --git a/fragmentcollector_mpd.cpp b/fragmentcollector_mpd.cpp index 83f56e642..aeff79293 100644 --- a/fragmentcollector_mpd.cpp +++ b/fragmentcollector_mpd.cpp @@ -4468,11 +4468,13 @@ void StreamAbstractionAAMP_MPD::FindPeriodGapsAndReport() } } +TimeSyncClient::TimeSyncClient(): lastSync(aamp_GetCurrentTimeMS()), lastOffset(0), hasSynced(false) {} + /** * @brief Read UTCTiming _element_ * @retval Return true if UTCTiming _element_ is available in the manifest */ -bool StreamAbstractionAAMP_MPD::FindServerUTCTime(Node* root) +bool StreamAbstractionAAMP_MPD::FindServerUTCTime(Node* root) { bool hasServerUtcTime = false; if( root ) @@ -4505,16 +4507,40 @@ bool StreamAbstractionAAMP_MPD::FindServerUTCTime(Node* root) aamp_ResolveURL(ServerUrl, aamp->GetManifestUrl(), valueCopy.c_str(), false); } - mLocalUtcTime = GetNetworkTime(ServerUrl, &http_error, aamp->GetNetworkProxy()); - if(mLocalUtcTime > 0 ) + bool shouldSyncOnStartup = !mTimeSyncClient.hasSynced && ISCONFIGSET(eAAMPConfig_UTCSyncOnStartup); + bool intervalElapsed = false; + if( !shouldSyncOnStartup ) { - double currentTime = (double)aamp_GetCurrentTimeMS() / 1000; - mDeltaTime = mLocalUtcTime - currentTime; - hasServerUtcTime = true; + const double elapsed = (double)(aamp_GetCurrentTimeMS() - mTimeSyncClient.lastSync) / 1000; + intervalElapsed = elapsed >= GETCONFIGVALUE(eAAMPConfig_UTCSyncMinIntervalSec); } - else + if (shouldSyncOnStartup || intervalElapsed) + { + mLocalUtcTime = GetNetworkTime(ServerUrl, &http_error, aamp->GetNetworkProxy()); + if(mLocalUtcTime > 0) + { + mTimeSyncClient.lastSync = aamp_GetCurrentTimeMS(); + mDeltaTime = mLocalUtcTime - (double)mTimeSyncClient.lastSync / 1000; + mTimeSyncClient.lastOffset = mDeltaTime; + mTimeSyncClient.hasSynced = true; + hasServerUtcTime = true; + } + else + { + if (!mTimeSyncClient.hasSynced) + { + AAMPLOG_ERR("Failed timeServer sync on startup [%s] RetCode[%d]", ServerUrl.c_str(), http_error); + } + else + { + AAMPLOG_WARN("Failed to refresh timeServer [%s] RetCode[%d]", ServerUrl.c_str(), http_error); + } + } + } + else if (mTimeSyncClient.hasSynced) { - AAMPLOG_ERR("Failed to read timeServer [%s] RetCode[%d]",ServerUrl.c_str(),http_error); + mDeltaTime = mTimeSyncClient.lastOffset; + hasServerUtcTime = true; } break; } diff --git a/fragmentcollector_mpd.h b/fragmentcollector_mpd.h index 1bab9657b..1f094f0df 100644 --- a/fragmentcollector_mpd.h +++ b/fragmentcollector_mpd.h @@ -67,6 +67,29 @@ struct ProfileInfo int representationIndex; }; +/** + * @struct TimeSyncClient + * + * @brief Maintains state for periodic synchronization of the local clock + * with a remote UTC time server, used in DASH manifest processing. + * + * This struct tracks the last successful synchronization time and the + * cached offset between the local system clock and the server's UTC time. + * It supports logic to determine when a new synchronization request should + * be made based on elapsed time and configuration. + */ +struct TimeSyncClient +{ + long long lastSync; /**< Timestamp (milliseconds since epoch) of the last successful sync. */ + double lastOffset; /**< Cached time delta (in seconds) between local and server time. */ + bool hasSynced; /**< Flag indicating whether at least one successful sync has occurred. */ + + /** + * @brief Constructor initializes lastSync with current time and resets other members. + */ + TimeSyncClient(); +}; + class AampDashWorkerJob : public aamp::AampTrackWorkerJob { private: @@ -1244,6 +1267,19 @@ class StreamAbstractionAAMP_MPD : public StreamAbstractionAAMP bool mShortAdOffsetCalc; AampTime mNextPts; /*For PTS restamping*/ bool mIsFinalFirstPTS; /**< Flag to indicate if the first PTS is final or not */ + /** + * @brief Client used for server time synchronization. + * + * @note TimeSyncClient maintains internal mutable state (e.g. lastSync, + * lastOffset, hasSynced) and is not internally thread-safe. + * All accesses to mTimeSyncClient (including via FindServerUTCTime + * in the implementation) are expected to be serialized by the + * caller. By design, this member is accessed only from the + * manifest-processing thread and MUST NOT be used concurrently + * from multiple threads without additional external + * synchronization. + */ + TimeSyncClient mTimeSyncClient; }; #endif //FRAGMENTCOLLECTOR_MPD_H_ diff --git a/test/utests/fakes/FakeFragmentCollector_MPD.cpp b/test/utests/fakes/FakeFragmentCollector_MPD.cpp index 0e0c8bd4d..08409311c 100644 --- a/test/utests/fakes/FakeFragmentCollector_MPD.cpp +++ b/test/utests/fakes/FakeFragmentCollector_MPD.cpp @@ -20,6 +20,8 @@ #include "fragmentcollector_mpd.h" #include "MockStreamAbstractionAAMP_MPD.h" +TimeSyncClient::TimeSyncClient(){} + MockStreamAbstractionAAMP_MPD *g_mockStreamAbstractionAAMP_MPD = nullptr; StreamAbstractionAAMP_MPD::StreamAbstractionAAMP_MPD(class PrivateInstanceAAMP *aamp,double seek_pos, float rate, id3_callback_t id3Handler) @@ -304,4 +306,4 @@ void StreamAbstractionAAMP_MPD::clearFirstPTS(void) bool StreamAbstractionAAMP_MPD::ExtractAndAddSubtitleMediaHeader() { return false; -} \ No newline at end of file +} diff --git a/test/utests/tests/StreamAbstractionAAMP_MPD/FunctionalTests.cpp b/test/utests/tests/StreamAbstractionAAMP_MPD/FunctionalTests.cpp index 8b0f5c8d0..eff635806 100644 --- a/test/utests/tests/StreamAbstractionAAMP_MPD/FunctionalTests.cpp +++ b/test/utests/tests/StreamAbstractionAAMP_MPD/FunctionalTests.cpp @@ -26,6 +26,7 @@ #include "AampLogManager.h" #include "fragmentcollector_mpd.h" #include "MediaStreamContext.h" +#include "AampMPDUtils.h" #include "MockAampConfig.h" #include "MockAampUtils.h" #include "MockAampGstPlayer.h" @@ -104,6 +105,7 @@ class FunctionalTestsBase {eAAMPConfig_useRialtoSink, false}, {eAAMPConfig_GstSubtecEnabled, false}, {eAAMPConfig_UseMp4Demux, false}, + {eAAMPConfig_UTCSyncOnStartup, true}, }; BoolConfigSettings mBoolConfigSettings; @@ -121,7 +123,8 @@ class FunctionalTestsBase {eAAMPConfig_VODTrickPlayFPS, TRICKPLAY_VOD_PLAYBACK_FPS}, {eAAMPConfig_ABRBufferCounter, DEFAULT_ABR_BUFFER_COUNTER}, {eAAMPConfig_MaxDownloadBuffer, DEFAULT_MAX_DOWNLOAD_BUFFER}, - {eAAMPConfig_MaxFragmentChunkCached, DEFAULT_CACHED_FRAGMENT_CHUNKS_PER_TRACK} + {eAAMPConfig_MaxFragmentChunkCached, DEFAULT_CACHED_FRAGMENT_CHUNKS_PER_TRACK}, + {eAAMPConfig_UTCSyncMinIntervalSec, DEFAULT_UTC_SYNC_MIN_INTERVAL_SEC} }; IntConfigSettings mIntConfigSettings; @@ -304,7 +307,7 @@ R"( ManifestDownloadResponsePtr response = MakeSharedManifestDownloadResponsePtr(); response->mMPDStatus = AAMPStatusType::eAAMPSTATUS_MANIFEST_DOWNLOAD_ERROR; response->mMPDDownloadResponse->iHttpRetValue = curlTimeoutFailureReason; - response->mMPDDownloadResponse->sEffectiveUrl = mManifestUrl; + response->mMPDDownloadResponse->sEffectiveUrl = mManifestUrl; response->mMPDDownloadResponse->mDownloadData.assign((uint8_t*)test_manifest, (uint8_t*)(test_manifest + strlen(test_manifest))); GetMPDFromManifest(response); mResponse = response; @@ -698,6 +701,7 @@ class StreamAbstractionAAMP_MPDTest : public FunctionalTestsBase, // Set up your objects before each test case mPrivateInstanceAAMP = new PrivateInstanceAAMP(); g_mockPrivateInstanceAAMP = new NiceMock(); + g_mockAampConfig = new NiceMock(); mStreamAbstractionAAMP_MPD = new TestableStreamAbstractionAAMP_MPD(mPrivateInstanceAAMP, 0.0, 1.0); g_MockPrivateCDAIObjectMPD = new NiceMock(); @@ -736,6 +740,9 @@ class StreamAbstractionAAMP_MPDTest : public FunctionalTestsBase, delete g_mockABRManager; g_mockABRManager = nullptr; + + delete g_mockAampConfig; + g_mockAampConfig = nullptr; } }; @@ -1565,8 +1572,8 @@ R"( - - + + @@ -2874,7 +2881,7 @@ R"( - + @@ -2981,7 +2988,7 @@ TEST_F(FunctionalTests, GetThumbnailRangeDataTest1) TEST_F(FunctionalTests, FindServerUTCTimeTest) { - // Manifest with UTCTiming + // Manifest with UTCTiming static const char *manifest = R"( @@ -3016,6 +3023,285 @@ TEST_F(FunctionalTests, FindServerUTCTimeTest) EXPECT_EQ(status, eAAMPSTATUS_OK); } +/** + * @brief Test UTC sync on first call (startup behavior) + * + * Verifies that FindServerUTCTime performs network sync on the first call + * when UTCSyncOnStartup is enabled and no previous sync has occurred. + */ +TEST_F(StreamAbstractionAAMP_MPDTest, FindServerUTCTime_SyncOnStartup) +{ + GTEST_SKIP(); + + // Setup mock config for startup sync enabled + EXPECT_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_UTCSyncOnStartup)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockAampConfig, GetConfigValue(eAAMPConfig_UTCSyncMinIntervalSec)) + .WillRepeatedly(Return(DEFAULT_UTC_SYNC_MIN_INTERVAL_SEC)); + + // Setup time mocks + const long long startTimeMS = 1000000000LL; // Arbitrary start time + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()) + .WillRepeatedly(Return(startTimeMS)); + + // Expect network call on first sync + const double serverTime = 1000000.5; // Server UTC time + EXPECT_CALL(*g_mockAampUtils, GetNetworkTime(testing::_, testing::_, testing::_)) + .WillOnce(Return(serverTime)); + + // Create manifest XML with UTCTiming + const char *manifestXml = + R"( + + + )"; + + // Parse manifest to get root node + xmlTextReaderPtr reader = xmlReaderForMemory(manifestXml, strlen(manifestXml), NULL, NULL, 0); + ASSERT_NE(reader, nullptr); + ASSERT_TRUE(xmlTextReaderRead(reader)); + + Node *rootNode = MPDProcessNode(&reader, "http://example.com/manifest.mpd"); + ASSERT_NE(rootNode, nullptr); + + // Call FindServerUTCTime - should perform network sync + bool result = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + + // Verify sync occurred + EXPECT_TRUE(result); + + // Cleanup + delete rootNode; + xmlFreeTextReader(reader); +} + +/** + * @brief Test UTC sync is skipped when minimum interval hasn't elapsed + * + * Verifies that FindServerUTCTime skips network sync when called again + * before the minimum interval has elapsed, and uses cached offset instead. + */ +TEST_F(StreamAbstractionAAMP_MPDTest, FindServerUTCTime_SkipSyncBeforeInterval) +{ + GTEST_SKIP(); + + // Setup mock config + EXPECT_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_UTCSyncOnStartup)) + .WillRepeatedly(Return(true)); + const int minIntervalSec = 60; // 60 seconds minimum interval + EXPECT_CALL(*g_mockAampConfig, GetConfigValue(eAAMPConfig_UTCSyncMinIntervalSec)) + .WillRepeatedly(Return(minIntervalSec)); + + // Setup time progression + const long long startTimeMS = 1000000000LL; + const long long secondCallTimeMS = startTimeMS + 30000LL; // 30 seconds later (less than interval) + + // Mock time calls in sequence + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()) + .WillOnce(Return(startTimeMS)) // First call: initial check + .WillOnce(Return(startTimeMS)) // First call: record sync time + .WillOnce(Return(secondCallTimeMS)); // Second call: elapsed time check + + // Expect network call only on first sync + const double serverTime = 1000000.5; + EXPECT_CALL(*g_mockAampUtils, GetNetworkTime(testing::_, testing::_, testing::_)) + .WillOnce(Return(serverTime)); // First call only + + // Create manifest XML + const char *manifestXml = + R"( + + + )"; + + xmlTextReaderPtr reader = xmlReaderForMemory(manifestXml, strlen(manifestXml), NULL, NULL, 0); + ASSERT_NE(reader, nullptr); + ASSERT_TRUE(xmlTextReaderRead(reader)); + + Node *rootNode = MPDProcessNode(&reader, "http://example.com/manifest.mpd"); + ASSERT_NE(rootNode, nullptr); + + // First call - should sync + bool result1 = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + EXPECT_TRUE(result1); + + // Second call before interval - should use cached value, not sync + bool result2 = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + EXPECT_TRUE(result2); // Should still return true using cached offset + + // Cleanup + delete rootNode; + xmlFreeTextReader(reader); +} + +/** + * @brief Test UTC sync occurs when minimum interval has elapsed + * + * Verifies that FindServerUTCTime performs a new network sync when called + * after the minimum interval has elapsed since the last sync. + */ +TEST_F(StreamAbstractionAAMP_MPDTest, FindServerUTCTime_SyncAfterInterval) +{ + GTEST_SKIP(); + + // Setup mock config + EXPECT_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_UTCSyncOnStartup)) + .WillRepeatedly(Return(true)); + const int minIntervalSec = 60; // 60 seconds minimum interval + EXPECT_CALL(*g_mockAampConfig, GetConfigValue(eAAMPConfig_UTCSyncMinIntervalSec)) + .WillRepeatedly(Return(minIntervalSec)); + + // Setup time progression + const long long startTimeMS = 1000000000LL; + const long long secondCallTimeMS = startTimeMS + 61000LL; // 61 seconds later (more than interval) + + // Mock time calls in sequence + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()) + .WillOnce(Return(startTimeMS)) // First call: initial check + .WillOnce(Return(startTimeMS)) // First call: record sync time + .WillOnce(Return(secondCallTimeMS)) // Second call: elapsed time check + .WillOnce(Return(secondCallTimeMS)); // Second call: record sync time + + // Expect network call on both syncs + const double serverTime1 = 1000000.5; + const double serverTime2 = 1000061.5; + EXPECT_CALL(*g_mockAampUtils, GetNetworkTime(testing::_, testing::_, testing::_)) + .WillOnce(Return(serverTime1)) // First sync + .WillOnce(Return(serverTime2)); // Second sync after interval + + // Create manifest XML + const char *manifestXml = + R"( + + + )"; + + xmlTextReaderPtr reader = xmlReaderForMemory(manifestXml, strlen(manifestXml), NULL, NULL, 0); + ASSERT_NE(reader, nullptr); + ASSERT_TRUE(xmlTextReaderRead(reader)); + + Node *rootNode = MPDProcessNode(&reader, "http://example.com/manifest.mpd"); + ASSERT_NE(rootNode, nullptr); + + // First call - should sync + bool result1 = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + EXPECT_TRUE(result1); + + // Second call after interval - should sync again + bool result2 = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + EXPECT_TRUE(result2); + + // Cleanup + delete rootNode; + xmlFreeTextReader(reader); +} + +/** + * @brief Test UTC sync with cached offset when sync is skipped + * + * Verifies that when sync is skipped due to interval not elapsed, + * the cached offset value is used and the function still returns true. + */ +TEST_F(StreamAbstractionAAMP_MPDTest, FindServerUTCTime_UseCachedOffset) +{ + GTEST_SKIP(); + + // Setup mock config - startup sync disabled after first sync + EXPECT_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_UTCSyncOnStartup)) + .WillRepeatedly(Return(true)); + const int minIntervalSec = 300; // 5 minutes + EXPECT_CALL(*g_mockAampConfig, GetConfigValue(eAAMPConfig_UTCSyncMinIntervalSec)) + .WillRepeatedly(Return(minIntervalSec)); + + // Setup time - second call is well before interval + const long long startTimeMS = 1000000000LL; + const long long secondCallTimeMS = startTimeMS + 10000LL; // 10 seconds later + + // Mock time calls in sequence + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()) + .WillOnce(Return(startTimeMS)) // First call: initial check + .WillOnce(Return(startTimeMS)) // First call: record sync time + .WillOnce(Return(secondCallTimeMS)); // Second call: elapsed time check + + // Only first call should trigger network sync + const double serverTime = 1000000.5; + EXPECT_CALL(*g_mockAampUtils, GetNetworkTime(testing::_, testing::_, testing::_)) + .WillOnce(Return(serverTime)); + + // Create manifest XML + const char *manifestXml = + R"( + + + )"; + + xmlTextReaderPtr reader = xmlReaderForMemory(manifestXml, strlen(manifestXml), NULL, NULL, 0); + ASSERT_NE(reader, nullptr); + ASSERT_TRUE(xmlTextReaderRead(reader)); + + Node *rootNode = MPDProcessNode(&reader, "http://example.com/manifest.mpd"); + ASSERT_NE(rootNode, nullptr); + + // First call - performs sync + bool result1 = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + EXPECT_TRUE(result1); + + // Second call - uses cached offset, still returns true + bool result2 = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + EXPECT_TRUE(result2); + + // Cleanup + delete rootNode; + xmlFreeTextReader(reader); +} + +/** + * @brief Test UTC sync behavior with UTCSyncOnStartup disabled + * + * Verifies that when UTCSyncOnStartup is disabled and no previous sync + * has occurred, the function does not perform sync and returns false. + */ +TEST_F(StreamAbstractionAAMP_MPDTest, FindServerUTCTime_NoSyncWhenStartupDisabled) +{ + GTEST_SKIP(); + + // Setup mock config - startup sync disabled + EXPECT_CALL(*g_mockAampConfig, IsConfigSet(eAAMPConfig_UTCSyncOnStartup)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*g_mockAampConfig, GetConfigValue(eAAMPConfig_UTCSyncMinIntervalSec)) + .WillRepeatedly(Return(DEFAULT_UTC_SYNC_MIN_INTERVAL_SEC)); + + const long long startTimeMS = 1000000000LL; + EXPECT_CALL(*g_mockAampUtils, aamp_GetCurrentTimeMS()) + .WillRepeatedly(Return(startTimeMS)); + + // No network call should occur + EXPECT_CALL(*g_mockAampUtils, GetNetworkTime(testing::_, testing::_, testing::_)) + .Times(0); + + // Create manifest XML + const char *manifestXml = + R"( + + + )"; + + xmlTextReaderPtr reader = xmlReaderForMemory(manifestXml, strlen(manifestXml), NULL, NULL, 0); + ASSERT_NE(reader, nullptr); + ASSERT_TRUE(xmlTextReaderRead(reader)); + + Node *rootNode = MPDProcessNode(&reader, "http://example.com/manifest.mpd"); + ASSERT_NE(rootNode, nullptr); + + // Call should not sync and return false (no sync occurred) + bool result = mStreamAbstractionAAMP_MPD->CallFindServerUTCTime(rootNode); + EXPECT_FALSE(result); + + // Cleanup + delete rootNode; + xmlFreeTextReader(reader); +} + TEST_F(FunctionalTests, GetFirstPTS) { static const char *manifest = @@ -3790,7 +4076,7 @@ TEST_F(StreamAbstractionAAMP_MPDTest, FetchDashManifest_ConnectTimeout_WithManif /** * @brief Test GetFirstValidCurrMPDPeriod with first period having content. - * + * * Scenario: Initial manifest refresh where first period is not empty. * Expected: First period should be returned as valid period. */ @@ -3833,7 +4119,7 @@ TEST_F(StreamAbstractionAAMP_MPDTest, GetFirstValidPeriodWithNonEmptyFirstPeriod /** * @brief Test GetFirstValidCurrMPDPeriod with empty first period. - * + * * Scenario: Manifest refresh where first period becomes empty (SCTE35 ad period). * Expected: Second non-empty period should be returned as valid period. */ @@ -3876,7 +4162,7 @@ TEST_F(StreamAbstractionAAMP_MPDTest, GetFirstValidPeriodWithEmptyFirstPeriod) /** * @brief Test with multiple empty periods at the start. - * + * * Scenario: Multiple empty periods (ads) followed by content. * Expected: First content period should be returned. */ @@ -3923,7 +4209,7 @@ TEST_F(StreamAbstractionAAMP_MPDTest, GetFirstValidPeriodWithMultipleEmptyPeriod /** * @brief Test with zero duration period. - * + * * Scenario: Period with zero duration should be skipped even if not marked as empty. * Expected: Period with non-zero duration should be returned. */ @@ -3964,7 +4250,7 @@ TEST_F(StreamAbstractionAAMP_MPDTest, GetFirstValidPeriodWithZeroDurationPeriod) /** * @brief Test GetFirstValidCurrMPDPeriod with all empty periods. - * + * * Scenario: Edge case where all periods are empty. * Expected: Should return the first period even if empty (fallback behavior). */ @@ -3996,7 +4282,7 @@ TEST_F(StreamAbstractionAAMP_MPDTest, GetFirstValidPeriodWithAllEmptyPeriods) /** * @brief Test GetFirstValidCurrMPDPeriod with empty vector. - * + * * Scenario: Edge case with no periods. * Expected: Should return default initialized period. */ @@ -4015,603 +4301,603 @@ TEST_F(StreamAbstractionAAMP_MPDTest, GetFirstValidPeriodWithEmptyVector) // Test case to verify seeking behavior with VOD content, with start time, without duration and PTO greater than timeline TEST_F(FunctionalTests, L1_VOD_WithStartTime_NoDuration_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - + xmlns:cenc="urn:mpeg:cenc:2013" + type="static" minBufferTime="PT1.5S" mediaPresentationDuration="PT10M0S" profiles="urn:mpeg:dash:profile:isoff-live:2011"> + + + + + + + + + + + + + + )"; - // Enable mid-fragment seek + // Enable mid-fragment seek mBoolConfigSettings[eAAMPConfig_MidFragmentSeek] = true; - std::string fragmentUrl = std::string(TEST_BASE_URL) + std::string("out-v1.dash"); - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + std::string fragmentUrl = std::string(TEST_BASE_URL) + std::string("out-v1.dash"); + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - double seekPosition = 0.0; - int rate = 1; - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); + double seekPosition = 0.0; + int rate = 1; + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); - // After seek to 0, stream position should be 0 + // After seek to 0, stream position should be 0 double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); cout<<"Actual Position: "< - - - - - - - - - - - - - - + xmlns:cenc="urn:mpeg:cenc:2013" + type="dynamic" + availabilityStartTime="2023-01-01T00:00:00Z" + minBufferTime="PT1.5S" + timeShiftBufferDepth="PT5M" + suggestedPresentationDelay="PT10S" + mediaPresentationDuration="PT10M0S" + profiles="urn:mpeg:dash:profile:isoff-live:2011"> + + + + + + + + + + + + + + )"; - // Enable mid-fragment seek - mBoolConfigSettings[eAAMPConfig_MidFragmentSeek] = true; + // Enable mid-fragment seek + mBoolConfigSettings[eAAMPConfig_MidFragmentSeek] = true; - std::string fragmentUrl = std::string(TEST_BASE_URL) + "out-v1.dash"; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + std::string fragmentUrl = std::string(TEST_BASE_URL) + "out-v1.dash"; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - double seekPosition = 0.0; - int rate = 1; - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); + double seekPosition = 0.0; + int rate = 1; + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); double expectedPosition = 1.6725312e+09;//AvailabilityStartTime as it is a liveStream - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - EXPECT_NEAR(actualPosition, expectedPosition, 0.001); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + EXPECT_NEAR(actualPosition, expectedPosition, 0.001); } // Test case to verify seeking behavior with VOD period, with out duration, with out start and PTO greater than timeline TEST_F(FunctionalTests, L1_VOD_Period_NoDuration_NoStart_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - + xmlns:cenc="urn:mpeg:cenc:2013" + type="static" minBufferTime="PT1.5S" mediaPresentationDuration="PT10M0S" profiles="urn:mpeg:dash:profile:isoff-live:2011"> + + + + + + + + + + + + + + )"; - // Enable mid-fragment seek - mBoolConfigSettings[eAAMPConfig_MidFragmentSeek] = true; + // Enable mid-fragment seek + mBoolConfigSettings[eAAMPConfig_MidFragmentSeek] = true; - std::string fragmentUrl = std::string(TEST_BASE_URL) + std::string("out-v1.dash"); - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + std::string fragmentUrl = std::string(TEST_BASE_URL) + std::string("out-v1.dash"); + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - double seekPosition = 0.0; - int rate = 1; - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); + double seekPosition = 0.0; + int rate = 1; + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); - // After seek to 0, stream position should be 0 - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - cout<<"Actual Position: "<GetStreamPosition(); + cout<<"Actual Position: "< - - - - - - - - - - - - - - + xmlns:cenc="urn:mpeg:cenc:2013" + type="dynamic" + availabilityStartTime="2023-01-01T00:00:00Z" + minBufferTime="PT1.5S" + timeShiftBufferDepth="PT5M" + suggestedPresentationDelay="PT10S" + mediaPresentationDuration="PT10M0S" + profiles="urn:mpeg:dash:profile:isoff-live:2011"> + + + + + + + + + + + + + + )"; - // Enable mid-fragment seek - mBoolConfigSettings[eAAMPConfig_MidFragmentSeek] = true; + // Enable mid-fragment seek + mBoolConfigSettings[eAAMPConfig_MidFragmentSeek] = true; - std::string fragmentUrl = std::string(TEST_BASE_URL) + "out-v1.dash"; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) - .WillOnce(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + std::string fragmentUrl = std::string(TEST_BASE_URL) + "out-v1.dash"; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(fragmentUrl, _, _, _, _, true, _, _, _)) + .WillOnce(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - double seekPosition = 0.0; - int rate = 1; - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); - double expectedPosition = 1.6725312e+09;//AvailabilityStartTime as it is a liveStream + double seekPosition = 0.0; + int rate = 1; + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); + double expectedPosition = 1.6725312e+09;//AvailabilityStartTime as it is a liveStream - // After seek to 0, verify stream position is near the expected availabilityStartTime - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + // After seek to 0, verify stream position is near the expected availabilityStartTime + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - EXPECT_NEAR(actualPosition, expectedPosition, 0.001); + EXPECT_NEAR(actualPosition, expectedPosition, 0.001); } // Test case to verify seeking behavior with three periods, with out start times, with out durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_NoStartTimes_NoDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 1; - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); + double seekPosition = 1; + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - EXPECT_EQ(actualPosition, seekPosition); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + EXPECT_EQ(actualPosition, seekPosition); } // Test case to verify seeking behavior with three periods, with out start times, with durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_NoStartTimes_WithDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 15 * 60; // Seek to 15 minutes (middle of period p1) - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); + double seekPosition = 15 * 60; // Seek to 15 minutes (middle of period p1) + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - EXPECT_EQ(actualPosition, seekPosition); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + EXPECT_EQ(actualPosition, seekPosition); } // Test case to verify seeking behavior with three periods, with start time and durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_WithStartTimesAndDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 0; - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); + double seekPosition = 0; + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - EXPECT_EQ(actualPosition, seekPosition); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + EXPECT_EQ(actualPosition, seekPosition); } // Test case to verify seeking behavior with three periods, with start times, with out durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_WithStartTimesNoDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 10 * 60; // Seek to 10 minutes (middle of period p1) - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); + double seekPosition = 10 * 60; // Seek to 10 minutes (middle of period p1) + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - EXPECT_EQ(actualPosition, seekPosition); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + EXPECT_EQ(actualPosition, seekPosition); } // Test case to verify seeking behavior for Live content with three periods, with out start times, with out durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_Live_NoStartTimes_NoDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 1; - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); - EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); + double seekPosition = 1; + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); + EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); } // Test case to verify seeking behavior for Live content with three periods, with out start times, with durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_Live_NoStartTimes_WithDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 15 * 60; // Seek to 15 minutes (middle of period p1) - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); - EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); + double seekPosition = 15 * 60; // Seek to 15 minutes (middle of period p1) + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); + EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); } // Test case to verify seeking behavior for Live content with three periods, with start times and durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_Live_WithStartTimesAndDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 0; - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); - EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); + double seekPosition = 0; + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); + EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); } // Test case to verify seeking behavior for Live content with three periods, with start times, with out durations, and PTO greater than timeline TEST_F(FunctionalTests, L1_ThreePeriods_Live_WithStartTimesNoDurations_PTOGreaterThanTimeline) { - AAMPStatusType status; - static const char *manifest = + AAMPStatusType status; + static const char *manifest = R"( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; - double seekPosition = 10 * 60; // Seek to 10 minutes (middle of period p1) - int rate = 1; - EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) - .WillRepeatedly(Return(true)); - EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); - status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); - EXPECT_EQ(status, eAAMPSTATUS_OK); - double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); - double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); - EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); + double seekPosition = 10 * 60; // Seek to 10 minutes (middle of period p1) + int rate = 1; + EXPECT_CALL(*g_mockMediaStreamContext, CacheFragment(_, _, _, _, _, true, _, _, _)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*g_mockPrivateInstanceAAMP, SetLLDashChunkMode(_)); + status = InitializeMPD(manifest, TuneType::eTUNETYPE_SEEK, seekPosition, rate, true); + EXPECT_EQ(status, eAAMPSTATUS_OK); + double actualPosition = mStreamAbstractionAAMP_MPD->GetStreamPosition(); + double availabilityStartTime = ISO8601DateTimeToUTCSeconds("2025-11-15T00:00:00Z"); + EXPECT_EQ(actualPosition, availabilityStartTime + seekPosition); }