From c8d8ff55780be332fb7bf657b68694b628648b81 Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 9 Dec 2022 16:55:51 +0000 Subject: [PATCH] Add AssetLoader interface This is a step towards allowing apps to inject a custom AssetLoader PiperOrigin-RevId: 494185078 --- .../media3/transformer/AssetLoader.java | 110 ++++++++++++++++++ .../transformer/ExoPlayerAssetLoader.java | 37 ++---- .../ExoPlayerAssetLoaderRenderer.java | 4 +- .../media3/transformer/SamplePipeline.java | 4 +- .../transformer/TransformerInternal.java | 14 +-- 5 files changed, 134 insertions(+), 35 deletions(-) create mode 100644 libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java new file mode 100644 index 0000000000..b6d9c1a691 --- /dev/null +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AssetLoader.java @@ -0,0 +1,110 @@ +/* + * Copyright 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 androidx.media3.transformer; + +import androidx.media3.common.Format; +import androidx.media3.common.util.UnstableApi; + +/** + * Provides media data to a {@linkplain Transformer}. + * + *

The output samples can be encoded or decoded. + * + *

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. + */ +@UnstableApi +public interface AssetLoader { + + /** + * A listener of asset loader events. + * + *

This listener is typically used in the following way: + * + *

+ * + *

This listener can be called from any thread. + */ + interface Listener { + + /** Called when the duration of the input media is known. */ + void onDurationUs(long durationUs); + + /** + * Called to register a single output track of sample data. + * + *

Must be called for each track that will be output. + * + *

Must be called on the same thread as {@link #onTrackAdded(Format, long, long)}. + */ + void onTrackRegistered(); + + /** Called when all the tracks have been {@linkplain #onTrackRegistered() registered}. */ + void onAllTracksRegistered(); + + /** + * Called when the information on a {@linkplain #onTrackRegistered() registered} track is known. + * + *

Must be called after the duration has been {@linkplain #onDurationUs(long) reported} and + * all the tracks have been {@linkplain #onAllTracksRegistered registered}. + * + *

Must be called on the same thread as {@link #onTrackRegistered()}. + * + * @param format The {@link Format} of the input media (prior to video slow motion flattening or + * to decoding). + * @param streamStartPositionUs The start position of the stream (offset by {@code + * streamOffsetUs}), in microseconds. + * @param streamOffsetUs The offset that will be added to the timestamps to make sure they are + * non-negative, in microseconds. + * @return The {@link SamplePipeline.Input} describing the type of sample data expected, and to + * which to pass this data. + * @throws TransformationException If an error occurs configuring the {@link + * SamplePipeline.Input}. + */ + SamplePipeline.Input onTrackAdded( + Format format, long streamStartPositionUs, long streamOffsetUs) + throws TransformationException; + + /** + * Called if an error occurs in the asset loader. In this case, the asset loader will be + * {@linkplain #release() released} automatically. + */ + void onError(Exception e); + } + + /** Starts the asset loader. */ + void start(); + + /** + * Returns the current {@link Transformer.ProgressState} and updates {@code progressHolder} with + * the current progress if it is {@link Transformer#PROGRESS_STATE_AVAILABLE available}. + * + * @param progressHolder A {@link ProgressHolder}, updated to hold the percentage progress if + * {@link Transformer#PROGRESS_STATE_AVAILABLE available}. + * @return The {@link Transformer.ProgressState}. + */ + @Transformer.ProgressState + int getProgress(ProgressHolder progressHolder); + + /** Stops loading data and releases all resources associated with the asset loader. */ + void release(); +} 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 a4ba735566..3b8623e81c 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java @@ -30,7 +30,6 @@ import android.content.Context; import android.os.Handler; import android.os.Looper; import androidx.media3.common.C; -import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.PlaybackException; import androidx.media3.common.Player; @@ -48,22 +47,7 @@ import androidx.media3.exoplayer.text.TextOutput; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.video.VideoRendererEventListener; -/* package */ final class ExoPlayerAssetLoader { - - public interface Listener { - - void onDurationUs(long durationUs); - - void onTrackRegistered(); - - void onAllTracksRegistered(); - - SamplePipeline.Input onTrackAdded( - Format format, long streamStartPositionUs, long streamOffsetUs) - throws TransformationException; - - void onError(Exception e); - } +/* package */ final class ExoPlayerAssetLoader implements AssetLoader { private final MediaItem mediaItem; private final ExoPlayer player; @@ -119,6 +103,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener; progressState = PROGRESS_STATE_NO_TRANSFORMATION; } + @Override public void start() { player.setMediaItem(mediaItem); player.prepare(); @@ -126,6 +111,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener; progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; } + @Override public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) { if (progressState == PROGRESS_STATE_AVAILABLE) { long durationMs = player.getDuration(); @@ -135,6 +121,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener; return progressState; } + @Override public void release() { player.release(); progressState = PROGRESS_STATE_NO_TRANSFORMATION; @@ -147,14 +134,14 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener; private final boolean removeVideo; private final boolean flattenForSlowMotion; private final Codec.DecoderFactory decoderFactory; - private final ExoPlayerAssetLoader.Listener assetLoaderListener; + private final Listener assetLoaderListener; public RenderersFactoryImpl( boolean removeAudio, boolean removeVideo, boolean flattenForSlowMotion, Codec.DecoderFactory decoderFactory, - ExoPlayerAssetLoader.Listener assetLoaderListener) { + Listener assetLoaderListener) { this.removeAudio = removeAudio; this.removeVideo = removeVideo; this.flattenForSlowMotion = flattenForSlowMotion; @@ -199,10 +186,10 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener; private final class PlayerListener implements Player.Listener { - private final Listener listener; + private final Listener assetLoaderListener; - public PlayerListener(Listener listener) { - this.listener = listener; + public PlayerListener(Listener assetLoaderListener) { + this.assetLoaderListener = assetLoaderListener; } @Override @@ -221,18 +208,18 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener; durationUs <= 0 || durationUs == C.TIME_UNSET ? PROGRESS_STATE_UNAVAILABLE : PROGRESS_STATE_AVAILABLE; - listener.onDurationUs(window.durationUs); + assetLoaderListener.onDurationUs(window.durationUs); } } @Override public void onTracksChanged(Tracks tracks) { - listener.onAllTracksRegistered(); + assetLoaderListener.onAllTracksRegistered(); } @Override public void onPlayerError(PlaybackException error) { - listener.onError(error); + assetLoaderListener.onError(error); } } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoaderRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoaderRenderer.java index 9727fe765b..3337553174 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoaderRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoaderRenderer.java @@ -43,7 +43,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private final boolean flattenForSlowMotion; private final Codec.DecoderFactory decoderFactory; private final TransformerMediaClock mediaClock; - private final ExoPlayerAssetLoader.Listener assetLoaderListener; + private final AssetLoader.Listener assetLoaderListener; private final DecoderInputBuffer decoderInputBuffer; private boolean isTransformationRunning; @@ -60,7 +60,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; boolean flattenForSlowMotion, Codec.DecoderFactory decoderFactory, TransformerMediaClock mediaClock, - ExoPlayerAssetLoader.Listener assetLoaderListener) { + AssetLoader.Listener assetLoaderListener) { super(trackType); this.flattenForSlowMotion = flattenForSlowMotion; this.decoderFactory = decoderFactory; diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java index f4058784ec..e33c79ad52 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/SamplePipeline.java @@ -17,6 +17,7 @@ package androidx.media3.transformer; import androidx.annotation.Nullable; +import androidx.media3.common.util.UnstableApi; import androidx.media3.decoder.DecoderInputBuffer; /** @@ -24,7 +25,8 @@ import androidx.media3.decoder.DecoderInputBuffer; * *

This pipeline can be used to implement transformations of audio or video samples. */ -/* package */ interface SamplePipeline { +@UnstableApi +public interface SamplePipeline { /** Input of a {@link SamplePipeline}. */ interface Input { 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 4f4078334b..a909d95b24 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java @@ -102,7 +102,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final Clock clock; private final HandlerThread internalHandlerThread; private final HandlerWrapper internalHandler; - private final ExoPlayerAssetLoader exoPlayerAssetLoader; + private final AssetLoader assetLoader; private final List samplePipelines; private final ConditionVariable dequeueBufferConditionVariable; private final MuxerWrapper muxerWrapper; @@ -151,7 +151,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; internalHandlerThread.start(); Looper internalLooper = internalHandlerThread.getLooper(); ComponentListener componentListener = new ComponentListener(mediaItem, fallbackListener); - exoPlayerAssetLoader = + assetLoader = new ExoPlayerAssetLoader( context, mediaItem, @@ -256,7 +256,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } private void startInternal() { - exoPlayerAssetLoader.start(); + assetLoader.start(); } private void registerSamplePipelineInternal(SamplePipeline samplePipeline) { @@ -310,7 +310,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; dequeueBufferConditionVariable.open(); try { try { - exoPlayerAssetLoader.release(); + assetLoader.release(); } finally { try { for (int i = 0; i < samplePipelines.size(); i++) { @@ -368,11 +368,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; } private void updateProgressInternal(ProgressHolder progressHolder) { - progressState = exoPlayerAssetLoader.getProgress(progressHolder); + progressState = assetLoader.getProgress(progressHolder); transformerConditionVariable.open(); } - private class ComponentListener implements ExoPlayerAssetLoader.Listener { + private class ComponentListener implements AssetLoader.Listener { private final MediaItem mediaItem; private final FallbackListener fallbackListener; @@ -388,7 +388,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; durationUs = C.TIME_UNSET; } - // ExoPlayerAssetLoader.Listener implementation. + // AssetLoader.Listener implementation. @Override public void onDurationUs(long durationUs) {