Remove restriction from ConcatenatingMediaSource2
The class currently disallows offsets of periods in their windows except for the very first window. This is not necessary because we can use TimeOffsetMediaPeriod to eliminate the offset if needed. This makes the class more useful for many use cases, in particular for using it with ClippingMediaSource. Issue: google/ExoPlayer#11226 PiperOrigin-RevId: 563702120
This commit is contained in:
parent
763dddfbd4
commit
5a1322c9f9
@ -18,6 +18,9 @@
|
|||||||
([#612](https://github.com/androidx/media/issues/612)).
|
([#612](https://github.com/androidx/media/issues/612)).
|
||||||
* Add `MediaPeriodId` parameter to
|
* Add `MediaPeriodId` parameter to
|
||||||
`CompositeMediaSource.getMediaTimeForChildMediaTime`.
|
`CompositeMediaSource.getMediaTimeForChildMediaTime`.
|
||||||
|
* Support `ClippingMediaSource` (and other sources with period/window time
|
||||||
|
offsets) in `ConcatenatingMediaSource2`
|
||||||
|
([#11226](https://github.com/google/ExoPlayer/issues/11226)).
|
||||||
* Transformer:
|
* Transformer:
|
||||||
* Changed `frameRate` and `durationUs` parameters of
|
* Changed `frameRate` and `durationUs` parameters of
|
||||||
`SampleConsumer.queueInputBitmap` to `TimestampIterator`.
|
`SampleConsumer.queueInputBitmap` to `TimestampIterator`.
|
||||||
|
@ -37,19 +37,21 @@ import androidx.media3.datasource.TransferListener;
|
|||||||
import androidx.media3.exoplayer.upstream.Allocator;
|
import androidx.media3.exoplayer.upstream.Allocator;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenates multiple {@link MediaSource MediaSources}, combining everything in one single {@link
|
* Concatenates multiple {@link MediaSource MediaSources}, combining everything in one single {@link
|
||||||
* Timeline.Window}.
|
* Timeline.Window}.
|
||||||
*
|
*
|
||||||
* <p>This class can only be used under the following conditions:
|
* <p>This class can be used under the following conditions:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>All sources must be non-empty.
|
* <li>All sources must be non-empty.
|
||||||
* <li>All {@link Timeline.Window Windows} defined by the sources, except the first, must have an
|
* <li>The {@link Timeline.Window#getPositionInFirstPeriodUs() period offset} in all windows
|
||||||
* {@link Timeline.Window#getPositionInFirstPeriodUs() period offset} of zero. This excludes,
|
* (except for the first one) must not change during the lifetime of this media source. This
|
||||||
* for example, live streams or {@link ClippingMediaSource} with a non-zero start position.
|
* excludes, for example, live streams with moving live windows or dynamic updates to the
|
||||||
|
* clipping start time of a {@link ClippingMediaSource}.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -155,6 +157,13 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
checkStateNotNull(
|
checkStateNotNull(
|
||||||
mediaSourceFactory,
|
mediaSourceFactory,
|
||||||
"Must use useDefaultMediaSourceFactory or setMediaSourceFactory first.");
|
"Must use useDefaultMediaSourceFactory or setMediaSourceFactory first.");
|
||||||
|
if (initialPlaceholderDurationMs == C.TIME_UNSET
|
||||||
|
&& mediaItem.clippingConfiguration.endPositionMs != C.TIME_END_OF_SOURCE) {
|
||||||
|
// If the item is going to be clipped, we can provide a placeholder duration automatically.
|
||||||
|
initialPlaceholderDurationMs =
|
||||||
|
mediaItem.clippingConfiguration.endPositionMs
|
||||||
|
- mediaItem.clippingConfiguration.startPositionMs;
|
||||||
|
}
|
||||||
return add(mediaSourceFactory.createMediaSource(mediaItem), initialPlaceholderDurationMs);
|
return add(mediaSourceFactory.createMediaSource(mediaItem), initialPlaceholderDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,8 +286,15 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
id.windowSequenceNumber, mediaSourceHolders.size(), holder.index));
|
id.windowSequenceNumber, mediaSourceHolders.size(), holder.index));
|
||||||
enableChildSource(holder.index);
|
enableChildSource(holder.index);
|
||||||
holder.activeMediaPeriods++;
|
holder.activeMediaPeriods++;
|
||||||
|
long timeOffsetUs =
|
||||||
|
id.isAd()
|
||||||
|
? 0
|
||||||
|
: checkNotNull(holder.periodTimeOffsetsByUid.get(childMediaPeriodId.periodUid));
|
||||||
MediaPeriod mediaPeriod =
|
MediaPeriod mediaPeriod =
|
||||||
holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
|
new TimeOffsetMediaPeriod(
|
||||||
|
holder.mediaSource.createPeriod(
|
||||||
|
childMediaPeriodId, allocator, startPositionUs - timeOffsetUs),
|
||||||
|
timeOffsetUs);
|
||||||
mediaSourceByMediaPeriod.put(mediaPeriod, holder);
|
mediaSourceByMediaPeriod.put(mediaPeriod, holder);
|
||||||
disableUnusedMediaSources();
|
disableUnusedMediaSources();
|
||||||
return mediaPeriod;
|
return mediaPeriod;
|
||||||
@ -287,7 +303,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
@Override
|
@Override
|
||||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||||
MediaSourceHolder holder = checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
|
MediaSourceHolder holder = checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
|
||||||
holder.mediaSource.releasePeriod(mediaPeriod);
|
holder.mediaSource.releasePeriod(((TimeOffsetMediaPeriod) mediaPeriod).getWrappedMediaPeriod());
|
||||||
holder.activeMediaPeriods--;
|
holder.activeMediaPeriods--;
|
||||||
if (!mediaSourceByMediaPeriod.isEmpty()) {
|
if (!mediaSourceByMediaPeriod.isEmpty()) {
|
||||||
disableUnusedMediaSources();
|
disableUnusedMediaSources();
|
||||||
@ -336,6 +352,21 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected long getMediaTimeForChildMediaTime(
|
||||||
|
Integer childSourceId, long mediaTimeMs, @Nullable MediaPeriodId mediaPeriodId) {
|
||||||
|
if (mediaTimeMs == C.TIME_UNSET || mediaPeriodId == null || mediaPeriodId.isAd()) {
|
||||||
|
return mediaTimeMs;
|
||||||
|
}
|
||||||
|
@Nullable
|
||||||
|
Long timeOffsetUs =
|
||||||
|
mediaSourceHolders.get(childSourceId).periodTimeOffsetsByUid.get(mediaPeriodId.periodUid);
|
||||||
|
if (timeOffsetUs == null) {
|
||||||
|
return mediaTimeMs;
|
||||||
|
}
|
||||||
|
return mediaTimeMs + Util.usToMs(timeOffsetUs);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean handleMessage(Message msg) {
|
private boolean handleMessage(Message msg) {
|
||||||
if (msg.what == MSG_UPDATE_TIMELINE) {
|
if (msg.what == MSG_UPDATE_TIMELINE) {
|
||||||
updateTimeline();
|
updateTimeline();
|
||||||
@ -383,13 +414,15 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
boolean manifestsAreIdentical = true;
|
boolean manifestsAreIdentical = true;
|
||||||
boolean hasInitialManifest = false;
|
boolean hasInitialManifest = false;
|
||||||
@Nullable Object initialManifest = null;
|
@Nullable Object initialManifest = null;
|
||||||
for (int i = 0; i < mediaSourceHolders.size(); i++) {
|
int mediaSourceHoldersCount = mediaSourceHolders.size();
|
||||||
|
for (int i = 0; i < mediaSourceHoldersCount; i++) {
|
||||||
MediaSourceHolder holder = mediaSourceHolders.get(i);
|
MediaSourceHolder holder = mediaSourceHolders.get(i);
|
||||||
Timeline timeline = holder.mediaSource.getTimeline();
|
Timeline timeline = holder.mediaSource.getTimeline();
|
||||||
checkArgument(!timeline.isEmpty(), "Can't concatenate empty child Timeline.");
|
checkArgument(!timeline.isEmpty(), "Can't concatenate empty child Timeline.");
|
||||||
timelinesBuilder.add(timeline);
|
timelinesBuilder.add(timeline);
|
||||||
firstPeriodIndicesBuilder.add(periodCount);
|
firstPeriodIndicesBuilder.add(periodCount);
|
||||||
periodCount += timeline.getPeriodCount();
|
int periodCountInMediaSourceHolder = timeline.getPeriodCount();
|
||||||
|
periodCount += periodCountInMediaSourceHolder;
|
||||||
for (int j = 0; j < timeline.getWindowCount(); j++) {
|
for (int j = 0; j < timeline.getWindowCount(); j++) {
|
||||||
timeline.getWindow(/* windowIndex= */ j, window);
|
timeline.getWindow(/* windowIndex= */ j, window);
|
||||||
if (!hasInitialManifest) {
|
if (!hasInitialManifest) {
|
||||||
@ -411,31 +444,38 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
if (holder.index == 0 && j == 0) {
|
if (holder.index == 0 && j == 0) {
|
||||||
defaultPositionUs = window.defaultPositionUs;
|
defaultPositionUs = window.defaultPositionUs;
|
||||||
nextPeriodOffsetInWindowUs = -window.positionInFirstPeriodUs;
|
nextPeriodOffsetInWindowUs = -window.positionInFirstPeriodUs;
|
||||||
} else {
|
|
||||||
checkArgument(
|
|
||||||
window.positionInFirstPeriodUs == 0,
|
|
||||||
"Can't concatenate windows. A window has a non-zero offset in a period.");
|
|
||||||
}
|
}
|
||||||
// Assume placeholder windows are seekable to not prevent seeking in other periods.
|
// Assume placeholder windows are seekable to not prevent seeking in other periods.
|
||||||
isSeekable &= window.isSeekable || window.isPlaceholder;
|
isSeekable &= window.isSeekable || window.isPlaceholder;
|
||||||
isDynamic |= window.isDynamic;
|
isDynamic |= window.isDynamic;
|
||||||
}
|
|
||||||
int childPeriodCount = timeline.getPeriodCount();
|
for (int k = window.firstPeriodIndex; k <= window.lastPeriodIndex; k++) {
|
||||||
for (int j = 0; j < childPeriodCount; j++) {
|
periodOffsetsInWindowUsBuilder.add(nextPeriodOffsetInWindowUs);
|
||||||
periodOffsetsInWindowUsBuilder.add(nextPeriodOffsetInWindowUs);
|
timeline.getPeriod(/* periodIndex= */ k, period, /* setIds= */ true);
|
||||||
timeline.getPeriod(/* periodIndex= */ j, period);
|
long periodDurationUs = period.durationUs;
|
||||||
long periodDurationUs = period.durationUs;
|
if (periodDurationUs == C.TIME_UNSET) {
|
||||||
if (periodDurationUs == C.TIME_UNSET) {
|
checkArgument(
|
||||||
|
window.firstPeriodIndex == window.lastPeriodIndex,
|
||||||
|
"Can't apply placeholder duration to multiple periods with unknown duration "
|
||||||
|
+ "in a single window.");
|
||||||
|
periodDurationUs = windowDurationUs + window.positionInFirstPeriodUs;
|
||||||
|
}
|
||||||
|
long timeOffsetUsForPeriod = 0;
|
||||||
|
boolean isFirstPeriodInNonFirstWindow =
|
||||||
|
k == window.firstPeriodIndex && (holder.index != 0 || j != 0);
|
||||||
|
if (isFirstPeriodInNonFirstWindow && periodDurationUs != C.TIME_UNSET) {
|
||||||
|
timeOffsetUsForPeriod = -window.positionInFirstPeriodUs;
|
||||||
|
periodDurationUs += timeOffsetUsForPeriod;
|
||||||
|
}
|
||||||
|
Object periodUid = checkNotNull(period.uid);
|
||||||
checkArgument(
|
checkArgument(
|
||||||
childPeriodCount == 1,
|
holder.activeMediaPeriods == 0
|
||||||
"Can't concatenate multiple periods with unknown duration in one window.");
|
|| !holder.periodTimeOffsetsByUid.containsKey(periodUid)
|
||||||
long windowDurationUs =
|
|| holder.periodTimeOffsetsByUid.get(periodUid).equals(timeOffsetUsForPeriod),
|
||||||
window.durationUs != C.TIME_UNSET
|
"Can't handle windows with changing offset in first period.");
|
||||||
? window.durationUs
|
holder.periodTimeOffsetsByUid.put(periodUid, timeOffsetUsForPeriod);
|
||||||
: holder.initialPlaceholderDurationUs;
|
nextPeriodOffsetInWindowUs += periodDurationUs;
|
||||||
periodDurationUs = windowDurationUs + window.positionInFirstPeriodUs;
|
|
||||||
}
|
}
|
||||||
nextPeriodOffsetInWindowUs += periodDurationUs;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ConcatenatedTimeline(
|
return new ConcatenatedTimeline(
|
||||||
@ -492,6 +532,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
public final MaskingMediaSource mediaSource;
|
public final MaskingMediaSource mediaSource;
|
||||||
public final int index;
|
public final int index;
|
||||||
public final long initialPlaceholderDurationUs;
|
public final long initialPlaceholderDurationUs;
|
||||||
|
public final HashMap<Object, Long> periodTimeOffsetsByUid;
|
||||||
|
|
||||||
public int activeMediaPeriods;
|
public int activeMediaPeriods;
|
||||||
|
|
||||||
@ -500,6 +541,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
this.mediaSource = new MaskingMediaSource(mediaSource, /* useLazyPreparation= */ false);
|
this.mediaSource = new MaskingMediaSource(mediaSource, /* useLazyPreparation= */ false);
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.initialPlaceholderDurationUs = initialPlaceholderDurationUs;
|
this.initialPlaceholderDurationUs = initialPlaceholderDurationUs;
|
||||||
|
this.periodTimeOffsetsByUid = new HashMap<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,8 +589,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Window getWindow(
|
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||||
int windowIndex, Window window, long defaultPositionProjectionUs) {
|
|
||||||
return window.set(
|
return window.set(
|
||||||
Window.SINGLE_WINDOW_UID,
|
Window.SINGLE_WINDOW_UID,
|
||||||
mediaItem,
|
mediaItem,
|
||||||
@ -567,7 +608,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Period getPeriodByUid(Object periodUid, Period period) {
|
public Period getPeriodByUid(Object periodUid, Period period) {
|
||||||
int childIndex = getChildIndex(periodUid);
|
int childIndex = getChildIndex(periodUid);
|
||||||
Object childPeriodUid = getChildPeriodUid(periodUid);
|
Object childPeriodUid = getChildPeriodUid(periodUid);
|
||||||
Timeline timeline = timelines.get(childIndex);
|
Timeline timeline = timelines.get(childIndex);
|
||||||
@ -576,17 +617,19 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
timeline.getPeriodByUid(childPeriodUid, period);
|
timeline.getPeriodByUid(childPeriodUid, period);
|
||||||
period.windowIndex = 0;
|
period.windowIndex = 0;
|
||||||
period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex);
|
period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex);
|
||||||
|
period.durationUs = getPeriodDurationUs(period, periodIndex);
|
||||||
period.uid = periodUid;
|
period.uid = periodUid;
|
||||||
return period;
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
int childIndex = getChildIndexByPeriodIndex(periodIndex);
|
int childIndex = getChildIndexByPeriodIndex(periodIndex);
|
||||||
int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex);
|
int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex);
|
||||||
timelines.get(childIndex).getPeriod(periodIndex - firstPeriodIndexInChild, period, setIds);
|
timelines.get(childIndex).getPeriod(periodIndex - firstPeriodIndexInChild, period, setIds);
|
||||||
period.windowIndex = 0;
|
period.windowIndex = 0;
|
||||||
period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex);
|
period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex);
|
||||||
|
period.durationUs = getPeriodDurationUs(period, periodIndex);
|
||||||
if (setIds) {
|
if (setIds) {
|
||||||
period.uid = getPeriodUid(childIndex, checkNotNull(period.uid));
|
period.uid = getPeriodUid(childIndex, checkNotNull(period.uid));
|
||||||
}
|
}
|
||||||
@ -594,7 +637,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getIndexOfPeriod(Object uid) {
|
public int getIndexOfPeriod(Object uid) {
|
||||||
if (!(uid instanceof Pair) || !(((Pair<?, ?>) uid).first instanceof Integer)) {
|
if (!(uid instanceof Pair) || !(((Pair<?, ?>) uid).first instanceof Integer)) {
|
||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
@ -607,7 +650,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Object getUidOfPeriod(int periodIndex) {
|
public Object getUidOfPeriod(int periodIndex) {
|
||||||
int childIndex = getChildIndexByPeriodIndex(periodIndex);
|
int childIndex = getChildIndexByPeriodIndex(periodIndex);
|
||||||
int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex);
|
int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex);
|
||||||
Object periodUidInChild =
|
Object periodUidInChild =
|
||||||
@ -619,5 +662,18 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
|
|||||||
return Util.binarySearchFloor(
|
return Util.binarySearchFloor(
|
||||||
firstPeriodIndices, periodIndex + 1, /* inclusive= */ false, /* stayInBounds= */ false);
|
firstPeriodIndices, periodIndex + 1, /* inclusive= */ false, /* stayInBounds= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long getPeriodDurationUs(Timeline.Period childPeriod, int periodIndex) {
|
||||||
|
// Keep unset duration, but force duration to match offset of next period otherwise.
|
||||||
|
if (childPeriod.durationUs == C.TIME_UNSET) {
|
||||||
|
return C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
long periodStartTimeInWindowUs = periodOffsetsInWindowUs.get(periodIndex);
|
||||||
|
long periodEndTimeInWindowUs =
|
||||||
|
periodIndex == periodOffsetsInWindowUs.size() - 1
|
||||||
|
? durationUs
|
||||||
|
: periodOffsetsInWindowUs.get(periodIndex + 1);
|
||||||
|
return periodEndTimeInWindowUs - periodStartTimeInWindowUs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
import androidx.media3.exoplayer.source.ConcatenatingMediaSource2;
|
||||||
import androidx.media3.test.utils.CapturingRenderersFactory;
|
import androidx.media3.test.utils.CapturingRenderersFactory;
|
||||||
import androidx.media3.test.utils.DumpFileAsserts;
|
import androidx.media3.test.utils.DumpFileAsserts;
|
||||||
import androidx.media3.test.utils.FakeClock;
|
import androidx.media3.test.utils.FakeClock;
|
||||||
@ -71,7 +72,7 @@ public final class ClippingPlaylistPlaybackTest {
|
|||||||
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
|
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() throws Exception {
|
public void playbackWithClippingMediaSources() throws Exception {
|
||||||
Context applicationContext = ApplicationProvider.getApplicationContext();
|
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||||
CapturingRenderersFactory capturingRenderersFactory =
|
CapturingRenderersFactory capturingRenderersFactory =
|
||||||
new CapturingRenderersFactory(applicationContext);
|
new CapturingRenderersFactory(applicationContext);
|
||||||
@ -113,6 +114,61 @@ public final class ClippingPlaylistPlaybackTest {
|
|||||||
"playbackdumps/clipping/" + firstItemConfig.name + "_" + secondItemConfig.name + ".dump");
|
"playbackdumps/clipping/" + firstItemConfig.name + "_" + secondItemConfig.name + ".dump");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void playbackWithClippingMediaSourcesInConcatenatingMediaSource2() throws Exception {
|
||||||
|
Context applicationContext = ApplicationProvider.getApplicationContext();
|
||||||
|
CapturingRenderersFactory capturingRenderersFactory =
|
||||||
|
new CapturingRenderersFactory(applicationContext);
|
||||||
|
ExoPlayer player =
|
||||||
|
new ExoPlayer.Builder(applicationContext, capturingRenderersFactory)
|
||||||
|
.setClock(new FakeClock(/* isAutoAdvancing= */ true))
|
||||||
|
.build();
|
||||||
|
Surface surface = new Surface(new SurfaceTexture(/* texName= */ 1));
|
||||||
|
player.setVideoSurface(surface);
|
||||||
|
PlaybackOutput playbackOutput = PlaybackOutput.register(player, capturingRenderersFactory);
|
||||||
|
|
||||||
|
player.addMediaSource(
|
||||||
|
new ConcatenatingMediaSource2.Builder()
|
||||||
|
.useDefaultMediaSourceFactory(applicationContext)
|
||||||
|
.add(
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri(TEST_MP4_URI)
|
||||||
|
.setClippingConfiguration(
|
||||||
|
new MediaItem.ClippingConfiguration.Builder()
|
||||||
|
.setStartPositionMs(firstItemConfig.startMs)
|
||||||
|
.setEndPositionMs(firstItemConfig.endMs)
|
||||||
|
.build())
|
||||||
|
.build(),
|
||||||
|
/* initialPlaceholderDurationMs= */ firstItemConfig.endMs == C.TIME_END_OF_SOURCE
|
||||||
|
? 1
|
||||||
|
: C.TIME_UNSET)
|
||||||
|
.add(
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri(TEST_MP4_URI)
|
||||||
|
.setClippingConfiguration(
|
||||||
|
new MediaItem.ClippingConfiguration.Builder()
|
||||||
|
.setStartPositionMs(secondItemConfig.startMs)
|
||||||
|
.setEndPositionMs(secondItemConfig.endMs)
|
||||||
|
.build())
|
||||||
|
.build(),
|
||||||
|
/* initialPlaceholderDurationMs= */ secondItemConfig.endMs == C.TIME_END_OF_SOURCE
|
||||||
|
? 1
|
||||||
|
: C.TIME_UNSET)
|
||||||
|
.build());
|
||||||
|
player.prepare();
|
||||||
|
player.play();
|
||||||
|
TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
player.release();
|
||||||
|
surface.release();
|
||||||
|
|
||||||
|
// Intentionally uses the same dump files as for the test above because the renderer output
|
||||||
|
// should not be affected by combining all sources in a ConcatenatingMediaSource2.
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
applicationContext,
|
||||||
|
playbackOutput,
|
||||||
|
"playbackdumps/clipping/" + firstItemConfig.name + "_" + secondItemConfig.name + ".dump");
|
||||||
|
}
|
||||||
|
|
||||||
private static final class ClippingConfig {
|
private static final class ClippingConfig {
|
||||||
|
|
||||||
public final String name;
|
public final String name;
|
||||||
|
@ -123,6 +123,68 @@ public final class ConcatenatingMediaSource2Test {
|
|||||||
/* manifest= */ null)
|
/* manifest= */ null)
|
||||||
.withAdPlaybackState(/* periodIndex= */ 4, adPlaybackState)));
|
.withAdPlaybackState(/* periodIndex= */ 4, adPlaybackState)));
|
||||||
|
|
||||||
|
// Second full example with additional offsets in all other windows (including multiple windows
|
||||||
|
// per source and a single last period with an offset).
|
||||||
|
builder.add(
|
||||||
|
new TestConfig(
|
||||||
|
"offset_in_multiple_windows_and_ads",
|
||||||
|
buildConcatenatingMediaSource(
|
||||||
|
buildMediaSource(
|
||||||
|
buildWindow(
|
||||||
|
/* periodCount= */ 2,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* durationMs= */ 1000,
|
||||||
|
/* defaultPositionMs= */ 123,
|
||||||
|
/* windowOffsetInFirstPeriodMs= */ 50),
|
||||||
|
buildWindow(
|
||||||
|
/* periodCount= */ 2,
|
||||||
|
/* isSeekable= */ false,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* durationMs= */ 2500,
|
||||||
|
/* defaultPositionMs= */ 234,
|
||||||
|
/* windowOffsetInFirstPeriodMs= */ 300)),
|
||||||
|
buildMediaSource(
|
||||||
|
buildWindow(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* durationMs= */ 500,
|
||||||
|
/* defaultPositionMs= */ 234,
|
||||||
|
/* windowOffsetInFirstPeriodMs= */ 100,
|
||||||
|
adPlaybackState)),
|
||||||
|
buildMediaSource(
|
||||||
|
buildWindow(
|
||||||
|
/* periodCount= */ 2,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* durationMs= */ 1200,
|
||||||
|
/* defaultPositionMs= */ 234,
|
||||||
|
/* windowOffsetInFirstPeriodMs= */ 250)),
|
||||||
|
buildMediaSource(
|
||||||
|
buildWindow(
|
||||||
|
/* periodCount= */ 1,
|
||||||
|
/* isSeekable= */ true,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* durationMs= */ 600,
|
||||||
|
/* defaultPositionMs= */ 234,
|
||||||
|
/* windowOffsetInFirstPeriodMs= */ 200))),
|
||||||
|
/* expectedAdDiscontinuities= */ 3,
|
||||||
|
new ExpectedTimelineData(
|
||||||
|
/* isSeekable= */ false,
|
||||||
|
/* isDynamic= */ false,
|
||||||
|
/* defaultPositionMs= */ 123,
|
||||||
|
/* periodDurationsMs= */ new long[] {550, 500, 1250, 1250, 500, 600, 600, 600},
|
||||||
|
/* periodOffsetsInWindowMs= */ new long[] {
|
||||||
|
-50, 500, 1000, 2250, 3500, 4000, 4600, 5200
|
||||||
|
},
|
||||||
|
/* periodIsPlaceholder= */ new boolean[] {
|
||||||
|
false, false, false, false, false, false, false, false
|
||||||
|
},
|
||||||
|
/* windowDurationMs= */ 5800,
|
||||||
|
/* manifest= */ null)
|
||||||
|
.withAdPlaybackState(/* periodIndex= */ 4, adPlaybackState)));
|
||||||
|
|
||||||
builder.add(
|
builder.add(
|
||||||
new TestConfig(
|
new TestConfig(
|
||||||
"multipleMediaSource_sameManifest",
|
"multipleMediaSource_sameManifest",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user