diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java index 91b0aff28d..cb8ff2650a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java @@ -198,6 +198,13 @@ public abstract class BaseMediaSource implements MediaSource { drmEventDispatcher.removeEventListener(eventListener); } + @SuppressWarnings("deprecation") // Overriding deprecated method to make it final. + @Override + public final void prepareSource( + MediaSourceCaller caller, @Nullable TransferListener mediaTransferListener) { + prepareSource(caller, mediaTransferListener, PlayerId.UNSET); + } + @Override public final void prepareSource( MediaSourceCaller caller, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index ec03387e93..58b8b22846 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -22,10 +22,8 @@ import static java.lang.annotation.ElementType.TYPE_USE; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; @@ -39,7 +37,7 @@ import java.util.ArrayList; * {@link MediaSource} that wraps a source and clips its timeline based on specified start/end * positions. The wrapped source must consist of a single period. */ -public final class ClippingMediaSource extends CompositeMediaSource { +public final class ClippingMediaSource extends WrappingMediaSource { /** Thrown when a {@link ClippingMediaSource} cannot clip its wrapped source. */ public static final class IllegalClippingException extends IOException { @@ -85,7 +83,6 @@ public final class ClippingMediaSource extends CompositeMediaSource { } } - private final MediaSource mediaSource; private final long startUs; private final long endUs; private final boolean enableInitialDiscontinuity; @@ -181,8 +178,8 @@ public final class ClippingMediaSource extends CompositeMediaSource { boolean enableInitialDiscontinuity, boolean allowDynamicClippingUpdates, boolean relativeToDefaultPosition) { + super(Assertions.checkNotNull(mediaSource)); Assertions.checkArgument(startPositionUs >= 0); - this.mediaSource = Assertions.checkNotNull(mediaSource); startUs = startPositionUs; endUs = endPositionUs; this.enableInitialDiscontinuity = enableInitialDiscontinuity; @@ -192,17 +189,6 @@ public final class ClippingMediaSource extends CompositeMediaSource { window = new Timeline.Window(); } - @Override - public MediaItem getMediaItem() { - return mediaSource.getMediaItem(); - } - - @Override - protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { - super.prepareSourceInternal(mediaTransferListener); - prepareChildSource(/* id= */ null, mediaSource); - } - @Override public void maybeThrowSourceInfoRefreshError() throws IOException { if (clippingError != null) { @@ -240,7 +226,7 @@ public final class ClippingMediaSource extends CompositeMediaSource { } @Override - protected void onChildSourceInfoRefreshed(Void id, MediaSource mediaSource, Timeline timeline) { + protected void onChildSourceInfoRefreshed(Timeline timeline) { if (clippingError != null) { return; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java index 99e62728ac..da47e2b649 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java @@ -19,12 +19,10 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.AbstractConcatenatedTimeline; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; -import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.ShuffleOrder.UnshuffledShuffleOrder; import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import java.util.HashMap; import java.util.Map; @@ -41,9 +39,8 @@ import java.util.Map; * times. */ @Deprecated -public final class LoopingMediaSource extends CompositeMediaSource { +public final class LoopingMediaSource extends WrappingMediaSource { - private final MaskingMediaSource maskingMediaSource; private final int loopCount; private final Map childMediaPeriodIdToMediaPeriodId; private final Map mediaPeriodToChildMediaPeriodId; @@ -65,21 +62,17 @@ public final class LoopingMediaSource extends CompositeMediaSource { * @param loopCount The desired number of loops. Must be strictly positive. */ public LoopingMediaSource(MediaSource childSource, int loopCount) { + super(new MaskingMediaSource(childSource, /* useLazyPreparation= */ false)); Assertions.checkArgument(loopCount > 0); - this.maskingMediaSource = new MaskingMediaSource(childSource, /* useLazyPreparation= */ false); this.loopCount = loopCount; childMediaPeriodIdToMediaPeriodId = new HashMap<>(); mediaPeriodToChildMediaPeriodId = new HashMap<>(); } - @Override - public MediaItem getMediaItem() { - return maskingMediaSource.getMediaItem(); - } - @Override @Nullable public Timeline getInitialTimeline() { + MaskingMediaSource maskingMediaSource = (MaskingMediaSource) mediaSource; return loopCount != Integer.MAX_VALUE ? new LoopingTimeline(maskingMediaSource.getTimeline(), loopCount) : new InfinitelyLoopingTimeline(maskingMediaSource.getTimeline()); @@ -90,29 +83,23 @@ public final class LoopingMediaSource extends CompositeMediaSource { return false; } - @Override - protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { - super.prepareSourceInternal(mediaTransferListener); - prepareChildSource(/* id= */ null, maskingMediaSource); - } - @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { if (loopCount == Integer.MAX_VALUE) { - return maskingMediaSource.createPeriod(id, allocator, startPositionUs); + return mediaSource.createPeriod(id, allocator, startPositionUs); } Object childPeriodUid = LoopingTimeline.getChildPeriodUidFromConcatenatedUid(id.periodUid); MediaPeriodId childMediaPeriodId = id.copyWithPeriodUid(childPeriodUid); childMediaPeriodIdToMediaPeriodId.put(childMediaPeriodId, id); MediaPeriod mediaPeriod = - maskingMediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs); + mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs); mediaPeriodToChildMediaPeriodId.put(mediaPeriod, childMediaPeriodId); return mediaPeriod; } @Override public void releasePeriod(MediaPeriod mediaPeriod) { - maskingMediaSource.releasePeriod(mediaPeriod); + mediaSource.releasePeriod(mediaPeriod); @Nullable MediaPeriodId childMediaPeriodId = mediaPeriodToChildMediaPeriodId.remove(mediaPeriod); if (childMediaPeriodId != null) { @@ -121,7 +108,7 @@ public final class LoopingMediaSource extends CompositeMediaSource { } @Override - protected void onChildSourceInfoRefreshed(Void id, MediaSource mediaSource, Timeline timeline) { + protected void onChildSourceInfoRefreshed(Timeline timeline) { Timeline loopingTimeline = loopCount != Integer.MAX_VALUE ? new LoopingTimeline(timeline, loopCount) @@ -131,8 +118,7 @@ public final class LoopingMediaSource extends CompositeMediaSource { @Override @Nullable - protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId( - Void id, MediaPeriodId mediaPeriodId) { + protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(MediaPeriodId mediaPeriodId) { return loopCount != Integer.MAX_VALUE ? childMediaPeriodIdToMediaPeriodId.get(mediaPeriodId) : mediaPeriodId; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java index 04304a87f4..6ea76c5f78 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MaskingMediaSource.java @@ -26,7 +26,6 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline.Window; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.upstream.Allocator; -import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import org.checkerframework.checker.nullness.qual.RequiresNonNull; @@ -35,9 +34,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; * A {@link MediaSource} that masks the {@link Timeline} with a placeholder until the actual media * structure is known. */ -public final class MaskingMediaSource extends CompositeMediaSource { +public final class MaskingMediaSource extends WrappingMediaSource { - private final MediaSource mediaSource; private final boolean useLazyPreparation; private final Timeline.Window window; private final Timeline.Period period; @@ -57,7 +55,7 @@ public final class MaskingMediaSource extends CompositeMediaSource { * initial preparations are triggered only when the player starts buffering the media. */ public MaskingMediaSource(MediaSource mediaSource, boolean useLazyPreparation) { - this.mediaSource = mediaSource; + super(mediaSource); this.useLazyPreparation = useLazyPreparation && mediaSource.isSingleWindow(); window = new Timeline.Window(); period = new Timeline.Period(); @@ -78,19 +76,13 @@ public final class MaskingMediaSource extends CompositeMediaSource { } @Override - public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { - super.prepareSourceInternal(mediaTransferListener); + public void prepareSourceInternal() { if (!useLazyPreparation) { hasStartedPreparing = true; - prepareChildSource(/* id= */ null, mediaSource); + prepareChildSource(); } } - @Override - public MediaItem getMediaItem() { - return mediaSource.getMediaItem(); - } - @Override @SuppressWarnings("MissingSuperCall") public void maybeThrowSourceInfoRefreshError() { @@ -113,7 +105,7 @@ public final class MaskingMediaSource extends CompositeMediaSource { unpreparedMaskingMediaPeriod = mediaPeriod; if (!hasStartedPreparing) { hasStartedPreparing = true; - prepareChildSource(/* id= */ null, mediaSource); + prepareChildSource(); } } return mediaPeriod; @@ -135,8 +127,7 @@ public final class MaskingMediaSource extends CompositeMediaSource { } @Override - protected void onChildSourceInfoRefreshed( - Void id, MediaSource mediaSource, Timeline newTimeline) { + protected void onChildSourceInfoRefreshed(Timeline newTimeline) { @Nullable MediaPeriodId idForMaskingPeriodPreparation = null; if (isPrepared) { timeline = timeline.cloneWithUpdatedTimeline(newTimeline); @@ -206,8 +197,7 @@ public final class MaskingMediaSource extends CompositeMediaSource { @Override @Nullable - protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId( - Void id, MediaPeriodId mediaPeriodId) { + protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(MediaPeriodId mediaPeriodId) { return mediaPeriodId.copyWithPeriodUid(getExternalPeriodUid(mediaPeriodId.periodUid)); } @@ -302,11 +292,6 @@ public final class MaskingMediaSource extends CompositeMediaSource { return new MaskingTimeline(timeline, replacedInternalWindowUid, replacedInternalPeriodUid); } - /** Returns the wrapped timeline. */ - public Timeline getTimeline() { - return timeline; - } - @Override public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { timeline.getWindow(windowIndex, window, defaultPositionProjectionUs); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/WrappingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/WrappingMediaSource.java new file mode 100644 index 0000000000..cf2a421198 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/WrappingMediaSource.java @@ -0,0 +1,229 @@ +/* + * 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.source; + +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.Timeline; +import com.google.android.exoplayer2.analytics.PlayerId; +import com.google.android.exoplayer2.upstream.Allocator; +import com.google.android.exoplayer2.upstream.TransferListener; + +/** + * An abstract {@link MediaSource} wrapping a single child {@link MediaSource}. + * + *

The implementation may want to override the following methods as needed: + * + *

    + *
  • {@link #getMediaItem()}: Amend the {@link MediaItem} for this media source. This is only + * used before the child source is prepared. + *
  • {@link #onChildSourceInfoRefreshed(Timeline)}: Called whenever the child source's {@link + * Timeline} changed. This {@link Timeline} can be amended if needed, for example using {@link + * ForwardingTimeline}. The {@link Timeline} for the wrapping source needs to be published + * with {@link #refreshSourceInfo(Timeline)}. + *
  • {@link #createPeriod}/{@link #releasePeriod}: These methods create and release {@link + * MediaPeriod} instances. They typically forward to the wrapped media source and optionally + * wrap the returned {@link MediaPeriod}. + *
+ * + *

Other methods like {@link #prepareSourceInternal}, {@link #enableInternal}, {@link + * #disableInternal} or {@link #releaseSourceInternal} only need to be overwritten if required for + * resource management. + */ +public abstract class WrappingMediaSource extends CompositeMediaSource { + + private static final Void CHILD_SOURCE_ID = null; + + /** The wrapped child {@link MediaSource}. */ + protected final MediaSource mediaSource; + + /** + * Creates the wrapping {@link MediaSource}. + * + * @param mediaSource The wrapped child {@link MediaSource}. + */ + protected WrappingMediaSource(MediaSource mediaSource) { + this.mediaSource = mediaSource; + } + + @Override + protected final void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { + super.prepareSourceInternal(mediaTransferListener); + prepareSourceInternal(); + } + + /** + * Starts source preparation and enables the source, see {@link #prepareSource(MediaSourceCaller, + * TransferListener, PlayerId)}. This method is called at most once until the next call to {@link + * #releaseSourceInternal()}. + */ + protected void prepareSourceInternal() { + prepareChildSource(); + } + + @Nullable + @Override + public Timeline getInitialTimeline() { + return mediaSource.getInitialTimeline(); + } + + @Override + public boolean isSingleWindow() { + return mediaSource.isSingleWindow(); + } + + /** + * Returns the {@link MediaItem} for this media source. + * + *

This method can be overridden to amend the {@link MediaItem} of the child source. It is only + * used before the child source is prepared. + * + * @see MediaSource#getMediaItem() + */ + @Override + public MediaItem getMediaItem() { + return mediaSource.getMediaItem(); + } + + /** + * Creates the requested {@link MediaPeriod}. + * + *

This method typically forwards to the wrapped media source and optionally wraps the returned + * {@link MediaPeriod}. + * + * @see MediaSource#createPeriod(MediaPeriodId, Allocator, long) + */ + @Override + public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { + return mediaSource.createPeriod(id, allocator, startPositionUs); + } + + /** + * Releases a {@link MediaPeriod}. + * + *

This method typically forwards to the wrapped media source and optionally unwraps the + * provided {@link MediaPeriod}. + * + * @see MediaSource#releasePeriod(MediaPeriod) + */ + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + mediaSource.releasePeriod(mediaPeriod); + } + + @Override + protected final void onChildSourceInfoRefreshed( + Void id, MediaSource mediaSource, Timeline timeline) { + onChildSourceInfoRefreshed(timeline); + } + + /** + * Called when the child source info has been refreshed. + * + *

This {@link Timeline} can be amended if needed, for example using {@link + * ForwardingTimeline}. The {@link Timeline} for the wrapping source needs to be published with + * {@link #refreshSourceInfo(Timeline)}. + * + * @param timeline The timeline of the child source. + */ + protected void onChildSourceInfoRefreshed(Timeline timeline) { + refreshSourceInfo(timeline); + } + + @Override + protected final int getWindowIndexForChildWindowIndex(Void id, int windowIndex) { + return getWindowIndexForChildWindowIndex(windowIndex); + } + + /** + * Returns the window index in the wrapping source corresponding to the specified window index in + * a child source. The default implementation does not change the window index. + * + * @param windowIndex A window index of the child source. + * @return The corresponding window index in the wrapping source. + */ + protected int getWindowIndexForChildWindowIndex(int windowIndex) { + return windowIndex; + } + + @Nullable + @Override + protected final MediaPeriodId getMediaPeriodIdForChildMediaPeriodId( + Void id, MediaPeriodId mediaPeriodId) { + return getMediaPeriodIdForChildMediaPeriodId(mediaPeriodId); + } + + /** + * Returns the {@link MediaPeriodId} in the wrapping source corresponding to the specified {@link + * MediaPeriodId} in a child source. The default implementation does not change the media period + * id. + * + * @param mediaPeriodId A {@link MediaPeriodId} of the child source. + * @return The corresponding {@link MediaPeriodId} in the wrapping source. Null if no + * corresponding media period id can be determined. + */ + @Nullable + protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(MediaPeriodId mediaPeriodId) { + return mediaPeriodId; + } + + @Override + protected final long getMediaTimeForChildMediaTime(Void id, long mediaTimeMs) { + return getMediaTimeForChildMediaTime(mediaTimeMs); + } + + /** + * Returns the media time in the {@link MediaPeriod} of the wrapping source corresponding to the + * specified media time in the {@link MediaPeriod} of the child source. The default implementation + * does not change the media time. + * + * @param mediaTimeMs A media time in the {@link MediaPeriod} of the child source, in + * milliseconds. + * @return The corresponding media time in the {@link MediaPeriod} of the wrapping source, in + * milliseconds. + */ + protected long getMediaTimeForChildMediaTime(long mediaTimeMs) { + return mediaTimeMs; + } + + /** + * Prepares the wrapped child source. + * + *

{@link #onChildSourceInfoRefreshed(Timeline)} will be called when the child source updates + * its timeline. + * + *

If sources aren't explicitly released with {@link #releaseChildSource()} they will be + * released in {@link #releaseSourceInternal()}. + */ + protected final void prepareChildSource() { + prepareChildSource(CHILD_SOURCE_ID, mediaSource); + } + + /** Enables the child source. */ + protected final void enableChildSource() { + enableChildSource(CHILD_SOURCE_ID); + } + + /** Disables the child source. */ + protected final void disableChildSource() { + disableChildSource(CHILD_SOURCE_ID); + } + + /** Releases the child source. */ + protected final void releaseChildSource() { + releaseChildSource(CHILD_SOURCE_ID); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 3d9d5d4f2e..43af7172bb 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -106,7 +106,6 @@ import com.google.android.exoplayer2.metadata.id3.BinaryFrame; import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; import com.google.android.exoplayer2.robolectric.TestPlayerRunHelper; import com.google.android.exoplayer2.source.ClippingMediaSource; -import com.google.android.exoplayer2.source.CompositeMediaSource; import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MaskingMediaSource; import com.google.android.exoplayer2.source.MediaPeriod; @@ -117,6 +116,7 @@ import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.SinglePeriodTimeline; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.source.WrappingMediaSource; import com.google.android.exoplayer2.source.ads.AdPlaybackState; import com.google.android.exoplayer2.source.ads.ServerSideAdInsertionMediaSource; import com.google.android.exoplayer2.testutil.Action; @@ -3932,33 +3932,15 @@ public final class ExoPlayerTest { new TimelineWindowDefinition( /* isSeekable= */ true, /* isDynamic= */ false, /* durationUs= */ 10_000_000)); final ConcatenatingMediaSource underlyingSource = new ConcatenatingMediaSource(); - CompositeMediaSource delegatingMediaSource = - new CompositeMediaSource() { + WrappingMediaSource delegatingMediaSource = + new WrappingMediaSource(underlyingSource) { @Override - public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { - super.prepareSourceInternal(mediaTransferListener); + public void prepareSourceInternal() { underlyingSource.addMediaSource( new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT)); underlyingSource.addMediaSource( new FakeMediaSource(fakeTimeline, ExoPlayerTestRunner.VIDEO_FORMAT)); - prepareChildSource(null, underlyingSource); - } - - @Override - public MediaPeriod createPeriod( - MediaPeriodId id, Allocator allocator, long startPositionUs) { - return underlyingSource.createPeriod(id, allocator, startPositionUs); - } - - @Override - public void releasePeriod(MediaPeriod mediaPeriod) { - underlyingSource.releasePeriod(mediaPeriod); - } - - @Override - protected void onChildSourceInfoRefreshed( - Void id, MediaSource mediaSource, Timeline timeline) { - refreshSourceInfo(timeline); + super.prepareSourceInternal(); } @Override @@ -3966,11 +3948,6 @@ public final class ExoPlayerTest { return false; } - @Override - public MediaItem getMediaItem() { - return underlyingSource.getMediaItem(); - } - @Override @Nullable public Timeline getInitialTimeline() {