Commit a7b02027 by bachinger Committed by Ian Baker

Resolve media period ids in multi-period windows

Ignorable ad periods are skipped to resolve the media period id with the
ad playback state of the resulting period. In case of a change in the period
position un-played ad periods are rolled forward to be played.

PiperOrigin-RevId: 428011116
parent 00dbb780
......@@ -524,6 +524,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
adPlaybackState,
/* fromPositionUs= */ secToUs(cuePoint.getStartTime()),
/* contentResumeOffsetUs= */ 0,
// TODO(b/192231683) Use getEndTimeMs()/getStartTimeMs() after jar target was removed
/* adDurationsUs...= */ secToUs(cuePoint.getEndTime() - cuePoint.getStartTime()));
}
return adPlaybackState;
......
......@@ -438,12 +438,13 @@ import java.util.Set;
new AdPlaybackState(checkNotNull(adsId), /* adGroupTimesUs...= */ 0)
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdDurationsUs(/* adGroupIndex= */ 0, periodDurationUs)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
.withContentResumeOffsetUs(/* adGroupIndex= */ 0, adGroup.contentResumeOffsetUs);
long periodEndUs = periodStartUs + periodDurationUs;
long adDurationsUs = 0;
for (int i = 0; i < adGroup.count; i++) {
adDurationsUs += adGroup.durationsUs[i];
if (periodEndUs == adGroup.timeUs + adDurationsUs) {
if (periodEndUs <= adGroup.timeUs + adDurationsUs + 10_000) {
// Map the state of the global ad state to the period specific ad state.
switch (adGroup.states[i]) {
case AdPlaybackState.AD_STATE_PLAYED:
......
......@@ -1365,7 +1365,9 @@ public final class ImaAdsLoaderTest {
}
private AdPlaybackState getAdPlaybackState(int periodIndex) {
return timelineWindowDefinitions[periodIndex].adPlaybackState;
int adPlaybackStateCount = timelineWindowDefinitions[periodIndex].adPlaybackStates.size();
return timelineWindowDefinitions[periodIndex].adPlaybackStates.get(
periodIndex % adPlaybackStateCount);
}
private static AdEvent getAdEvent(AdEventType adEventType, @Nullable Ad ad) {
......@@ -1408,7 +1410,11 @@ public final class ImaAdsLoaderTest {
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
TimelineWindowDefinition timelineWindowDefinition = timelineWindowDefinitions[periodIndex];
assertThat(adPlaybackState.adsId).isEqualTo(timelineWindowDefinition.adPlaybackState.adsId);
assertThat(adPlaybackState.adsId)
.isEqualTo(
timelineWindowDefinition.adPlaybackStates.get(
periodIndex % timelineWindowDefinition.adPlaybackStates.size())
.adsId);
timelineWindowDefinitions[periodIndex] =
new TimelineWindowDefinition(
timelineWindowDefinition.periodCount,
......
......@@ -459,6 +459,52 @@ public class ImaUtilTest {
}
@Test
public void splitAdPlaybackStateForPeriods_singleAdOfAdGroupSpansMultiplePeriods_correctState() {
int periodCount = 8;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
AdPlaybackState adPlaybackState =
new AdPlaybackState(/* adsId= */ "adsId", 0, periodDurationUs, 2 * periodDurationUs)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAdCount(/* adGroupIndex= */ 1, 1)
.withAdCount(/* adGroupIndex= */ 2, 1)
.withAdDurationsUs(
/* adGroupIndex= */ 0, /* adDurationsUs...= */
DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US + (2 * periodDurationUs))
.withAdDurationsUs(
/* adGroupIndex= */ 1, /* adDurationsUs...= */ (2 * periodDurationUs))
.withAdDurationsUs(
/* adGroupIndex= */ 2, /* adDurationsUs...= */ (2 * periodDurationUs))
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
.withPlayedAd(/* adGroupIndex= */ 1, /* adIndexInAdGroup= */ 0)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
.withIsServerSideInserted(/* adGroupIndex= */ 1, true)
.withIsServerSideInserted(/* adGroupIndex= */ 2, true);
FakeTimeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ periodCount, /* id= */ 0L));
ImmutableMap<Object, AdPlaybackState> adPlaybackStates =
ImaUtil.splitAdPlaybackStateForPeriods(adPlaybackState, timeline);
assertThat(adPlaybackStates).hasSize(periodCount);
assertThat(adPlaybackStates.get(new Pair<>(0L, 0)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
assertThat(adPlaybackStates.get(new Pair<>(0L, 1)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
assertThat(adPlaybackStates.get(new Pair<>(0L, 2)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 3)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
assertThat(adPlaybackStates.get(new Pair<>(0L, 4)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
assertThat(adPlaybackStates.get(new Pair<>(0L, 5)).adGroupCount).isEqualTo(0);
assertThat(adPlaybackStates.get(new Pair<>(0L, 6)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
assertThat(adPlaybackStates.get(new Pair<>(0L, 7)).getAdGroup(/* adGroupIndex= */ 0).states[0])
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
}
@Test
public void splitAdPlaybackStateForPeriods_lateMidrollAdGroupStartTimeUs_adGroupIgnored() {
int periodCount = 4;
long periodDurationUs = DEFAULT_WINDOW_DURATION_US / periodCount;
......
......@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -222,7 +223,7 @@ public class TimelineTest {
/* durationUs= */ 2,
/* defaultPositionUs= */ 22,
/* windowOffsetInFirstPeriodUs= */ 222,
AdPlaybackState.NONE,
ImmutableList.of(AdPlaybackState.NONE),
new MediaItem.Builder().setMediaId("mediaId2").build()),
new TimelineWindowDefinition(
/* periodCount= */ 3,
......@@ -234,7 +235,7 @@ public class TimelineTest {
/* durationUs= */ 3,
/* defaultPositionUs= */ 33,
/* windowOffsetInFirstPeriodUs= */ 333,
AdPlaybackState.NONE,
ImmutableList.of(AdPlaybackState.NONE),
new MediaItem.Builder().setMediaId("mediaId3").build()));
Timeline restoredTimeline = Timeline.CREATOR.fromBundle(timeline.toBundle());
......
......@@ -1170,7 +1170,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
requestedContentPositionUs =
seekPosition.windowPositionUs == C.TIME_UNSET ? C.TIME_UNSET : resolvedContentPositionUs;
periodId =
queue.resolveMediaPeriodIdForAds(
queue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
playbackInfo.timeline, periodUid, resolvedContentPositionUs);
if (periodId.isAd()) {
playbackInfo.timeline.getPeriodByUid(periodId.periodUid, period);
......@@ -1484,7 +1484,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
window, period, firstWindowIndex, /* windowPositionUs= */ C.TIME_UNSET);
// Add ad metadata if any and propagate the window sequence number to new period id.
MediaPeriodId firstPeriodId =
queue.resolveMediaPeriodIdForAds(
queue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
timeline, firstPeriodAndPositionUs.first, /* positionUs= */ 0);
long positionUs = firstPeriodAndPositionUs.second;
if (firstPeriodId.isAd()) {
......@@ -2346,7 +2346,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private PlaybackInfo handlePositionDiscontinuity(
MediaPeriodId mediaPeriodId,
long positionUs,
long contentPositionUs,
long requestedContentPositionUs,
long discontinuityStartPositionUs,
boolean reportDiscontinuity,
@DiscontinuityReason int discontinuityReason) {
......@@ -2371,9 +2371,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
staticMetadata = extractMetadataFromTrackSelectionArray(trackSelectorResult.selections);
// Ensure the media period queue requested content position matches the new playback info.
if (playingPeriodHolder != null
&& playingPeriodHolder.info.requestedContentPositionUs != contentPositionUs) {
&& playingPeriodHolder.info.requestedContentPositionUs != requestedContentPositionUs) {
playingPeriodHolder.info =
playingPeriodHolder.info.copyWithRequestedContentPositionUs(contentPositionUs);
playingPeriodHolder.info.copyWithRequestedContentPositionUs(requestedContentPositionUs);
}
} else if (!mediaPeriodId.equals(playbackInfo.periodId)) {
// Reset previously kept track info if unprepared and the period changes.
......@@ -2387,7 +2387,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
return playbackInfo.copyWithNewPosition(
mediaPeriodId,
positionUs,
contentPositionUs,
requestedContentPositionUs,
discontinuityStartPositionUs,
getTotalBufferedDurationUs(),
trackGroupArray,
......@@ -2660,7 +2660,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
// Ensure ad insertion metadata is up to date.
MediaPeriodId periodIdWithAds =
queue.resolveMediaPeriodIdForAds(timeline, newPeriodUid, contentPositionForAdResolutionUs);
queue.resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
timeline, newPeriodUid, contentPositionForAdResolutionUs);
boolean earliestCuePointIsUnchangedOrLater =
periodIdWithAds.nextAdGroupIndex == C.INDEX_UNSET
|| (oldPeriodId.nextAdGroupIndex != C.INDEX_UNSET
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max;
import android.os.Handler;
......@@ -444,21 +445,7 @@ import com.google.common.collect.ImmutableList;
Timeline timeline, Object periodUid, long positionUs) {
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
return resolveMediaPeriodIdForAds(
timeline, periodUid, positionUs, windowSequenceNumber, period);
}
// Internal methods.
private void notifyQueueUpdate() {
ImmutableList.Builder<MediaPeriodId> builder = ImmutableList.builder();
@Nullable MediaPeriodHolder period = playing;
while (period != null) {
builder.add(period.info.id);
period = period.getNext();
}
@Nullable MediaPeriodId readingPeriodId = reading == null ? null : reading.info.id;
analyticsCollectorHandler.post(
() -> analyticsCollector.updateMediaPeriodQueueInfo(builder.build(), readingPeriodId));
timeline, periodUid, positionUs, windowSequenceNumber, window, period);
}
/**
......@@ -479,8 +466,21 @@ import com.google.common.collect.ImmutableList;
Object periodUid,
long positionUs,
long windowSequenceNumber,
Timeline.Window window,
Timeline.Period period) {
timeline.getPeriodByUid(periodUid, period);
timeline.getWindow(period.windowIndex, window);
int periodIndex = timeline.getIndexOfPeriod(periodUid);
// Skip ignorable server side inserted ad periods.
while ((period.durationUs == 0
&& period.getAdGroupCount() > 0
&& period.isServerSideInsertedAdGroup(period.getRemovedAdGroupCount())
&& period.getAdGroupIndexForPositionUs(0) == C.INDEX_UNSET)
&& periodIndex++ < window.lastPeriodIndex) {
timeline.getPeriod(periodIndex, period, /* setIds= */ true);
periodUid = checkNotNull(period.uid);
}
timeline.getPeriodByUid(periodUid, period);
int adGroupIndex = period.getAdGroupIndexForPositionUs(positionUs);
if (adGroupIndex == C.INDEX_UNSET) {
int nextAdGroupIndex = period.getAdGroupIndexAfterPositionUs(positionUs);
......@@ -492,6 +492,55 @@ import com.google.common.collect.ImmutableList;
}
/**
* Resolves the specified timeline period and position to a {@link MediaPeriodId} that should be
* played after a period position change, returning an identifier for an ad group if one needs to
* be played before the specified position, or an identifier for a content media period if not.
*
* @param timeline The timeline the period is part of.
* @param periodUid The uid of the timeline period to play.
* @param positionUs The next content position in the period to play.
* @return The identifier for the first media period to play, taking into account unplayed ads.
*/
public MediaPeriodId resolveMediaPeriodIdForAdsAfterPeriodPositionChange(
Timeline timeline, Object periodUid, long positionUs) {
long windowSequenceNumber = resolvePeriodIndexToWindowSequenceNumber(timeline, periodUid);
// Check for preceding ad periods in multi-period window.
timeline.getPeriodByUid(periodUid, period);
timeline.getWindow(period.windowIndex, window);
Object periodUidToPlay = periodUid;
boolean seenAdPeriod = false;
for (int i = timeline.getIndexOfPeriod(periodUid); i >= window.firstPeriodIndex; i--) {
timeline.getPeriod(/* periodIndex= */ i, period, /* setIds= */ true);
boolean isAdPeriod = period.getAdGroupCount() > 0;
seenAdPeriod |= isAdPeriod;
if (period.getAdGroupIndexForPositionUs(period.durationUs) != C.INDEX_UNSET) {
// Roll forward to preceding un-played ad period.
periodUidToPlay = checkNotNull(period.uid);
}
if (seenAdPeriod && (!isAdPeriod || period.durationUs != 0)) {
// Stop for any periods except un-played ads with no content.
break;
}
}
return resolveMediaPeriodIdForAds(
timeline, periodUidToPlay, positionUs, windowSequenceNumber, window, period);
}
// Internal methods.
private void notifyQueueUpdate() {
ImmutableList.Builder<MediaPeriodId> builder = ImmutableList.builder();
@Nullable MediaPeriodHolder period = playing;
while (period != null) {
builder.add(period.info.id);
period = period.getNext();
}
@Nullable MediaPeriodId readingPeriodId = reading == null ? null : reading.info.id;
analyticsCollectorHandler.post(
() -> analyticsCollector.updateMediaPeriodQueueInfo(builder.build(), readingPeriodId));
}
/**
* Resolves the specified period uid to a corresponding window sequence number. Either by reusing
* the window sequence number of an existing matching media period or by creating a new window
* sequence number.
......@@ -645,12 +694,12 @@ import com.google.common.collect.ImmutableList;
// We can't create a next period yet.
return null;
}
long startPositionUs;
long contentPositionUs;
// We either start a new period in the same window or the first period in the next window.
long startPositionUs = 0;
long contentPositionUs = 0;
int nextWindowIndex =
timeline.getPeriod(nextPeriodIndex, period, /* setIds= */ true).windowIndex;
Object nextPeriodUid = period.uid;
Object nextPeriodUid = checkNotNull(period.uid);
long windowSequenceNumber = mediaPeriodInfo.id.windowSequenceNumber;
if (timeline.getWindow(nextWindowIndex, window).firstPeriodIndex == nextPeriodIndex) {
// We're starting to buffer a new window. When playback transitions to this window we'll
......@@ -670,20 +719,32 @@ import com.google.common.collect.ImmutableList;
}
nextPeriodUid = defaultPositionUs.first;
startPositionUs = defaultPositionUs.second;
MediaPeriodHolder nextMediaPeriodHolder = mediaPeriodHolder.getNext();
@Nullable MediaPeriodHolder nextMediaPeriodHolder = mediaPeriodHolder.getNext();
if (nextMediaPeriodHolder != null && nextMediaPeriodHolder.uid.equals(nextPeriodUid)) {
windowSequenceNumber = nextMediaPeriodHolder.info.id.windowSequenceNumber;
} else {
windowSequenceNumber = nextWindowSequenceNumber++;
}
} else {
// We're starting to buffer a new period within the same window.
startPositionUs = 0;
contentPositionUs = 0;
}
@Nullable
MediaPeriodId periodId =
resolveMediaPeriodIdForAds(
timeline, nextPeriodUid, startPositionUs, windowSequenceNumber, period);
timeline, nextPeriodUid, startPositionUs, windowSequenceNumber, window, period);
if (contentPositionUs != C.TIME_UNSET
&& mediaPeriodInfo.requestedContentPositionUs != C.TIME_UNSET) {
boolean isPrecedingPeriodAnAd =
timeline.getPeriodByUid(mediaPeriodInfo.id.periodUid, period).getAdGroupCount() > 0
&& period.isServerSideInsertedAdGroup(period.getRemovedAdGroupCount());
// Handle the requested content position for period transitions within the same window.
if (periodId.isAd() && isPrecedingPeriodAnAd) {
// Propagate the requested position to the following ad period in the same window.
contentPositionUs = mediaPeriodInfo.requestedContentPositionUs;
} else if (isPrecedingPeriodAnAd) {
// Use the requested content position of the preceding ad period as the start position.
startPositionUs = mediaPeriodInfo.requestedContentPositionUs;
}
}
return getMediaPeriodInfo(timeline, periodId, contentPositionUs, startPositionUs);
}
......
......@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
/** Fake {@link MediaSourceFactory} that creates a {@link FakeMediaSource}. */
// Implement and return deprecated type for backwards compatibility.
......@@ -64,7 +65,7 @@ public final class FakeMediaSourceFactory implements MediaSourceFactory {
/* durationUs= */ 1000 * C.MICROS_PER_SECOND,
/* defaultPositionUs= */ 2 * C.MICROS_PER_SECOND,
/* windowOffsetInFirstPeriodUs= */ Util.msToUs(123456789),
AdPlaybackState.NONE,
ImmutableList.of(AdPlaybackState.NONE),
mediaItem);
return new FakeMediaSource(new FakeTimeline(timelineWindowDefinition));
}
......
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_DURATION_US;
import static com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ads.AdPlaybackState;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link FakeTimeline}. */
@RunWith(AndroidJUnit4.class)
public class FakeTimelineTest {
@Test
public void createMultiPeriodAdTimeline_firstPeriodIsAd() {
Timeline.Window window = new Timeline.Window();
Timeline.Period period = new Timeline.Period();
Object windowId = new Object();
int numberOfPlayedAds = 2;
FakeTimeline timeline =
FakeTimeline.createMultiPeriodAdTimeline(
windowId,
numberOfPlayedAds,
/* isAdPeriodFlags...= */ true,
false,
true,
true,
true,
false,
true);
assertThat(timeline.getWindowCount()).isEqualTo(1);
assertThat(timeline.getPeriodCount()).isEqualTo(7);
// Assert content periods and window duration.
Timeline.Period contentPeriod1 = timeline.getPeriod(/* periodIndex= */ 1, period);
Timeline.Period contentPeriod5 = timeline.getPeriod(/* periodIndex= */ 5, period);
assertThat(contentPeriod1.durationUs).isEqualTo(DEFAULT_WINDOW_DURATION_US / 7);
assertThat(contentPeriod5.durationUs).isEqualTo(DEFAULT_WINDOW_DURATION_US / 7);
assertThat(contentPeriod1.getAdGroupCount()).isEqualTo(0);
assertThat(contentPeriod5.getAdGroupCount()).isEqualTo(0);
timeline.getWindow(/* windowIndex= */ 0, window);
assertThat(window.uid).isEqualTo(windowId);
assertThat(window.durationUs).isEqualTo(contentPeriod1.durationUs + contentPeriod5.durationUs);
assertThat(window.positionInFirstPeriodUs).isEqualTo(DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US);
// Assert ad periods.
int[] adIndices = {0, 2, 3, 4, 6};
int adCounter = 0;
for (int periodIndex : adIndices) {
Timeline.Period adPeriod = timeline.getPeriod(periodIndex, period);
assertThat(adPeriod.isServerSideInsertedAdGroup(0)).isTrue();
assertThat(adPeriod.getAdGroupCount()).isEqualTo(1);
assertThat(adPeriod.durationUs).isEqualTo(0);
if (adPeriod.getAdGroupCount() > 0) {
if (adCounter < numberOfPlayedAds) {
assertThat(adPeriod.getAdState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
.isEqualTo(AdPlaybackState.AD_STATE_PLAYED);
} else {
assertThat(adPeriod.getAdState(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
.isEqualTo(AdPlaybackState.AD_STATE_UNAVAILABLE);
}
adCounter++;
}
long expectedDurationUs =
(DEFAULT_WINDOW_DURATION_US / 7)
+ (periodIndex == 0 ? DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US : 0);
assertThat(adPeriod.getAdDurationUs(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0))
.isEqualTo(expectedDurationUs);
}
}
@Test
public void createMultiPeriodAdTimeline_firstPeriodIsContent_correctWindowDurationUs() {
Timeline.Window window = new Timeline.Window();
FakeTimeline timeline =
FakeTimeline.createMultiPeriodAdTimeline(
/* windowId= */ new Object(),
/* numberOfPlayedAds= */ 0,
/* isAdPeriodFlags...= */ false,
true,
true,
false);
timeline.getWindow(/* windowIndex= */ 0, window);
// Assert content periods and window duration.
assertThat(window.durationUs).isEqualTo(DEFAULT_WINDOW_DURATION_US / 2);
assertThat(window.positionInFirstPeriodUs).isEqualTo(DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment