Skip to content

Commit 5def6e4

Browse files
toniheimicrokatz
authored andcommitted
Calculate SSAI window duration for live periods with unset duration.
We currently skip this calculation entirely, but it can be added by calculating the window duration using the wrapped window's duration and the provided AdPlaybackState. Issue: #10764 PiperOrigin-RevId: 488614767 (cherry picked from commit 20151b9)
1 parent 54d3dbf commit 5def6e4

File tree

2 files changed

+98
-8
lines changed

2 files changed

+98
-8
lines changed

library/core/src/main/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionMediaSource.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,8 +1016,9 @@ public ServerSideAdInsertionTimeline(
10161016
@Override
10171017
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
10181018
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
1019+
Period period = new Period();
10191020
Object firstPeriodUid =
1020-
checkNotNull(getPeriod(window.firstPeriodIndex, new Period(), /* setIds= */ true).uid);
1021+
checkNotNull(getPeriod(window.firstPeriodIndex, period, /* setIds= */ true).uid);
10211022
AdPlaybackState firstAdPlaybackState = checkNotNull(adPlaybackStates.get(firstPeriodUid));
10221023
long positionInPeriodUs =
10231024
getMediaPeriodPositionUsForContent(
@@ -1029,11 +1030,21 @@ public Window getWindow(int windowIndex, Window window, long defaultPositionProj
10291030
window.durationUs = firstAdPlaybackState.contentDurationUs - positionInPeriodUs;
10301031
}
10311032
} else {
1032-
Period lastPeriod = getPeriod(/* periodIndex= */ window.lastPeriodIndex, new Period());
1033+
Period originalLastPeriod =
1034+
super.getPeriod(/* periodIndex= */ window.lastPeriodIndex, period, /* setIds= */ true);
1035+
long originalLastPeriodPositionInWindowUs = originalLastPeriod.positionInWindowUs;
1036+
AdPlaybackState lastAdPlaybackState =
1037+
checkNotNull(adPlaybackStates.get(originalLastPeriod.uid));
1038+
Period adjustedLastPeriod = getPeriod(/* periodIndex= */ window.lastPeriodIndex, period);
1039+
long originalWindowDurationInLastPeriodUs =
1040+
window.durationUs - originalLastPeriodPositionInWindowUs;
1041+
long adjustedWindowDurationInLastPeriodUs =
1042+
getMediaPeriodPositionUsForContent(
1043+
originalWindowDurationInLastPeriodUs,
1044+
/* nextAdGroupIndex= */ C.INDEX_UNSET,
1045+
lastAdPlaybackState);
10331046
window.durationUs =
1034-
lastPeriod.durationUs == C.TIME_UNSET
1035-
? C.TIME_UNSET
1036-
: lastPeriod.positionInWindowUs + lastPeriod.durationUs;
1047+
adjustedLastPeriod.positionInWindowUs + adjustedWindowDurationInLastPeriodUs;
10371048
}
10381049
window.positionInFirstPeriodUs = positionInPeriodUs;
10391050
return window;

library/core/src/test/java/com/google/android/exoplayer2/source/ads/ServerSideAdInsertionMediaSourceTest.java

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import android.view.Surface;
3838
import androidx.test.core.app.ApplicationProvider;
3939
import androidx.test.ext.junit.runners.AndroidJUnit4;
40+
import com.google.android.exoplayer2.C;
4041
import com.google.android.exoplayer2.ExoPlayer;
4142
import com.google.android.exoplayer2.MediaItem;
4243
import com.google.android.exoplayer2.Player;
@@ -46,6 +47,7 @@
4647
import com.google.android.exoplayer2.robolectric.PlaybackOutput;
4748
import com.google.android.exoplayer2.robolectric.ShadowMediaCodecConfig;
4849
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
50+
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
4951
import com.google.android.exoplayer2.testutil.CapturingRenderersFactory;
5052
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
5153
import com.google.android.exoplayer2.testutil.FakeClock;
@@ -70,15 +72,15 @@ public final class ServerSideAdInsertionMediaSourceTest {
7072
private static final String TEST_ASSET_DUMP = "playbackdumps/mp4/ssai-sample.mp4.dump";
7173

7274
@Test
73-
public void timeline_containsAdsDefinedInAdPlaybackState() throws Exception {
75+
public void timeline_vodSinglePeriod_containsAdsDefinedInAdPlaybackState() throws Exception {
7476
FakeTimeline wrappedTimeline =
7577
new FakeTimeline(
7678
new FakeTimeline.TimelineWindowDefinition(
7779
/* periodCount= */ 1,
7880
/* id= */ 0,
7981
/* isSeekable= */ true,
80-
/* isDynamic= */ true,
81-
/* isLive= */ true,
82+
/* isDynamic= */ false,
83+
/* isLive= */ false,
8284
/* isPlaceholder= */ false,
8385
/* durationUs= */ 10_000_000,
8486
/* defaultPositionUs= */ 3_000_000,
@@ -146,6 +148,83 @@ public void timeline_containsAdsDefinedInAdPlaybackState() throws Exception {
146148
assertThat(window.durationUs).isEqualTo(9_800_000);
147149
}
148150

151+
@Test
152+
public void timeline_liveSinglePeriodWithUnsetPeriodDuration_containsAdsDefinedInAdPlaybackState()
153+
throws Exception {
154+
Timeline wrappedTimeline =
155+
new SinglePeriodTimeline(
156+
/* periodDurationUs= */ C.TIME_UNSET,
157+
/* windowDurationUs= */ 10_000_000,
158+
/* windowPositionInPeriodUs= */ 42_000_000L,
159+
/* windowDefaultStartPositionUs= */ 3_000_000,
160+
/* isSeekable= */ true,
161+
/* isDynamic= */ true,
162+
/* useLiveConfiguration= */ true,
163+
/* manifest= */ null,
164+
/* mediaItem= */ MediaItem.EMPTY);
165+
ServerSideAdInsertionMediaSource mediaSource =
166+
new ServerSideAdInsertionMediaSource(
167+
new FakeMediaSource(wrappedTimeline), /* adPlaybackStateUpdater= */ null);
168+
// Test with one ad group before the window, and the window starting within the second ad group.
169+
AdPlaybackState adPlaybackState =
170+
new AdPlaybackState(
171+
/* adsId= */ new Object(), /* adGroupTimesUs= */ 15_000_000, 41_500_000, 42_200_000)
172+
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
173+
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
174+
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
175+
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
176+
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 2)
177+
.withAdCount(/* adGroupIndex= */ 2, /* adCount= */ 1)
178+
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs= */ 500_000)
179+
.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs= */ 300_000, 100_000)
180+
.withAdDurationsUs(/* adGroupIndex= */ 2, /* adDurationsUs= */ 400_000)
181+
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, /* contentResumeOffsetUs= */ 100_000)
182+
.withContentResumeOffsetUs(/* adGroupIndex= */ 1, /* contentResumeOffsetUs= */ 400_000)
183+
.withContentResumeOffsetUs(/* adGroupIndex= */ 2, /* contentResumeOffsetUs= */ 200_000);
184+
AtomicReference<Timeline> timelineReference = new AtomicReference<>();
185+
mediaSource.setAdPlaybackStates(
186+
ImmutableMap.of(
187+
wrappedTimeline.getPeriod(
188+
/* periodIndex= */ 0, new Timeline.Period(), /* setIds= */ true)
189+
.uid,
190+
adPlaybackState));
191+
192+
mediaSource.prepareSource(
193+
(source, timeline) -> timelineReference.set(timeline),
194+
/* mediaTransferListener= */ null,
195+
PlayerId.UNSET);
196+
runMainLooperUntil(() -> timelineReference.get() != null);
197+
198+
Timeline timeline = timelineReference.get();
199+
assertThat(timeline.getPeriodCount()).isEqualTo(1);
200+
Timeline.Period period = timeline.getPeriod(/* periodIndex= */ 0, new Timeline.Period());
201+
assertThat(period.getAdGroupCount()).isEqualTo(3);
202+
assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 0)).isEqualTo(1);
203+
assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 1)).isEqualTo(2);
204+
assertThat(period.getAdCountInAdGroup(/* adGroupIndex= */ 2)).isEqualTo(1);
205+
assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 0)).isEqualTo(15_000_000);
206+
assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 1)).isEqualTo(41_500_000);
207+
assertThat(period.getAdGroupTimeUs(/* adGroupIndex= */ 2)).isEqualTo(42_200_000);
208+
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
209+
.isEqualTo(500_000);
210+
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0))
211+
.isEqualTo(300_000);
212+
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 1))
213+
.isEqualTo(100_000);
214+
assertThat(period.getAdDurationUs(/* adGroupIndex= */ 2, /* adIndexInAdGroup= */ 0))
215+
.isEqualTo(400_000);
216+
assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 0)).isEqualTo(100_000);
217+
assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 1)).isEqualTo(400_000);
218+
assertThat(period.getContentResumeOffsetUs(/* adGroupIndex= */ 2)).isEqualTo(200_000);
219+
assertThat(period.getDurationUs()).isEqualTo(C.TIME_UNSET);
220+
// positionInWindowUs + sum(adDurationsBeforeWindow) - sum(contentResumeOffsetsBeforeWindow)
221+
assertThat(period.getPositionInWindowUs()).isEqualTo(-41_600_000);
222+
Timeline.Window window = timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window());
223+
assertThat(window.positionInFirstPeriodUs).isEqualTo(41_600_000);
224+
// windowDurationUs - sum(adDurationsInWindow) + sum(applicableContentResumeOffsetUs)
225+
assertThat(window.durationUs).isEqualTo(9_800_000);
226+
}
227+
149228
@Test
150229
public void timeline_missingAdPlaybackStateByPeriodUid_isAssertedAndThrows() {
151230
ServerSideAdInsertionMediaSource mediaSource =

0 commit comments

Comments
 (0)