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.IntRange;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
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
* 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
public interface AssetLoader {
/**
* 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}.
*/
/** A factory for {@link AssetLoader} instances. */
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
* 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 editedMediaItem The {@link EditedMediaItem} to load.
* @param looper The {@link Looper} that's used to access the {@link AssetLoader} after it's
* been created.
* @param listener The {@link Listener} on which the {@link AssetLoader} should notify of
* events.
* @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.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
@ -44,7 +43,7 @@ import java.util.concurrent.atomic.AtomicLong;
*/
/* package */ final class CompositeAssetLoader implements AssetLoader, AssetLoader.Listener {
private final List<MediaItem> mediaItems;
private final List<EditedMediaItem> editedMediaItems;
private final AtomicInteger currentMediaItemIndex;
private final AssetLoader.Factory assetLoaderFactory;
private final HandlerWrapper handler;
@ -58,12 +57,12 @@ import java.util.concurrent.atomic.AtomicLong;
private volatile long currentDurationUs;
public CompositeAssetLoader(
List<MediaItem> mediaItems,
List<EditedMediaItem> editedMediaItems,
AssetLoader.Factory assetLoaderFactory,
Looper looper,
Listener listener,
Clock clock) {
this.mediaItems = mediaItems;
this.editedMediaItems = editedMediaItems;
this.assetLoaderFactory = assetLoaderFactory;
compositeAssetLoaderListener = listener;
currentMediaItemIndex = new AtomicInteger();
@ -75,7 +74,7 @@ import java.util.concurrent.atomic.AtomicLong;
// constructor.
@SuppressWarnings("nullness:argument.type.incompatible")
AssetLoader currentAssetLoader =
assetLoaderFactory.createAssetLoader(mediaItems.get(0), looper, /* listener= */ this);
assetLoaderFactory.createAssetLoader(editedMediaItems.get(0), looper, /* listener= */ this);
this.currentAssetLoader = currentAssetLoader;
}
@ -87,7 +86,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Override
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
int progressState = currentAssetLoader.getProgress(progressHolder);
int mediaItemCount = mediaItems.size();
int mediaItemCount = editedMediaItems.size();
if (mediaItemCount == 1 || progressState == PROGRESS_STATE_NOT_STARTED) {
return progressState;
}
@ -116,7 +115,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Override
public void onDurationUs(long durationUs) {
currentDurationUs = durationUs;
if (mediaItems.size() == 1) {
if (editedMediaItems.size() == 1) {
compositeAssetLoaderListener.onDurationUs(durationUs);
} else if (currentMediaItemIndex.get() == 0) {
// 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());
if (inputBuffer.isEndOfStream()) {
nonEndedTracks.decrementAndGet();
if (currentMediaItemIndex.get() < mediaItems.size() - 1) {
if (currentMediaItemIndex.get() < editedMediaItems.size() - 1) {
if (nonEndedTracks.get() == 0) {
switchAssetLoader();
}
@ -233,7 +232,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Override
public void signalEndOfVideoInput() {
nonEndedTracks.decrementAndGet();
if (currentMediaItemIndex.get() < mediaItems.size() - 1) {
if (currentMediaItemIndex.get() < editedMediaItems.size() - 1) {
if (nonEndedTracks.get() == 0) {
switchAssetLoader();
sampleConsumer.setVideoOffsetToAddUs(totalDurationUs.get());
@ -248,10 +247,11 @@ import java.util.concurrent.atomic.AtomicLong;
handler.post(
() -> {
currentAssetLoader.release();
MediaItem mediaItem = mediaItems.get(currentMediaItemIndex.incrementAndGet());
EditedMediaItem editedMediaItem =
editedMediaItems.get(currentMediaItemIndex.incrementAndGet());
currentAssetLoader =
assetLoaderFactory.createAssetLoader(
mediaItem,
editedMediaItem,
checkNotNull(Looper.myLooper()),
/* listener= */ CompositeAssetLoader.this);
currentAssetLoader.start();

View File

@ -18,11 +18,9 @@ package androidx.media3.transformer;
import android.content.Context;
import android.os.Looper;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.source.MediaSource;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** The default {@link AssetLoader.Factory} implementation. */
@UnstableApi
@ -64,28 +62,9 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
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
public AssetLoader createAssetLoader(
MediaItem mediaItem, Looper looper, AssetLoader.Listener listener) {
return assetLoaderFactory.createAssetLoader(mediaItem, looper, listener);
EditedMediaItem editedMediaItem, Looper looper, AssetLoader.Listener listener) {
return assetLoaderFactory.createAssetLoader(editedMediaItem, looper, listener);
}
}

View File

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

View File

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

View File

@ -117,12 +117,10 @@ public class ExoPlayerAssetLoaderTest {
Looper looper, AssetLoader.Listener listener, Clock clock) {
Context context = ApplicationProvider.getApplicationContext();
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)
.setRemoveAudio(false)
.setRemoveVideo(false)
.setFlattenVideoForSlowMotion(false)
.createAssetLoader(mediaItem, looper, listener);
.createAssetLoader(editedMediaItem, looper, listener);
}
private static final class FakeSampleConsumer implements SampleConsumer {

View File

@ -1229,22 +1229,8 @@ public final class TransformerEndToEndTest {
}
@Override
public AssetLoader.Factory setRemoveAudio(boolean removeAudio) {
return this;
}
@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) {
public AssetLoader createAssetLoader(
EditedMediaItem editedMediaItem, Looper looper, Listener listener) {
return new FakeAssetLoader(listener, supportedOutputTypes, sampleConsumerRef);
}
}