mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add AssetLoader interface
This is a step towards allowing apps to inject a custom AssetLoader PiperOrigin-RevId: 494185078
This commit is contained in:
parent
be080f22ae
commit
c8d8ff5578
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user