Add custom MediaSource.Factory attribute to CompositionPlayer

Introduced a `mediaSourceFactory` attribute to CompositionPlayer. This allows for the use of a custom MediaSource.Factory when creating media sources, providing more flexibility and control over media source creation. Previously, the default `MediaSource.Factory` was used in all cases.

PiperOrigin-RevId: 700391911
This commit is contained in:
shahddaghash 2024-11-26 10:45:17 -08:00 committed by Copybara-Service
parent 96c35966d8
commit d5d5771cd2
2 changed files with 21 additions and 24 deletions

View File

@ -54,7 +54,9 @@ import androidx.media3.exoplayer.RendererCapabilities;
import androidx.media3.exoplayer.image.BitmapFactoryImageDecoder; import androidx.media3.exoplayer.image.BitmapFactoryImageDecoder;
import androidx.media3.exoplayer.image.ImageDecoder; import androidx.media3.exoplayer.image.ImageDecoder;
import androidx.media3.exoplayer.image.ImageDecoderException; import androidx.media3.exoplayer.image.ImageDecoderException;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.ExternalLoader; import androidx.media3.exoplayer.source.ExternalLoader;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.test.utils.TestSpeedProvider; import androidx.media3.test.utils.TestSpeedProvider;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.rules.ActivityScenarioRule;
@ -264,12 +266,15 @@ public class CompositionPlayerTest {
PlayerTestListener listener = new PlayerTestListener(TEST_TIMEOUT_MS); PlayerTestListener listener = new PlayerTestListener(TEST_TIMEOUT_MS);
ExternalLoader externalImageLoader = ExternalLoader externalImageLoader =
loadRequest -> immediateFuture(Util.getUtf8Bytes(loadRequest.uri.toString())); loadRequest -> immediateFuture(Util.getUtf8Bytes(loadRequest.uri.toString()));
MediaSource.Factory mediaSourceFactory =
new DefaultMediaSourceFactory(applicationContext)
.setExternalImageLoader(externalImageLoader);
instrumentation.runOnMainSync( instrumentation.runOnMainSync(
() -> { () -> {
compositionPlayer = compositionPlayer =
new CompositionPlayer.Builder(applicationContext) new CompositionPlayer.Builder(applicationContext)
.setExternalImageLoader(externalImageLoader) .setMediaSourceFactory(mediaSourceFactory)
.setImageDecoderFactory(new TestImageDecoderFactory()) .setImageDecoderFactory(new TestImageDecoderFactory())
.build(); .build();
// Set a surface on the player even though there is no UI on this test. We need a surface // Set a surface on the player even though there is no UI on this test. We need a surface

View File

@ -37,7 +37,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Effect; import androidx.media3.common.Effect;
import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.PreviewingVideoGraph; import androidx.media3.common.PreviewingVideoGraph;
@ -63,7 +62,6 @@ import androidx.media3.exoplayer.image.ImageDecoder;
import androidx.media3.exoplayer.source.ClippingMediaSource; import androidx.media3.exoplayer.source.ClippingMediaSource;
import androidx.media3.exoplayer.source.ConcatenatingMediaSource2; import androidx.media3.exoplayer.source.ConcatenatingMediaSource2;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.ExternalLoader;
import androidx.media3.exoplayer.source.FilteringMediaSource; import androidx.media3.exoplayer.source.FilteringMediaSource;
import androidx.media3.exoplayer.source.ForwardingTimeline; import androidx.media3.exoplayer.source.ForwardingTimeline;
import androidx.media3.exoplayer.source.MediaPeriod; import androidx.media3.exoplayer.source.MediaPeriod;
@ -118,7 +116,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
private @MonotonicNonNull Looper looper; private @MonotonicNonNull Looper looper;
private @MonotonicNonNull AudioSink audioSink; private @MonotonicNonNull AudioSink audioSink;
private @MonotonicNonNull ExternalLoader externalImageLoader; private MediaSource.Factory mediaSourceFactory;
private ImageDecoder.Factory imageDecoderFactory; private ImageDecoder.Factory imageDecoderFactory;
private Clock clock; private Clock clock;
private PreviewingVideoGraph.@MonotonicNonNull Factory previewingVideoGraphFactory; private PreviewingVideoGraph.@MonotonicNonNull Factory previewingVideoGraphFactory;
@ -131,6 +129,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
*/ */
public Builder(Context context) { public Builder(Context context) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
mediaSourceFactory = new DefaultMediaSourceFactory(context);
imageDecoderFactory = ImageDecoder.Factory.DEFAULT; imageDecoderFactory = ImageDecoder.Factory.DEFAULT;
clock = Clock.DEFAULT; clock = Clock.DEFAULT;
} }
@ -165,20 +164,19 @@ public final class CompositionPlayer extends SimpleBasePlayer
} }
/** /**
* Sets the {@link ExternalLoader} for loading image media items with MIME type set to {@link * Sets the {@link MediaSource.Factory} that *creates* the {@link MediaSource} for {@link
* MimeTypes#APPLICATION_EXTERNALLY_LOADED_IMAGE}. When setting an external loader, also set an * EditedMediaItem#mediaItem MediaItems} in a {@link Composition}.
* {@link ImageDecoder.Factory} with {@link #setImageDecoderFactory(ImageDecoder.Factory)}.
* *
* <p>By default, the player will not be able to load images with media type of {@link * <p>To use an external image loader, one could create a {@link DefaultMediaSourceFactory},
* androidx.media3.common.MimeTypes#APPLICATION_EXTERNALLY_LOADED_IMAGE}. * {@linkplain DefaultMediaSourceFactory#setExternalImageLoader set the external image loader},
* and pass in the {@link DefaultMediaSourceFactory} here.
* *
* @param externalImageLoader The {@link ExternalLoader}. * @param mediaSourceFactory The {@link MediaSource.Factory}
* @return This builder, for convenience. * @return This builder, for convenience.
* @see DefaultMediaSourceFactory#setExternalImageLoader(ExternalLoader)
*/ */
@CanIgnoreReturnValue @CanIgnoreReturnValue
public Builder setExternalImageLoader(ExternalLoader externalImageLoader) { public Builder setMediaSourceFactory(MediaSource.Factory mediaSourceFactory) {
this.externalImageLoader = externalImageLoader; this.mediaSourceFactory = mediaSourceFactory;
return this; return this;
} }
@ -287,7 +285,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
private final HandlerWrapper applicationHandler; private final HandlerWrapper applicationHandler;
private final List<ExoPlayer> players; private final List<ExoPlayer> players;
private final AudioSink finalAudioSink; private final AudioSink finalAudioSink;
@Nullable private final ExternalLoader externalImageLoader; private final MediaSource.Factory mediaSourceFactory;
private final ImageDecoder.Factory imageDecoderFactory; private final ImageDecoder.Factory imageDecoderFactory;
private final PreviewingVideoGraph.Factory previewingVideoGraphFactory; private final PreviewingVideoGraph.Factory previewingVideoGraphFactory;
private final HandlerWrapper compositionInternalListenerHandler; private final HandlerWrapper compositionInternalListenerHandler;
@ -316,7 +314,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
clock = builder.clock; clock = builder.clock;
applicationHandler = clock.createHandler(builder.looper, /* callback= */ null); applicationHandler = clock.createHandler(builder.looper, /* callback= */ null);
finalAudioSink = checkNotNull(builder.audioSink); finalAudioSink = checkNotNull(builder.audioSink);
externalImageLoader = builder.externalImageLoader; mediaSourceFactory = builder.mediaSourceFactory;
imageDecoderFactory = builder.imageDecoderFactory; imageDecoderFactory = builder.imageDecoderFactory;
previewingVideoGraphFactory = checkNotNull(builder.previewingVideoGraphFactory); previewingVideoGraphFactory = checkNotNull(builder.previewingVideoGraphFactory);
compositionInternalListenerHandler = clock.createHandler(builder.looper, /* callback= */ null); compositionInternalListenerHandler = clock.createHandler(builder.looper, /* callback= */ null);
@ -750,10 +748,6 @@ public final class CompositionPlayer extends SimpleBasePlayer
private void setPrimaryPlayerSequence(ExoPlayer player, EditedMediaItemSequence sequence) { private void setPrimaryPlayerSequence(ExoPlayer player, EditedMediaItemSequence sequence) {
ConcatenatingMediaSource2.Builder mediaSourceBuilder = new ConcatenatingMediaSource2.Builder(); ConcatenatingMediaSource2.Builder mediaSourceBuilder = new ConcatenatingMediaSource2.Builder();
DefaultMediaSourceFactory defaultMediaSourceFactory = new DefaultMediaSourceFactory(context);
if (externalImageLoader != null) {
defaultMediaSourceFactory.setExternalImageLoader(externalImageLoader);
}
for (int i = 0; i < sequence.editedMediaItems.size(); i++) { for (int i = 0; i < sequence.editedMediaItems.size(); i++) {
EditedMediaItem editedMediaItem = sequence.editedMediaItems.get(i); EditedMediaItem editedMediaItem = sequence.editedMediaItems.get(i);
@ -767,8 +761,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
editedMediaItem.mediaItem.clippingConfiguration.endPositionUs); editedMediaItem.mediaItem.clippingConfiguration.endPositionUs);
// The MediaSource that loads the MediaItem // The MediaSource that loads the MediaItem
MediaSource mainMediaSource = MediaSource mainMediaSource = mediaSourceFactory.createMediaSource(editedMediaItem.mediaItem);
defaultMediaSourceFactory.createMediaSource(editedMediaItem.mediaItem);
if (editedMediaItem.removeAudio) { if (editedMediaItem.removeAudio) {
mainMediaSource = mainMediaSource =
new FilteringMediaSource( new FilteringMediaSource(
@ -791,7 +784,6 @@ public final class CompositionPlayer extends SimpleBasePlayer
// TODO: b/331392198 - Repeat only looping sequences, after sequences can be of arbitrary // TODO: b/331392198 - Repeat only looping sequences, after sequences can be of arbitrary
// length. // length.
ConcatenatingMediaSource2.Builder mediaSourceBuilder = new ConcatenatingMediaSource2.Builder(); ConcatenatingMediaSource2.Builder mediaSourceBuilder = new ConcatenatingMediaSource2.Builder();
DefaultMediaSourceFactory defaultMediaSourceFactory = new DefaultMediaSourceFactory(context);
long accumulatedDurationUs = 0; long accumulatedDurationUs = 0;
int i = 0; int i = 0;
@ -801,7 +793,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
MediaItem mediaItem = editedMediaItem.mediaItem; MediaItem mediaItem = editedMediaItem.mediaItem;
if (accumulatedDurationUs + itemPresentationDurationUs <= primarySequenceDurationUs) { if (accumulatedDurationUs + itemPresentationDurationUs <= primarySequenceDurationUs) {
mediaSourceBuilder.add( mediaSourceBuilder.add(
defaultMediaSourceFactory.createMediaSource(mediaItem), mediaSourceFactory.createMediaSource(mediaItem),
/* initialPlaceholderDurationMs= */ usToMs(itemPresentationDurationUs)); /* initialPlaceholderDurationMs= */ usToMs(itemPresentationDurationUs));
accumulatedDurationUs += itemPresentationDurationUs; accumulatedDurationUs += itemPresentationDurationUs;
} else { } else {
@ -809,7 +801,7 @@ public final class CompositionPlayer extends SimpleBasePlayer
// TODO: b/289989542 - Handle already clipped, or speed adjusted media. // TODO: b/289989542 - Handle already clipped, or speed adjusted media.
mediaSourceBuilder.add( mediaSourceBuilder.add(
new ClippingMediaSource( new ClippingMediaSource(
defaultMediaSourceFactory.createMediaSource(mediaItem), mediaSourceFactory.createMediaSource(mediaItem),
mediaItem.clippingConfiguration.startPositionUs, mediaItem.clippingConfiguration.startPositionUs,
mediaItem.clippingConfiguration.startPositionUs + remainingDurationUs), mediaItem.clippingConfiguration.startPositionUs + remainingDurationUs),
/* initialPlaceholderDurationMs= */ usToMs(remainingDurationUs)); /* initialPlaceholderDurationMs= */ usToMs(remainingDurationUs));