Add AssetLoader interface

This is a step towards allowing apps to inject a custom AssetLoader

PiperOrigin-RevId: 494185078
This commit is contained in:
kimvde 2022-12-09 16:55:51 +00:00 committed by Ian Baker
parent be080f22ae
commit c8d8ff5578
5 changed files with 134 additions and 35 deletions

View File

@ -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}.
*
* <p>The output samples can be encoded or decoded.
*
* <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.
*/
@UnstableApi
public interface AssetLoader {
/**
* A listener of asset loader events.
*
* <p>This listener is typically used in the following way:
*
* <ul>
* <li>{@linkplain #onDurationUs(long)} Report} the duration of the input media.
* <li>{@linkplain #onTrackRegistered() Register} each output track.
* <li>{@linkplain #onAllTracksRegistered() Signal} that all the tracks have been registered.
* <li>{@linkplain #onTrackAdded(Format, long, long) Add} the information for each track.
* </ul>
*
* <p>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.
*
* <p>Must be called for each track that will be output.
*
* <p>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.
*
* <p>Must be called after the duration has been {@linkplain #onDurationUs(long) reported} and
* all the tracks have been {@linkplain #onAllTracksRegistered registered}.
*
* <p>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();
}

View File

@ -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);
}
}
}

View File

@ -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;

View File

@ -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;
*
* <p>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 {

View File

@ -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<SamplePipeline> 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) {