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.Handler;
import android.os.Looper; import android.os.Looper;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem; 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;
@ -48,22 +47,7 @@ import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import androidx.media3.exoplayer.video.VideoRendererEventListener; import androidx.media3.exoplayer.video.VideoRendererEventListener;
/* package */ final class ExoPlayerAssetLoader { /* package */ final class ExoPlayerAssetLoader implements AssetLoader {
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);
}
private final MediaItem mediaItem; private final MediaItem mediaItem;
private final ExoPlayer player; private final ExoPlayer player;
@ -119,6 +103,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
progressState = PROGRESS_STATE_NO_TRANSFORMATION; progressState = PROGRESS_STATE_NO_TRANSFORMATION;
} }
@Override
public void start() { public void start() {
player.setMediaItem(mediaItem); player.setMediaItem(mediaItem);
player.prepare(); player.prepare();
@ -126,6 +111,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY; progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
} }
@Override
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) { public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
if (progressState == PROGRESS_STATE_AVAILABLE) { if (progressState == PROGRESS_STATE_AVAILABLE) {
long durationMs = player.getDuration(); long durationMs = player.getDuration();
@ -135,6 +121,7 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
return progressState; return progressState;
} }
@Override
public void release() { public void release() {
player.release(); player.release();
progressState = PROGRESS_STATE_NO_TRANSFORMATION; progressState = PROGRESS_STATE_NO_TRANSFORMATION;
@ -147,14 +134,14 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
private final boolean removeVideo; private final boolean removeVideo;
private final boolean flattenForSlowMotion; private final boolean flattenForSlowMotion;
private final Codec.DecoderFactory decoderFactory; private final Codec.DecoderFactory decoderFactory;
private final ExoPlayerAssetLoader.Listener assetLoaderListener; private final Listener assetLoaderListener;
public RenderersFactoryImpl( public RenderersFactoryImpl(
boolean removeAudio, boolean removeAudio,
boolean removeVideo, boolean removeVideo,
boolean flattenForSlowMotion, boolean flattenForSlowMotion,
Codec.DecoderFactory decoderFactory, Codec.DecoderFactory decoderFactory,
ExoPlayerAssetLoader.Listener assetLoaderListener) { Listener assetLoaderListener) {
this.removeAudio = removeAudio; this.removeAudio = removeAudio;
this.removeVideo = removeVideo; this.removeVideo = removeVideo;
this.flattenForSlowMotion = flattenForSlowMotion; this.flattenForSlowMotion = flattenForSlowMotion;
@ -199,10 +186,10 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
private final class PlayerListener implements Player.Listener { private final class PlayerListener implements Player.Listener {
private final Listener listener; private final Listener assetLoaderListener;
public PlayerListener(Listener listener) { public PlayerListener(Listener assetLoaderListener) {
this.listener = listener; this.assetLoaderListener = assetLoaderListener;
} }
@Override @Override
@ -221,18 +208,18 @@ import androidx.media3.exoplayer.video.VideoRendererEventListener;
durationUs <= 0 || durationUs == C.TIME_UNSET durationUs <= 0 || durationUs == C.TIME_UNSET
? PROGRESS_STATE_UNAVAILABLE ? PROGRESS_STATE_UNAVAILABLE
: PROGRESS_STATE_AVAILABLE; : PROGRESS_STATE_AVAILABLE;
listener.onDurationUs(window.durationUs); assetLoaderListener.onDurationUs(window.durationUs);
} }
} }
@Override @Override
public void onTracksChanged(Tracks tracks) { public void onTracksChanged(Tracks tracks) {
listener.onAllTracksRegistered(); assetLoaderListener.onAllTracksRegistered();
} }
@Override @Override
public void onPlayerError(PlaybackException error) { 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 boolean flattenForSlowMotion;
private final Codec.DecoderFactory decoderFactory; private final Codec.DecoderFactory decoderFactory;
private final TransformerMediaClock mediaClock; private final TransformerMediaClock mediaClock;
private final ExoPlayerAssetLoader.Listener assetLoaderListener; private final AssetLoader.Listener assetLoaderListener;
private final DecoderInputBuffer decoderInputBuffer; private final DecoderInputBuffer decoderInputBuffer;
private boolean isTransformationRunning; private boolean isTransformationRunning;
@ -60,7 +60,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
boolean flattenForSlowMotion, boolean flattenForSlowMotion,
Codec.DecoderFactory decoderFactory, Codec.DecoderFactory decoderFactory,
TransformerMediaClock mediaClock, TransformerMediaClock mediaClock,
ExoPlayerAssetLoader.Listener assetLoaderListener) { AssetLoader.Listener assetLoaderListener) {
super(trackType); super(trackType);
this.flattenForSlowMotion = flattenForSlowMotion; this.flattenForSlowMotion = flattenForSlowMotion;
this.decoderFactory = decoderFactory; this.decoderFactory = decoderFactory;

View File

@ -17,6 +17,7 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.decoder.DecoderInputBuffer; 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. * <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}. */ /** Input of a {@link SamplePipeline}. */
interface Input { interface Input {

View File

@ -102,7 +102,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Clock clock; private final Clock clock;
private final HandlerThread internalHandlerThread; private final HandlerThread internalHandlerThread;
private final HandlerWrapper internalHandler; private final HandlerWrapper internalHandler;
private final ExoPlayerAssetLoader exoPlayerAssetLoader; private final AssetLoader assetLoader;
private final List<SamplePipeline> samplePipelines; private final List<SamplePipeline> samplePipelines;
private final ConditionVariable dequeueBufferConditionVariable; private final ConditionVariable dequeueBufferConditionVariable;
private final MuxerWrapper muxerWrapper; private final MuxerWrapper muxerWrapper;
@ -151,7 +151,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
internalHandlerThread.start(); internalHandlerThread.start();
Looper internalLooper = internalHandlerThread.getLooper(); Looper internalLooper = internalHandlerThread.getLooper();
ComponentListener componentListener = new ComponentListener(mediaItem, fallbackListener); ComponentListener componentListener = new ComponentListener(mediaItem, fallbackListener);
exoPlayerAssetLoader = assetLoader =
new ExoPlayerAssetLoader( new ExoPlayerAssetLoader(
context, context,
mediaItem, mediaItem,
@ -256,7 +256,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void startInternal() { private void startInternal() {
exoPlayerAssetLoader.start(); assetLoader.start();
} }
private void registerSamplePipelineInternal(SamplePipeline samplePipeline) { private void registerSamplePipelineInternal(SamplePipeline samplePipeline) {
@ -310,7 +310,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
dequeueBufferConditionVariable.open(); dequeueBufferConditionVariable.open();
try { try {
try { try {
exoPlayerAssetLoader.release(); assetLoader.release();
} finally { } finally {
try { try {
for (int i = 0; i < samplePipelines.size(); i++) { for (int i = 0; i < samplePipelines.size(); i++) {
@ -368,11 +368,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
private void updateProgressInternal(ProgressHolder progressHolder) { private void updateProgressInternal(ProgressHolder progressHolder) {
progressState = exoPlayerAssetLoader.getProgress(progressHolder); progressState = assetLoader.getProgress(progressHolder);
transformerConditionVariable.open(); transformerConditionVariable.open();
} }
private class ComponentListener implements ExoPlayerAssetLoader.Listener { private class ComponentListener implements AssetLoader.Listener {
private final MediaItem mediaItem; private final MediaItem mediaItem;
private final FallbackListener fallbackListener; private final FallbackListener fallbackListener;
@ -388,7 +388,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
durationUs = C.TIME_UNSET; durationUs = C.TIME_UNSET;
} }
// ExoPlayerAssetLoader.Listener implementation. // AssetLoader.Listener implementation.
@Override @Override
public void onDurationUs(long durationUs) { public void onDurationUs(long durationUs) {