diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java
index e2df0f4fd5..7e7a730b0f 100644
--- a/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java
+++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java
@@ -272,8 +272,6 @@ public final class TransformerActivity extends AppCompatActivity {
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
if (bundle != null) {
TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder();
- requestBuilder.setFlattenForSlowMotion(
- bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION));
@Nullable String audioMimeType = bundle.getString(ConfigurationActivity.AUDIO_MIME_TYPE);
if (audioMimeType != null) {
requestBuilder.setAudioMimeType(audioMimeType);
@@ -352,6 +350,8 @@ public final class TransformerActivity extends AppCompatActivity {
return editedMediaItemBuilder
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
+ .setFlattenForSlowMotion(
+ bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION))
.setEffects(new Effects(audioProcessors, videoEffects))
.build();
}
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java
index e9e98553e8..d574c5775c 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java
@@ -35,7 +35,6 @@ import androidx.media3.transformer.AndroidTestUtil.ForceEncodeEncoderFactory;
import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.Effects;
-import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.media3.transformer.VideoEncoderSettings;
@@ -203,13 +202,11 @@ public class TransformationTest {
return;
}
- Transformer transformer =
- new Transformer.Builder(context)
- .setTransformationRequest(
- new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
- .build();
+ Transformer transformer = new Transformer.Builder(context).build();
EditedMediaItem editedMediaItem =
- new EditedMediaItem.Builder(MediaItem.fromUri(Uri.parse(MP4_ASSET_SEF_URI_STRING))).build();
+ new EditedMediaItem.Builder(MediaItem.fromUri(Uri.parse(MP4_ASSET_SEF_URI_STRING)))
+ .setFlattenForSlowMotion(true)
+ .build();
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java
index 535dd37444..8ee4b990e4 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java
@@ -76,7 +76,7 @@ public interface AssetLoader {
* this is done on decoded samples.
*
*
For more information on slow motion flattening, see {@link
- * TransformationRequest.Builder#setFlattenForSlowMotion(boolean)}.
+ * EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)}.
*/
@CanIgnoreReturnValue
Factory setFlattenVideoForSlowMotion(boolean flattenVideoForSlowMotion);
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java
index 2e463c4c01..f98bef791d 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java
@@ -63,6 +63,7 @@ import org.checkerframework.dataflow.qual.Pure;
long streamStartPositionUs,
long streamOffsetUs,
TransformationRequest transformationRequest,
+ boolean flattenForSlowMotion,
ImmutableList audioProcessors,
long generateSilentAudioDurationUs,
Codec.EncoderFactory encoderFactory,
@@ -89,7 +90,7 @@ import org.checkerframework.dataflow.qual.Pure;
encoderInputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
encoderOutputBuffer = new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
- if (transformationRequest.flattenForSlowMotion) {
+ if (flattenForSlowMotion) {
audioProcessors =
new ImmutableList.Builder()
.add(new SpeedChangingAudioProcessor(new SegmentSpeedProvider(inputFormat)))
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java
index 9e0e30e633..ce7cf92613 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java
@@ -34,20 +34,34 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory {
* Creates an instance.
*
* @param context The {@link Context}.
- * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to
- * transform when an {@link ExoPlayerAssetLoader} is used.
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
* necessary).
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
* testing.
*/
+ public DefaultAssetLoaderFactory(
+ Context context, Codec.DecoderFactory decoderFactory, Clock clock) {
+ assetLoaderFactory = new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock);
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The {@link Context}.
+ * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
+ * necessary).
+ * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
+ * testing.
+ * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to
+ * transform when an {@link ExoPlayerAssetLoader} is used.
+ */
public DefaultAssetLoaderFactory(
Context context,
- MediaSource.Factory mediaSourceFactory,
Codec.DecoderFactory decoderFactory,
- Clock clock) {
+ Clock clock,
+ MediaSource.Factory mediaSourceFactory) {
assetLoaderFactory =
- new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock);
+ new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory);
}
@Override
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java b/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java
index b85ffd4a31..9720501905 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/EditedMediaItem.java
@@ -15,10 +15,13 @@
*/
package androidx.media3.transformer;
+import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
+import androidx.media3.exoplayer.source.MediaSource;
+import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -34,6 +37,7 @@ public class EditedMediaItem {
private boolean removeAudio;
private boolean removeVideo;
+ private boolean flattenForSlowMotion;
private @MonotonicNonNull Effects effects;
/**
@@ -79,6 +83,47 @@ public class EditedMediaItem {
return this;
}
+ /**
+ * Sets whether to flatten the {@link MediaItem} if it contains slow motion markers.
+ *
+ * The default value is {@code false}.
+ *
+ *
The flattened output is obtained by removing the slow motion metadata and by actually
+ * slowing down the parts of the video and audio streams defined in this metadata.
+ *
+ *
Only Samsung Extension Format (SEF) slow motion metadata type is supported. Flattening has
+ * no effect if the input does not contain this metadata type.
+ *
+ *
For SEF slow motion media, the following assumptions are made on the input:
+ *
+ *
+ * - The input container format is (unfragmented) MP4.
+ *
- The input contains an AVC video elementary stream with temporal SVC.
+ *
- The recording frame rate of the video is 120 or 240 fps.
+ *
+ *
+ * If using an {@link ExoPlayerAssetLoader.Factory} with a provided {@link
+ * MediaSource.Factory}, make sure that {@link Mp4Extractor#FLAG_READ_SEF_DATA} is set on the
+ * {@link Mp4Extractor} used. Otherwise, the slow motion metadata will be ignored and the input
+ * won't be flattened.
+ *
+ *
Using slow motion flattening together with {@link MediaItem.ClippingConfiguration} is not
+ * supported yet.
+ *
+ * @param flattenForSlowMotion Whether to flatten for slow motion.
+ * @return This builder.
+ */
+ @CanIgnoreReturnValue
+ public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
+ // TODO(b/233986762): Support clipping with SEF flattening.
+ checkArgument(
+ mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
+ || !flattenForSlowMotion,
+ "Slow motion flattening is not supported when clipping is requested");
+ this.flattenForSlowMotion = flattenForSlowMotion;
+ return this;
+ }
+
/**
* Sets the {@link Effects} to apply to the {@link MediaItem}.
*
@@ -100,21 +145,28 @@ public class EditedMediaItem {
new Effects(
/* audioProcessors= */ ImmutableList.of(), /* videoEffects= */ ImmutableList.of());
}
- return new EditedMediaItem(mediaItem, removeAudio, removeVideo, effects);
+ return new EditedMediaItem(
+ mediaItem, removeAudio, removeVideo, flattenForSlowMotion, effects);
}
}
/* package */ final MediaItem mediaItem;
/* package */ final boolean removeAudio;
/* package */ final boolean removeVideo;
+ /* package */ final boolean flattenForSlowMotion;
/* package */ final Effects effects;
private EditedMediaItem(
- MediaItem mediaItem, boolean removeAudio, boolean removeVideo, Effects effects) {
+ MediaItem mediaItem,
+ boolean removeAudio,
+ boolean removeVideo,
+ boolean flattenForSlowMotion,
+ Effects effects) {
checkState(!removeAudio || !removeVideo, "Audio and video cannot both be removed");
this.mediaItem = mediaItem;
this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
+ this.flattenForSlowMotion = flattenForSlowMotion;
this.effects = effects;
}
}
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Effects.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Effects.java
index c6b2348753..5fcb0f63ef 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/Effects.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Effects.java
@@ -49,8 +49,7 @@ public final class Effects {
* They are applied in the order of the list, and buffers will only be modified by that {@link
* AudioProcessor} if it {@link AudioProcessor#isActive()} based on the current configuration.
* @param videoEffects The list of {@link Effect} instances to apply to each video frame. They are
- * applied in the order of the list, after {@linkplain
- * TransformationRequest.Builder#setFlattenForSlowMotion(boolean) slow-motion flattening}.
+ * applied in the order of the list.
* @param frameProcessorFactory The {@link FrameProcessor.Factory} for the {@link FrameProcessor}
* to use when applying the {@code videoEffects} to the video frames.
*/
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java
index 85e3a425c0..91990daf44 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java
@@ -47,10 +47,13 @@ import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.metadata.MetadataOutput;
+import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
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;
@@ -62,34 +65,50 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
public static final class Factory implements AssetLoader.Factory {
private final Context context;
- private final MediaSource.Factory mediaSourceFactory;
private final Codec.DecoderFactory decoderFactory;
private final Clock clock;
+ @Nullable private final MediaSource.Factory mediaSourceFactory;
private boolean removeAudio;
private boolean removeVideo;
private boolean flattenVideoForSlowMotion;
/**
- * Creates an instance.
+ * Creates an instance using a {@link DefaultMediaSourceFactory}.
*
* @param context The {@link Context}.
- * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to
- * transform.
* @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
* necessary).
* @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
* testing.
*/
- public Factory(
- Context context,
- MediaSource.Factory mediaSourceFactory,
- Codec.DecoderFactory decoderFactory,
- Clock clock) {
+ public Factory(Context context, Codec.DecoderFactory decoderFactory, Clock clock) {
this.context = context;
- this.mediaSourceFactory = mediaSourceFactory;
this.decoderFactory = decoderFactory;
this.clock = clock;
+ this.mediaSourceFactory = null;
+ }
+
+ /**
+ * Creates an instance.
+ *
+ * @param context The {@link Context}.
+ * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if
+ * necessary).
+ * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for
+ * testing.
+ * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to
+ * transform.
+ */
+ public Factory(
+ Context context,
+ Codec.DecoderFactory decoderFactory,
+ Clock clock,
+ MediaSource.Factory mediaSourceFactory) {
+ this.context = context;
+ this.decoderFactory = decoderFactory;
+ this.clock = clock;
+ this.mediaSourceFactory = mediaSourceFactory;
}
@Override
@@ -115,6 +134,14 @@ public final class ExoPlayerAssetLoader implements AssetLoader {
@Override
public AssetLoader createAssetLoader(MediaItem mediaItem, Looper looper, Listener listener) {
+ MediaSource.Factory mediaSourceFactory = this.mediaSourceFactory;
+ if (mediaSourceFactory == null) {
+ DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
+ if (flattenVideoForSlowMotion) {
+ defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA);
+ }
+ mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory);
+ }
return new ExoPlayerAssetLoader(
context,
mediaItem,
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java
index dc81be9107..e03a9d199c 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java
@@ -26,8 +26,6 @@ import androidx.media3.common.C;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
-import androidx.media3.exoplayer.source.MediaSource;
-import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -110,7 +108,6 @@ public final class TransformationRequest {
/** A builder for {@link TransformationRequest} instances. */
public static final class Builder {
- private boolean flattenForSlowMotion;
private int outputHeight;
@Nullable private String audioMimeType;
@Nullable private String videoMimeType;
@@ -127,48 +124,12 @@ public final class TransformationRequest {
}
private Builder(TransformationRequest transformationRequest) {
- this.flattenForSlowMotion = transformationRequest.flattenForSlowMotion;
this.outputHeight = transformationRequest.outputHeight;
this.audioMimeType = transformationRequest.audioMimeType;
this.videoMimeType = transformationRequest.videoMimeType;
this.hdrMode = transformationRequest.hdrMode;
}
- /**
- * Sets whether the input should be flattened for media containing slow motion markers.
- *
- *
The transformed output is obtained by removing the slow motion metadata and by actually
- * slowing down the parts of the video and audio streams defined in this metadata. The default
- * value for {@code flattenForSlowMotion} is {@code false}.
- *
- *
Only Samsung Extension Format (SEF) slow motion metadata type is supported. The
- * transformation has no effect if the input does not contain this metadata type.
- *
- *
For SEF slow motion media, the following assumptions are made on the input:
- *
- *
- * - The input container format is (unfragmented) MP4.
- *
- The input contains an AVC video elementary stream with temporal SVC.
- *
- The recording frame rate of the video is 120 or 240 fps.
- *
- *
- * If using an {@link ExoPlayerAssetLoader.Factory} with a provided {@link
- * MediaSource.Factory}, make sure that {@link Mp4Extractor#FLAG_READ_SEF_DATA} is set on the
- * {@link Mp4Extractor} used. Otherwise, the slow motion metadata will be ignored and the input
- * won't be flattened.
- *
- *
Using slow motion flattening together with {@link
- * androidx.media3.common.MediaItem.ClippingConfiguration} is not supported yet.
- *
- * @param flattenForSlowMotion Whether to flatten for slow motion.
- * @return This builder.
- */
- @CanIgnoreReturnValue
- public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
- this.flattenForSlowMotion = flattenForSlowMotion;
- return this;
- }
-
/**
* Sets the output resolution using the output height of the displayed video.
*
@@ -290,17 +251,10 @@ public final class TransformationRequest {
/** Builds a {@link TransformationRequest} instance. */
public TransformationRequest build() {
- return new TransformationRequest(
- flattenForSlowMotion, outputHeight, audioMimeType, videoMimeType, hdrMode);
+ return new TransformationRequest(outputHeight, audioMimeType, videoMimeType, hdrMode);
}
}
- /**
- * Whether the input should be flattened for media containing slow motion markers.
- *
- * @see Builder#setFlattenForSlowMotion(boolean)
- */
- public final boolean flattenForSlowMotion;
/**
* The requested height of the output video, or {@link C#LENGTH_UNSET} if inferred from the input.
*
@@ -329,13 +283,10 @@ public final class TransformationRequest {
public final @HdrMode int hdrMode;
private TransformationRequest(
- boolean flattenForSlowMotion,
int outputHeight,
@Nullable String audioMimeType,
@Nullable String videoMimeType,
@HdrMode int hdrMode) {
-
- this.flattenForSlowMotion = flattenForSlowMotion;
this.outputHeight = outputHeight;
this.audioMimeType = audioMimeType;
this.videoMimeType = videoMimeType;
@@ -351,8 +302,7 @@ public final class TransformationRequest {
return false;
}
TransformationRequest that = (TransformationRequest) o;
- return flattenForSlowMotion == that.flattenForSlowMotion
- && outputHeight == that.outputHeight
+ return outputHeight == that.outputHeight
&& Util.areEqual(audioMimeType, that.audioMimeType)
&& Util.areEqual(videoMimeType, that.videoMimeType)
&& hdrMode == that.hdrMode;
@@ -360,8 +310,7 @@ public final class TransformationRequest {
@Override
public int hashCode() {
- int result = (flattenForSlowMotion ? 1 : 0);
- result = 31 * result + outputHeight;
+ int result = outputHeight;
result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0);
result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0);
result = 31 * result + hdrMode;
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
index 2363920fe5..8abfbc8687 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
@@ -42,9 +42,6 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.effect.GlEffectsFrameProcessor;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
-import androidx.media3.exoplayer.source.MediaSource;
-import androidx.media3.extractor.DefaultExtractorsFactory;
-import androidx.media3.extractor.mp4.Mp4Extractor;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
@@ -52,6 +49,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A transformer to transform media inputs.
@@ -86,9 +84,10 @@ public final class Transformer {
private ImmutableList videoEffects;
private boolean removeAudio;
private boolean removeVideo;
+ private boolean flattenForSlowMotion;
private boolean generateSilentAudio;
private ListenerSet listeners;
- @Nullable private AssetLoader.Factory assetLoaderFactory;
+ private AssetLoader.@MonotonicNonNull Factory assetLoaderFactory;
private FrameProcessor.Factory frameProcessorFactory;
private Codec.EncoderFactory encoderFactory;
private Muxer.Factory muxerFactory;
@@ -199,14 +198,14 @@ public final class Transformer {
}
/**
- * @deprecated Use {@link TransformationRequest.Builder#setFlattenForSlowMotion(boolean)}
- * instead.
+ * @deprecated Use {@link EditedMediaItem.Builder#setFlattenForSlowMotion(boolean)} to flatten
+ * the {@link EditedMediaItem} passed to {@link #startTransformation(EditedMediaItem,
+ * String)} or {@link #startTransformation(EditedMediaItem, ParcelFileDescriptor)} instead.
*/
@CanIgnoreReturnValue
@Deprecated
public Builder setFlattenForSlowMotion(boolean flattenForSlowMotion) {
- transformationRequest =
- transformationRequest.buildUpon().setFlattenForSlowMotion(flattenForSlowMotion).build();
+ this.flattenForSlowMotion = flattenForSlowMotion;
return this;
}
@@ -419,15 +418,8 @@ public final class Transformer {
checkSampleMimeType(transformationRequest.videoMimeType);
}
if (assetLoaderFactory == null) {
- DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory();
- if (transformationRequest.flattenForSlowMotion) {
- defaultExtractorsFactory.setMp4ExtractorFlags(Mp4Extractor.FLAG_READ_SEF_DATA);
- }
- MediaSource.Factory mediaSourceFactory =
- new DefaultMediaSourceFactory(context, defaultExtractorsFactory);
- Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
assetLoaderFactory =
- new DefaultAssetLoaderFactory(context, mediaSourceFactory, decoderFactory, clock);
+ new DefaultAssetLoaderFactory(context, new DefaultDecoderFactory(context), clock);
}
return new Transformer(
context,
@@ -436,6 +428,7 @@ public final class Transformer {
videoEffects,
removeAudio,
removeVideo,
+ flattenForSlowMotion,
generateSilentAudio,
listeners,
assetLoaderFactory,
@@ -556,6 +549,7 @@ public final class Transformer {
private final ImmutableList videoEffects;
private final boolean removeAudio;
private final boolean removeVideo;
+ private final boolean flattenForSlowMotion;
private final boolean generateSilentAudio;
private final ListenerSet listeners;
private final AssetLoader.Factory assetLoaderFactory;
@@ -575,6 +569,7 @@ public final class Transformer {
ImmutableList videoEffects,
boolean removeAudio,
boolean removeVideo,
+ boolean flattenForSlowMotion,
boolean generateSilentAudio,
ListenerSet listeners,
AssetLoader.Factory assetLoaderFactory,
@@ -591,6 +586,7 @@ public final class Transformer {
this.videoEffects = videoEffects;
this.removeAudio = removeAudio;
this.removeVideo = removeVideo;
+ this.flattenForSlowMotion = flattenForSlowMotion;
this.generateSilentAudio = generateSilentAudio;
this.listeners = listeners;
this.assetLoaderFactory = assetLoaderFactory;
@@ -712,10 +708,16 @@ public final class Transformer {
*/
@Deprecated
public void startTransformation(MediaItem mediaItem, String path) {
+ if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
+ && flattenForSlowMotion) {
+ throw new IllegalArgumentException(
+ "Clipping is not supported when slow motion flattening is requested");
+ }
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem)
.setRemoveAudio(removeAudio)
.setRemoveVideo(removeVideo)
+ .setFlattenForSlowMotion(flattenForSlowMotion)
.setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory))
.build();
startTransformationInternal(editedMediaItem, path, /* parcelFileDescriptor= */ null);
@@ -727,10 +729,16 @@ public final class Transformer {
@Deprecated
@RequiresApi(26)
public void startTransformation(MediaItem mediaItem, ParcelFileDescriptor parcelFileDescriptor) {
+ if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
+ && flattenForSlowMotion) {
+ throw new IllegalArgumentException(
+ "Clipping is not supported when slow motion flattening is requested");
+ }
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem)
.setRemoveAudio(removeAudio)
.setRemoveVideo(removeVideo)
+ .setFlattenForSlowMotion(flattenForSlowMotion)
.setEffects(new Effects(audioProcessors, videoEffects, frameProcessorFactory))
.build();
startTransformationInternal(editedMediaItem, /* path= */ null, parcelFileDescriptor);
@@ -740,22 +748,16 @@ public final class Transformer {
EditedMediaItem editedMediaItem,
@Nullable String path,
@Nullable ParcelFileDescriptor parcelFileDescriptor) {
- MediaItem mediaItem = editedMediaItem.mediaItem;
- if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
- && transformationRequest.flattenForSlowMotion) {
- // TODO(b/233986762): Support clipping with SEF flattening.
- throw new IllegalArgumentException(
- "Clipping is not supported when slow motion flattening is requested");
- }
verifyApplicationThread();
if (transformerInternal != null) {
throw new IllegalStateException("There is already a transformation in progress.");
}
TransformerInternalListener transformerInternalListener =
- new TransformerInternalListener(mediaItem);
+ new TransformerInternalListener(editedMediaItem.mediaItem);
HandlerWrapper applicationHandler = clock.createHandler(looper, /* callback= */ null);
FallbackListener fallbackListener =
- new FallbackListener(mediaItem, listeners, applicationHandler, transformationRequest);
+ new FallbackListener(
+ editedMediaItem.mediaItem, listeners, applicationHandler, transformationRequest);
transformerInternal =
new TransformerInternal(
context,
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
index 137e1901a5..d4b9bdf14f 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java
@@ -35,7 +35,6 @@ import androidx.media3.common.C;
import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect;
import androidx.media3.common.Format;
-import androidx.media3.common.MediaItem;
import androidx.media3.common.Metadata;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Clock;
@@ -99,7 +98,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final HandlerThread internalHandlerThread;
private final HandlerWrapper internalHandler;
private final AssetLoader assetLoader;
- private final Effects effects;
private final List samplePipelines;
private final MuxerWrapper muxerWrapper;
private final ConditionVariable transformerConditionVariable;
@@ -138,15 +136,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
internalHandlerThread = new HandlerThread("Transformer:Internal");
internalHandlerThread.start();
Looper internalLooper = internalHandlerThread.getLooper();
- MediaItem mediaItem = editedMediaItem.mediaItem;
- ComponentListener componentListener = new ComponentListener(mediaItem, fallbackListener);
+ ComponentListener componentListener = new ComponentListener(editedMediaItem, fallbackListener);
assetLoader =
assetLoaderFactory
.setRemoveAudio(editedMediaItem.removeAudio)
.setRemoveVideo(editedMediaItem.removeVideo)
- .setFlattenVideoForSlowMotion(transformationRequest.flattenForSlowMotion)
- .createAssetLoader(mediaItem, internalLooper, componentListener);
- effects = editedMediaItem.effects;
+ .setFlattenVideoForSlowMotion(editedMediaItem.flattenForSlowMotion)
+ .createAssetLoader(editedMediaItem.mediaItem, internalLooper, componentListener);
samplePipelines = new ArrayList<>();
muxerWrapper =
new MuxerWrapper(outputPath, outputParcelFileDescriptor, muxerFactory, componentListener);
@@ -324,7 +320,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private class ComponentListener implements AssetLoader.Listener, MuxerWrapper.Listener {
- private final MediaItem mediaItem;
+ private final EditedMediaItem editedMediaItem;
private final FallbackListener fallbackListener;
private final AtomicInteger trackCount;
@@ -332,8 +328,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private volatile long durationUs;
- public ComponentListener(MediaItem mediaItem, FallbackListener fallbackListener) {
- this.mediaItem = mediaItem;
+ public ComponentListener(EditedMediaItem editedMediaItem, FallbackListener fallbackListener) {
+ this.editedMediaItem = editedMediaItem;
this.fallbackListener = fallbackListener;
trackCount = new AtomicInteger();
durationUs = C.TIME_UNSET;
@@ -479,7 +475,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
streamStartPositionUs,
streamOffsetUs,
transformationRequest,
- effects.audioProcessors,
+ editedMediaItem.flattenForSlowMotion,
+ editedMediaItem.effects.audioProcessors,
generateSilentAudio ? durationUs : C.TIME_UNSET,
encoderFactory,
muxerWrapper,
@@ -491,8 +488,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
streamStartPositionUs,
streamOffsetUs,
transformationRequest,
- effects.videoEffects,
- effects.frameProcessorFactory,
+ editedMediaItem.effects.videoEffects,
+ editedMediaItem.effects.frameProcessorFactory,
encoderFactory,
muxerWrapper,
/* errorConsumer= */ this::onTransformationError,
@@ -520,10 +517,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
&& !muxerWrapper.supportsSampleMimeType(inputFormat.sampleMimeType)) {
return true;
}
- if (transformationRequest.flattenForSlowMotion && isSlowMotion(inputFormat)) {
+ if (editedMediaItem.flattenForSlowMotion && isSlowMotion(inputFormat)) {
return true;
}
- if (!effects.audioProcessors.isEmpty()) {
+ if (!editedMediaItem.effects.audioProcessors.isEmpty()) {
return true;
}
if (generateSilentAudio) {
@@ -549,7 +546,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private boolean shouldTranscodeVideo(
Format inputFormat, long streamStartPositionUs, long streamOffsetUs) {
if ((streamStartPositionUs - streamOffsetUs) != 0
- && !mediaItem.clippingConfiguration.startsAtKeyFrame) {
+ && !editedMediaItem.mediaItem.clippingConfiguration.startsAtKeyFrame) {
return true;
}
if (encoderFactory.videoNeedsEncoding()) {
@@ -571,8 +568,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
// TODO(b/265927935): consider generalizing this logic.
- for (int i = 0; i < effects.videoEffects.size(); i++) {
- Effect videoEffect = effects.videoEffects.get(i);
+ for (int i = 0; i < editedMediaItem.effects.videoEffects.size(); i++) {
+ Effect videoEffect = editedMediaItem.effects.videoEffects.get(i);
if (videoEffect instanceof Presentation) {
Presentation presentation = (Presentation) videoEffect;
// The decoder rotates encoded frames for display by inputFormat.rotationDegrees.
diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java
index 381d15415d..160db0222b 100644
--- a/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java
+++ b/libraries/transformer/src/test/java/androidx/media3/transformer/EditedMediaItemBuilderTest.java
@@ -38,4 +38,19 @@ public final class EditedMediaItemBuilderTest {
.setRemoveVideo(true)
.build());
}
+
+ @Test
+ public void setFlattenForSlowMotion_forClippedMediaItem_throws() {
+ MediaItem.ClippingConfiguration clippingConfiguration =
+ new MediaItem.ClippingConfiguration.Builder().setStartPositionMs(1000).build();
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setUri("Uri")
+ .setClippingConfiguration(clippingConfiguration)
+ .build();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new EditedMediaItem.Builder(mediaItem).setFlattenForSlowMotion(true).build());
+ }
}
diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java
index fd2077ba04..78ea52c03d 100644
--- a/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java
+++ b/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java
@@ -27,8 +27,6 @@ import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Clock;
import androidx.media3.decoder.DecoderInputBuffer;
-import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
-import androidx.media3.exoplayer.source.MediaSource;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.time.Duration;
@@ -118,10 +116,9 @@ public class ExoPlayerAssetLoaderTest {
private static AssetLoader getAssetLoader(
Looper looper, AssetLoader.Listener listener, Clock clock) {
Context context = ApplicationProvider.getApplicationContext();
- MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(context);
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
MediaItem mediaItem = MediaItem.fromUri("asset:///media/mp4/sample.mp4");
- return new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock)
+ return new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock)
.setRemoveAudio(false)
.setRemoveVideo(false)
.setFlattenVideoForSlowMotion(false)
diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformationRequestTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformationRequestTest.java
index 36499606d8..0501e187a3 100644
--- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformationRequestTest.java
+++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformationRequestTest.java
@@ -16,6 +16,7 @@
package androidx.media3.transformer;
+import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.MimeTypes;
@@ -35,9 +36,10 @@ public class TransformationRequestTest {
private static TransformationRequest createTestTransformationRequest() {
return new TransformationRequest.Builder()
- .setFlattenForSlowMotion(true)
+ .setResolution(720)
.setAudioMimeType(MimeTypes.AUDIO_AAC)
.setVideoMimeType(MimeTypes.VIDEO_H264)
+ .setHdrMode(HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL)
.build();
}
}
diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java
index 01dfbc408b..ecaf2718ce 100644
--- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java
+++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java
@@ -502,13 +502,10 @@ public final class TransformerEndToEndTest {
@Test
public void startTransformation_flattenForSlowMotion_completesSuccessfully() throws Exception {
- Transformer transformer =
- createTransformerBuilder(/* enableFallback= */ false)
- .setTransformationRequest(
- new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
- .build();
+ Transformer transformer = createTransformerBuilder(/* enableFallback= */ false).build();
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(MediaItem.fromUri(ASSET_URI_PREFIX + FILE_WITH_SEF_SLOW_MOTION))
+ .setFlattenForSlowMotion(true)
.build();
transformer.startTransformation(editedMediaItem, outputPath);
@@ -645,7 +642,7 @@ public final class TransformerEndToEndTest {
context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10));
Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context);
AssetLoader.Factory assetLoaderFactory =
- new ExoPlayerAssetLoader.Factory(context, mediaSourceFactory, decoderFactory, clock);
+ new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory);
Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1);
Transformer transformer =
createTransformerBuilder(/* enableFallback= */ false)