3737import android .view .Surface ;
3838import androidx .test .core .app .ApplicationProvider ;
3939import androidx .test .ext .junit .runners .AndroidJUnit4 ;
40+ import com .google .android .exoplayer2 .C ;
4041import com .google .android .exoplayer2 .ExoPlayer ;
4142import com .google .android .exoplayer2 .MediaItem ;
4243import com .google .android .exoplayer2 .Player ;
4647import com .google .android .exoplayer2 .robolectric .PlaybackOutput ;
4748import com .google .android .exoplayer2 .robolectric .ShadowMediaCodecConfig ;
4849import com .google .android .exoplayer2 .source .DefaultMediaSourceFactory ;
50+ import com .google .android .exoplayer2 .source .SinglePeriodTimeline ;
4951import com .google .android .exoplayer2 .testutil .CapturingRenderersFactory ;
5052import com .google .android .exoplayer2 .testutil .DumpFileAsserts ;
5153import 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