Load asset list 3 target durations before ad start time

This makes sure that the asset list is loaded about the
same duration before the ad start as on other platforms.

Further, interstitials in live streams are taken into
account when 3 target durations before the live window.
This enables the ads loader to preload such ads in a
future change. This may be indicated for live streams
that should play as close to the live edge as possible.
In such a case the buffered position may reach the start
of the ad only late. Preloading could help here to
decrease the performance requirements of ad servers.

PiperOrigin-RevId: 746521261
This commit is contained in:
bachinger 2025-04-11 10:59:13 -07:00 committed by Copybara-Service
parent 6f5982792a
commit 21faf85382
2 changed files with 33 additions and 23 deletions

View File

@ -786,11 +786,11 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
nextAssetResolution.adStartTimeUs != Long.MAX_VALUE
? nextAssetResolution.adStartTimeUs
: window.durationUs;
// Load 2 times the target duration before the ad starts.
// Load 3 times the target duration before the ad starts.
resolutionStartTimeUs =
max(
currentPeriodPositionUs,
resolutionStartTimeUs - (2 * nextAssetResolution.targetDurationUs));
resolutionStartTimeUs - (3 * nextAssetResolution.targetDurationUs));
if (resolutionStartTimeUs - currentPeriodPositionUs < 200_000L) {
// Start loading immediately.
nextAssetResolution.run();
@ -923,9 +923,11 @@ public final class HlsInterstitialsAdsLoader implements AdsLoader {
}
long positionInPlaylistWindowUs =
resolveInterstitialStartTimeUs(interstitial, mediaPlaylist) - mediaPlaylist.startTimeUs;
if (positionInPlaylistWindowUs < 0 || mediaPlaylist.durationUs < positionInPlaylistWindowUs) {
if (positionInPlaylistWindowUs < 0
|| mediaPlaylist.durationUs + (3 * mediaPlaylist.targetDurationUs)
< positionInPlaylistWindowUs) {
// Ignore when behind the window including C.TIME_UNSET and C.TIME_END_OF_SOURCE.
// When not yet in the window we wait until the window advances.
// When far in the future before the window, we wait until the window advances.
continue;
}
long timeUs = windowPositionInPeriodUs + positionInPlaylistWindowUs;

View File

@ -2195,7 +2195,7 @@ public class HlsInterstitialsAdsLoaderTest {
runMainLooperUntil(assetListLoadingListener::completed, TIMEOUT_MS, Clock.DEFAULT);
verify(mockAdsLoaderListener)
.onAssetListLoadCompleted(eq(contentMediaItem), eq("adsId"), eq(0), eq(0), any());
assertThat(midRollPlayerMessage.getPositionMs()).isEqualTo(12_000L);
assertThat(midRollPlayerMessage.getPositionMs()).isEqualTo(3_000L);
assertThat(midRollPlayerMessage.getPayload()).isEqualTo(contentMediaItem);
assertThat(midRollPlayerMessage.getLooper()).isEqualTo(Looper.myLooper());
InOrder inOrder = inOrder(mockPlayer);
@ -2289,8 +2289,12 @@ public class HlsInterstitialsAdsLoaderTest {
+ "#EXT-X-PROGRAM-DATE-TIME:2020-01-02T21:00:00.000Z\n"
+ "#EXTINF:9,\n"
+ "main0.0.ts\n"
+ "#EXTINF:16.001,\n"
+ "main0.0.ts\n"
+ "#EXTINF:9,\n"
+ "main1.0.ts\n"
+ "#EXTINF:9,\n"
+ "main2.0.ts\n"
+ "#EXTINF:7.001,\n"
+ "main3.0.ts\n"
+ "#EXT-X-ENDLIST"
+ "\n"
+ "#EXT-X-DATERANGE:"
@ -2303,7 +2307,7 @@ public class HlsInterstitialsAdsLoaderTest {
+ "#EXT-X-DATERANGE:"
+ "ID=\"ad1-0\","
+ "CLASS=\"com.apple.hls.interstitial\","
+ "START-DATE=\"2020-01-02T21:00:25.000Z\","
+ "START-DATE=\"2020-01-02T21:00:34.000Z\","
+ "X-ASSET-LIST=\"http://example.com/assetlist-1-0.json\""
+ "\n";
when(mockPlayer.getContentPosition()).thenReturn(0L);
@ -2317,7 +2321,7 @@ public class HlsInterstitialsAdsLoaderTest {
/* Looper ignored */ null);
when(mockPlayer.createMessage(any())).thenReturn(midRollPlayerMessage);
AdPlaybackState expectedAdPlaybackStateAtTimelineChange =
new AdPlaybackState("adsId", 0L, 25_000_000L)
new AdPlaybackState("adsId", 0L, 34_000_000L)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdCount(/* adGroupIndex= */ 1, 1)
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
@ -2419,7 +2423,7 @@ public class HlsInterstitialsAdsLoaderTest {
expectedAdPlaybackStateAtTimelineChange.withAdLoadError(
/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0))
.inOrder();
assertThat(midRollPlayerMessage.getPositionMs()).isEqualTo(33_000L);
assertThat(midRollPlayerMessage.getPositionMs()).isEqualTo(24_000L);
InOrder inOrder = inOrder(mockPlayer);
inOrder.verify(mockPlayer).addListener(any());
verifyTimelineUpdate(inOrder, mockPlayer, /* verifyMessageScheduled= */ false);
@ -2590,15 +2594,17 @@ public class HlsInterstitialsAdsLoaderTest {
+ "#EXTINF:9,\n"
+ "main0.0.ts\n"
+ "#EXTINF:9,\n"
+ "main0.0.ts\n"
+ "main1.0.ts\n"
+ "#EXTINF:9,\n"
+ "main0.0.ts\n"
+ "main2.0.ts\n"
+ "#EXTINF:9,\n"
+ "main3.0.ts\n"
+ "#EXT-X-ENDLIST"
+ "\n"
+ "#EXT-X-DATERANGE:"
+ "ID=\"ad0-0\","
+ "CLASS=\"com.apple.hls.interstitial\","
+ "START-DATE=\"2020-01-02T21:00:21.000Z\","
+ "START-DATE=\"2020-01-02T21:00:30.000Z\","
+ "X-ASSET-LIST=\"http://example.com/assetlist-0-0.json\""
+ "\n";
when(mockPlayer.getContentPosition()).thenReturn(0L);
@ -2691,7 +2697,7 @@ public class HlsInterstitialsAdsLoaderTest {
runMainLooperUntil(assetListLoadingListener::completed, TIMEOUT_MS, Clock.DEFAULT);
assertThat(midRoll2PlayerMessage.isCanceled()).isFalse();
assertThat(midRoll2PlayerMessage.getPositionMs()).isEqualTo(36_000L);
assertThat(midRoll2PlayerMessage.getPositionMs()).isEqualTo(27_000L);
assertThat(midRoll2PlayerMessage.getPayload()).isEqualTo(contentMediaItem);
assertThat(midRoll2PlayerMessage.getLooper()).isEqualTo(Looper.myLooper());
}
@ -2848,15 +2854,15 @@ public class HlsInterstitialsAdsLoaderTest {
TIMEOUT_MS,
Clock.DEFAULT);
assertThat(midRoll1PlayerMessage.isCanceled()).isTrue();
assertThat(midRoll1PlayerMessage.getPositionMs()).isEqualTo(12_000L);
assertThat(midRoll1PlayerMessage.getPositionMs()).isEqualTo(3_000L);
assertThat(midRoll1PlayerMessage.getPayload()).isEqualTo(contentMediaItem);
assertThat(midRoll1PlayerMessage.getLooper()).isEqualTo(Looper.myLooper());
assertThat(midRoll2PlayerMessage.isCanceled()).isTrue();
assertThat(midRoll2PlayerMessage.getPositionMs()).isEqualTo(36_000L);
assertThat(midRoll2PlayerMessage.getPositionMs()).isEqualTo(27_000L);
assertThat(midRoll2PlayerMessage.getPayload()).isEqualTo(contentMediaItem);
assertThat(midRoll1PlayerMessage.getLooper()).isEqualTo(Looper.myLooper());
assertThat(postRollPlayerMessage.isCanceled()).isFalse();
assertThat(postRollPlayerMessage.getPositionMs()).isEqualTo(72_000L);
assertThat(postRollPlayerMessage.getPositionMs()).isEqualTo(63_000L);
assertThat(postRollPlayerMessage.getPayload()).isEqualTo(contentMediaItem);
assertThat(midRoll1PlayerMessage.getLooper()).isEqualTo(Looper.myLooper());
ArgumentCaptor<AssetList> argumentCaptor = ArgumentCaptor.forClass(AssetList.class);
@ -2907,7 +2913,7 @@ public class HlsInterstitialsAdsLoaderTest {
+ "#EXT-X-DATERANGE:"
+ "ID=\"ad0-0\","
+ "CLASS=\"com.apple.hls.interstitial\","
+ "START-DATE=\"2020-01-02T21:00:21.000Z\","
+ "START-DATE=\"2020-01-02T21:00:30.000Z\","
+ "X-ASSET-LIST=\"http://example.com/assetlist-0-0.json\""
+ "\n"
+ "#EXT-X-DATERANGE:"
@ -2920,7 +2926,7 @@ public class HlsInterstitialsAdsLoaderTest {
Object periodUid = new Object();
when(mockPlayer.getContentPosition()).thenReturn(0L);
AdPlaybackState expectedAdPlaybackStateAtTimelineChange =
new AdPlaybackState("adsId", 21_000_000L, 51_000_000L)
new AdPlaybackState("adsId", 30_000_000L, 51_000_000L)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdCount(/* adGroupIndex= */ 1, 1)
.withAdId(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0, "ad0-0")
@ -2983,7 +2989,7 @@ public class HlsInterstitialsAdsLoaderTest {
assertThat(midRoll1PlayerMessage.getPositionMs()).isEqualTo(3_000L);
assertThat(midRoll1PlayerMessage.getPayload()).isEqualTo(contentMediaItem);
assertThat(midRoll2PlayerMessage.isCanceled()).isFalse();
assertThat(midRoll2PlayerMessage.getPositionMs()).isEqualTo(33_000L);
assertThat(midRoll2PlayerMessage.getPositionMs()).isEqualTo(24_000L);
assertThat(midRoll2PlayerMessage.getPayload()).isEqualTo(contentMediaItem);
ArgumentCaptor<AdPlaybackState> adPlaybackStateCaptor =
ArgumentCaptor.forClass(AdPlaybackState.class);
@ -3015,15 +3021,17 @@ public class HlsInterstitialsAdsLoaderTest {
+ "#EXTINF:9,\n"
+ "main0.0.ts\n"
+ "#EXTINF:9,\n"
+ "main0.0.ts\n"
+ "main1.0.ts\n"
+ "#EXTINF:9,\n"
+ "main0.0.ts\n"
+ "main2.0.ts\n"
+ "#EXTINF:9,\n"
+ "main3.0.ts\n"
+ "#EXT-X-ENDLIST"
+ "\n"
+ "#EXT-X-DATERANGE:"
+ "ID=\"ad0-0\","
+ "CLASS=\"com.apple.hls.interstitial\","
+ "START-DATE=\"2020-01-02T21:00:21.000Z\","
+ "START-DATE=\"2020-01-02T21:00:30.000Z\","
+ "X-ASSET-LIST=\"http://example.com/assetlist-0-0.json\""
+ "\n";
when(mockPlayer.getContentPosition()).thenReturn(0L);