Pass an EditedMediaItem to AssetLoader.Factory

PiperOrigin-RevId: 505671326
This commit is contained in:
kimvde 2023-01-30 14:09:39 +00:00 committed by christosts
parent 32d4686c56
commit f5e31989ab
7 changed files with 46 additions and 150 deletions

View File

@ -22,10 +22,8 @@ import android.os.Looper;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -38,61 +36,34 @@ import java.lang.annotation.Target;
* *
* <p>Only audio and video samples are supported. Both audio and video tracks can be provided by a * <p>Only audio and video samples are supported. Both audio and video tracks can be provided by a
* single asset loader, but outputting multiple tracks of the same type is not supported. * single asset loader, but outputting multiple tracks of the same type is not supported.
*
* <p>An asset loader is responsible for {@linkplain EditedMediaItem.Builder#setRemoveAudio(boolean)
* removing audio} or {@linkplain EditedMediaItem.Builder#setRemoveVideo(boolean) video} if
* requested.
*
* <p>If {@linkplain EditedMediaItem.Builder#setFlattenForSlowMotion(boolean) slow motion
* flattening} is requested, the asset loader should flatten the video track for media containing
* slow motion markers. This is usually done prior to decoding. The audio samples are flattened
* after they are output by the {@link AssetLoader}, because this is done on decoded samples.
*/ */
@UnstableApi @UnstableApi
public interface AssetLoader { public interface AssetLoader {
/** /** A factory for {@link AssetLoader} instances. */
* A factory for {@link AssetLoader} instances.
*
* <p>The setters in this interface will be called with the values used to build and start the
* {@link Transformer}.
*/
interface Factory { interface Factory {
/** /**
* Sets whether to remove the audio samples from the output (if any). * Creates an {@link AssetLoader} instance.
* *
* <p>The audio and video cannot both be removed because the output would not contain any * @param editedMediaItem The {@link EditedMediaItem} to load.
* samples.
*/
@CanIgnoreReturnValue
Factory setRemoveAudio(boolean removeAudio);
/**
* Sets whether to remove the video samples from the output (if any).
*
* <p>The audio and video cannot both be removed because the output would not contain any
* samples.
*/
@CanIgnoreReturnValue
Factory setRemoveVideo(boolean removeVideo);
/**
* Sets whether the video samples should be flattened prior to decoding for media containing
* slow motion markers.
*
* <p>The audio samples are flattened after they are output by the {@link AssetLoader}, because
* this is done on decoded samples.
*
* <p>For more information on slow motion flattening, see {@link
* EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)}.
*/
@CanIgnoreReturnValue
Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion);
/**
* Creates an {@link AssetLoader} instance. All the setters in this factory must be called
* before creating the {@link AssetLoader}.
*
* @param mediaItem The {@link MediaItem} to load.
* @param looper The {@link Looper} that's used to access the {@link AssetLoader} after it's * @param looper The {@link Looper} that's used to access the {@link AssetLoader} after it's
* been created. * been created.
* @param listener The {@link Listener} on which the {@link AssetLoader} should notify of * @param listener The {@link Listener} on which the {@link AssetLoader} should notify of
* events. * events.
* @return An {@link AssetLoader}. * @return An {@link AssetLoader}.
*/ */
AssetLoader createAssetLoader(MediaItem mediaItem, Looper looper, Listener listener); AssetLoader createAssetLoader(
EditedMediaItem editedMediaItem, Looper looper, Listener listener);
} }
/** /**

View File

@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.ColorInfo; import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper; import androidx.media3.common.util.HandlerWrapper;
@ -44,7 +43,7 @@ import java.util.concurrent.atomic.AtomicLong;
*/ */
/* package */ final class CompositeAssetLoader implements AssetLoader, AssetLoader.Listener { /* package */ final class CompositeAssetLoader implements AssetLoader, AssetLoader.Listener {
private final List<MediaItem> mediaItems; private final List<EditedMediaItem> editedMediaItems;
private final AtomicInteger currentMediaItemIndex; private final AtomicInteger currentMediaItemIndex;
private final AssetLoader.Factory assetLoaderFactory; private final AssetLoader.Factory assetLoaderFactory;
private final HandlerWrapper handler; private final HandlerWrapper handler;
@ -58,12 +57,12 @@ import java.util.concurrent.atomic.AtomicLong;
private volatile long currentDurationUs; private volatile long currentDurationUs;
public CompositeAssetLoader( public CompositeAssetLoader(
List<MediaItem> mediaItems, List<EditedMediaItem> editedMediaItems,
AssetLoader.Factory assetLoaderFactory, AssetLoader.Factory assetLoaderFactory,
Looper looper, Looper looper,
Listener listener, Listener listener,
Clock clock) { Clock clock) {
this.mediaItems = mediaItems; this.editedMediaItems = editedMediaItems;
this.assetLoaderFactory = assetLoaderFactory; this.assetLoaderFactory = assetLoaderFactory;
compositeAssetLoaderListener = listener; compositeAssetLoaderListener = listener;
currentMediaItemIndex = new AtomicInteger(); currentMediaItemIndex = new AtomicInteger();
@ -75,7 +74,7 @@ import java.util.concurrent.atomic.AtomicLong;
// constructor. // constructor.
@SuppressWarnings("nullness:argument.type.incompatible") @SuppressWarnings("nullness:argument.type.incompatible")
AssetLoader currentAssetLoader = AssetLoader currentAssetLoader =
assetLoaderFactory.createAssetLoader(mediaItems.get(0), looper, /* listener= */ this); assetLoaderFactory.createAssetLoader(editedMediaItems.get(0), looper, /* listener= */ this);
this.currentAssetLoader = currentAssetLoader; this.currentAssetLoader = currentAssetLoader;
} }
@ -87,7 +86,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Override @Override
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) { public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
int progressState = currentAssetLoader.getProgress(progressHolder); int progressState = currentAssetLoader.getProgress(progressHolder);
int mediaItemCount = mediaItems.size(); int mediaItemCount = editedMediaItems.size();
if (mediaItemCount == 1 || progressState == PROGRESS_STATE_NOT_STARTED) { if (mediaItemCount == 1 || progressState == PROGRESS_STATE_NOT_STARTED) {
return progressState; return progressState;
} }
@ -116,7 +115,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Override @Override
public void onDurationUs(long durationUs) { public void onDurationUs(long durationUs) {
currentDurationUs = durationUs; currentDurationUs = durationUs;
if (mediaItems.size() == 1) { if (editedMediaItems.size() == 1) {
compositeAssetLoaderListener.onDurationUs(durationUs); compositeAssetLoaderListener.onDurationUs(durationUs);
} else if (currentMediaItemIndex.get() == 0) { } else if (currentMediaItemIndex.get() == 0) {
// TODO(b/252537210): support silent audio track for sequence of AssetLoaders (silent audio // TODO(b/252537210): support silent audio track for sequence of AssetLoaders (silent audio
@ -194,7 +193,7 @@ import java.util.concurrent.atomic.AtomicLong;
DecoderInputBuffer inputBuffer = checkStateNotNull(sampleConsumer.getInputBuffer()); DecoderInputBuffer inputBuffer = checkStateNotNull(sampleConsumer.getInputBuffer());
if (inputBuffer.isEndOfStream()) { if (inputBuffer.isEndOfStream()) {
nonEndedTracks.decrementAndGet(); nonEndedTracks.decrementAndGet();
if (currentMediaItemIndex.get() < mediaItems.size() - 1) { if (currentMediaItemIndex.get() < editedMediaItems.size() - 1) {
if (nonEndedTracks.get() == 0) { if (nonEndedTracks.get() == 0) {
switchAssetLoader(); switchAssetLoader();
} }
@ -233,7 +232,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Override @Override
public void signalEndOfVideoInput() { public void signalEndOfVideoInput() {
nonEndedTracks.decrementAndGet(); nonEndedTracks.decrementAndGet();
if (currentMediaItemIndex.get() < mediaItems.size() - 1) { if (currentMediaItemIndex.get() < editedMediaItems.size() - 1) {
if (nonEndedTracks.get() == 0) { if (nonEndedTracks.get() == 0) {
switchAssetLoader(); switchAssetLoader();
sampleConsumer.setVideoOffsetToAddUs(totalDurationUs.get()); sampleConsumer.setVideoOffsetToAddUs(totalDurationUs.get());
@ -248,10 +247,11 @@ import java.util.concurrent.atomic.AtomicLong;
handler.post( handler.post(
() -> { () -> {
currentAssetLoader.release(); currentAssetLoader.release();
MediaItem mediaItem = mediaItems.get(currentMediaItemIndex.incrementAndGet()); EditedMediaItem editedMediaItem =
editedMediaItems.get(currentMediaItemIndex.incrementAndGet());
currentAssetLoader = currentAssetLoader =
assetLoaderFactory.createAssetLoader( assetLoaderFactory.createAssetLoader(
mediaItem, editedMediaItem,
checkNotNull(Looper.myLooper()), checkNotNull(Looper.myLooper()),
/* listener= */ CompositeAssetLoader.this); /* listener= */ CompositeAssetLoader.this);
currentAssetLoader.start(); currentAssetLoader.start();

View File

@ -18,11 +18,9 @@ package androidx.media3.transformer;
import android.content.Context; import android.content.Context;
import android.os.Looper; import android.os.Looper;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Clock; import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MediaSource;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** The default {@link AssetLoader.Factory} implementation. */ /** The default {@link AssetLoader.Factory} implementation. */
@UnstableApi @UnstableApi
@ -64,28 +62,9 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory); new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory);
} }
@Override
@CanIgnoreReturnValue
public AssetLoader.Factory setRemoveAudio(boolean removeAudio) {
return assetLoaderFactory.setRemoveAudio(removeAudio);
}
@Override
@CanIgnoreReturnValue
public AssetLoader.Factory setRemoveVideo(boolean removeVideo) {
return assetLoaderFactory.setRemoveVideo(removeVideo);
}
@Override
@CanIgnoreReturnValue
public AssetLoader.Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion) {
assetLoaderFactory.setFlattenVideoForSlowMotion(flattenVideoForSlowMotion);
return this;
}
@Override @Override
public AssetLoader createAssetLoader( public AssetLoader createAssetLoader(
MediaItem mediaItem, Looper looper, AssetLoader.Listener listener) { EditedMediaItem editedMediaItem, Looper looper, AssetLoader.Listener listener) {
return assetLoaderFactory.createAssetLoader(mediaItem, looper, listener); return assetLoaderFactory.createAssetLoader(editedMediaItem, looper, listener);
} }
} }

View File

@ -34,7 +34,6 @@ import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException; import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
@ -55,7 +54,6 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
import androidx.media3.extractor.DefaultExtractorsFactory; import androidx.media3.extractor.DefaultExtractorsFactory;
import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** An {@link AssetLoader} implementation that uses an {@link ExoPlayer} to load samples. */ /** An {@link AssetLoader} implementation that uses an {@link ExoPlayer} to load samples. */
@UnstableApi @UnstableApi
@ -69,10 +67,6 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
private final Clock clock; private final Clock clock;
@Nullable private final MediaSource.Factory mediaSourceFactory; @Nullable private final MediaSource.Factory mediaSourceFactory;
private boolean removeAudio;
private boolean removeVideo;
private boolean flattenVideoForSlowMotion;
/** /**
* Creates an instance using a {@link DefaultMediaSourceFactory}. * Creates an instance using a {@link DefaultMediaSourceFactory}.
* *
@ -112,51 +106,22 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
} }
@Override @Override
@CanIgnoreReturnValue public AssetLoader createAssetLoader(
public AssetLoader.Factory setRemoveAudio(boolean removeAudio) { EditedMediaItem editedMediaItem, Looper looper, Listener listener) {
this.removeAudio = removeAudio;
return this;
}
@Override
@CanIgnoreReturnValue
public AssetLoader.Factory setRemoveVideo(boolean removeVideo) {
this.removeVideo = removeVideo;
return this;
}
@Override
@CanIgnoreReturnValue
public AssetLoader.Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion) {
this.flattenVideoForSlowMotion = flattenVideoForSlowMotion;
return this;
}
@Override
public AssetLoader createAssetLoader(MediaItem mediaItem, Looper looper, Listener listener) {
MediaSource.Factory mediaSourceFactory = this.mediaSourceFactory; MediaSource.Factory mediaSourceFactory = this.mediaSourceFactory;
if (mediaSourceFactory == null) { if (mediaSourceFactory == null) {
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory(); DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
if (flattenVideoForSlowMotion) { if (editedMediaItem.flattenForSlowMotion) {
defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA); defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA);
} }
mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory); mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory);
} }
return new ExoPlayerAssetLoader( return new ExoPlayerAssetLoader(
context, context, editedMediaItem, mediaSourceFactory, decoderFactory, looper, listener, clock);
mediaItem,
removeAudio,
removeVideo,
flattenVideoForSlowMotion,
mediaSourceFactory,
decoderFactory,
looper,
listener,
clock);
} }
} }
private final MediaItem mediaItem; private final EditedMediaItem editedMediaItem;
private final CapturingDecoderFactory decoderFactory; private final CapturingDecoderFactory decoderFactory;
private final ExoPlayer player; private final ExoPlayer player;
@ -164,16 +129,13 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
private ExoPlayerAssetLoader( private ExoPlayerAssetLoader(
Context context, Context context,
MediaItem mediaItem, EditedMediaItem editedMediaItem,
boolean removeAudio,
boolean removeVideo,
boolean flattenForSlowMotion,
MediaSource.Factory mediaSourceFactory, MediaSource.Factory mediaSourceFactory,
Codec.DecoderFactory decoderFactory, Codec.DecoderFactory decoderFactory,
Looper looper, Looper looper,
Listener listener, Listener listener,
Clock clock) { Clock clock) {
this.mediaItem = mediaItem; this.editedMediaItem = editedMediaItem;
this.decoderFactory = new CapturingDecoderFactory(decoderFactory); this.decoderFactory = new CapturingDecoderFactory(decoderFactory);
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context); DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
@ -195,7 +157,11 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
new ExoPlayer.Builder( new ExoPlayer.Builder(
context, context,
new RenderersFactoryImpl( new RenderersFactoryImpl(
removeAudio, removeVideo, flattenForSlowMotion, this.decoderFactory, listener)) editedMediaItem.removeAudio,
editedMediaItem.removeVideo,
editedMediaItem.flattenForSlowMotion,
this.decoderFactory,
listener))
.setMediaSourceFactory(mediaSourceFactory) .setMediaSourceFactory(mediaSourceFactory)
.setTrackSelector(trackSelector) .setTrackSelector(trackSelector)
.setLoadControl(loadControl) .setLoadControl(loadControl)
@ -214,7 +180,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
@Override @Override
public void start() { public void start() {
player.setMediaItem(mediaItem); player.setMediaItem(editedMediaItem.mediaItem);
player.prepare(); player.prepare();
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
} }

View File

@ -136,11 +136,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
Looper internalLooper = internalHandlerThread.getLooper(); Looper internalLooper = internalHandlerThread.getLooper();
ComponentListener componentListener = new ComponentListener(editedMediaItem, fallbackListener); ComponentListener componentListener = new ComponentListener(editedMediaItem, fallbackListener);
assetLoader = assetLoader =
assetLoaderFactory assetLoaderFactory.createAssetLoader(editedMediaItem, internalLooper, componentListener);
.setRemoveAudio(editedMediaItem.removeAudio)
.setRemoveVideo(editedMediaItem.removeVideo)
.setFlattenVideoForSlowMotion(editedMediaItem.flattenForSlowMotion)
.createAssetLoader(editedMediaItem.mediaItem, internalLooper, componentListener);
samplePipelines = new ArrayList<>(); samplePipelines = new ArrayList<>();
muxerWrapper = new MuxerWrapper(outputPath, muxerFactory, componentListener); muxerWrapper = new MuxerWrapper(outputPath, muxerFactory, componentListener);
transformerConditionVariable = new ConditionVariable(); transformerConditionVariable = new ConditionVariable();

View File

@ -117,12 +117,10 @@ public class ExoPlayerAssetLoaderTest {
Looper looper, AssetLoader.Listener listener, Clock clock) { Looper looper, AssetLoader.Listener listener, Clock clock) {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context); Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
MediaItem mediaItem = MediaItem.fromUri("asset:///media/mp4/sample.mp4"); EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri("asset:///media/mp4/sample.mp4")).build();
return new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock) return new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock)
.setRemoveAudio(false) .createAssetLoader(editedMediaItem, looper, listener);
.setRemoveVideo(false)
.setFlattenVideoForSlowMotion(false)
.createAssetLoader(mediaItem, looper, listener);
} }
private static final class FakeSampleConsumer implements SampleConsumer { private static final class FakeSampleConsumer implements SampleConsumer {

View File

@ -1229,22 +1229,8 @@ public final class TransformerEndToEndTest {
} }
@Override @Override
public AssetLoader.Factory setRemoveAudio(boolean removeAudio) { public AssetLoader createAssetLoader(
return this; EditedMediaItem editedMediaItem, Looper looper, Listener listener) {
}
@Override
public AssetLoader.Factory setRemoveVideo(boolean removeVideo) {
return this;
}
@Override
public AssetLoader.Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion) {
return this;
}
@Override
public AssetLoader createAssetLoader(MediaItem mediaItem, Looper looper, Listener listener) {
return new FakeAssetLoader(listener, supportedOutputTypes, sampleConsumerRef); return new FakeAssetLoader(listener, supportedOutputTypes, sampleConsumerRef);
} }
} }