Skip to content

Commit c89ceb8

Browse files
kim-vdemicrokatz
authored andcommitted
Add ExoPlayerAssetLoader
Just move some code around for now, to start setting up the overall structure. PiperOrigin-RevId: 487229329 (cherry picked from commit 95f37b4)
1 parent d262f76 commit c89ceb8

File tree

2 files changed

+381
-227
lines changed

2 files changed

+381
-227
lines changed
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.media3.transformer;
18+
19+
import static androidx.media3.common.util.Assertions.checkNotNull;
20+
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
21+
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
22+
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
23+
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
24+
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_AVAILABLE;
25+
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION;
26+
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_UNAVAILABLE;
27+
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
28+
import static java.lang.Math.min;
29+
30+
import android.content.Context;
31+
import android.os.Handler;
32+
import android.os.Looper;
33+
import androidx.annotation.Nullable;
34+
import androidx.media3.common.C;
35+
import androidx.media3.common.DebugViewProvider;
36+
import androidx.media3.common.Effect;
37+
import androidx.media3.common.FrameProcessor;
38+
import androidx.media3.common.MediaItem;
39+
import androidx.media3.common.PlaybackException;
40+
import androidx.media3.common.Player;
41+
import androidx.media3.common.Timeline;
42+
import androidx.media3.common.Tracks;
43+
import androidx.media3.common.util.Clock;
44+
import androidx.media3.exoplayer.DefaultLoadControl;
45+
import androidx.media3.exoplayer.ExoPlayer;
46+
import androidx.media3.exoplayer.Renderer;
47+
import androidx.media3.exoplayer.RenderersFactory;
48+
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
49+
import androidx.media3.exoplayer.metadata.MetadataOutput;
50+
import androidx.media3.exoplayer.source.MediaSource;
51+
import androidx.media3.exoplayer.text.TextOutput;
52+
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
53+
import androidx.media3.exoplayer.video.VideoRendererEventListener;
54+
import com.google.common.collect.ImmutableList;
55+
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
56+
57+
/* package */ final class ExoPlayerAssetLoader {
58+
59+
public interface Listener {
60+
61+
void onEnded();
62+
63+
void onError(Exception e);
64+
}
65+
66+
private final Context context;
67+
private final TransformationRequest transformationRequest;
68+
private final ImmutableList<Effect> videoEffects;
69+
private final boolean removeAudio;
70+
private final boolean removeVideo;
71+
private final MediaSource.Factory mediaSourceFactory;
72+
private final Codec.DecoderFactory decoderFactory;
73+
private final Codec.EncoderFactory encoderFactory;
74+
private final FrameProcessor.Factory frameProcessorFactory;
75+
private final Looper looper;
76+
private final DebugViewProvider debugViewProvider;
77+
private final Clock clock;
78+
79+
private @MonotonicNonNull MuxerWrapper muxerWrapper;
80+
@Nullable private ExoPlayer player;
81+
private @Transformer.ProgressState int progressState;
82+
83+
public ExoPlayerAssetLoader(
84+
Context context,
85+
TransformationRequest transformationRequest,
86+
ImmutableList<Effect> videoEffects,
87+
boolean removeAudio,
88+
boolean removeVideo,
89+
MediaSource.Factory mediaSourceFactory,
90+
Codec.DecoderFactory decoderFactory,
91+
Codec.EncoderFactory encoderFactory,
92+
FrameProcessor.Factory frameProcessorFactory,
93+
Looper looper,
94+
DebugViewProvider debugViewProvider,
95+
Clock clock) {
96+
this.context = context;
97+
this.transformationRequest = transformationRequest;
98+
this.videoEffects = videoEffects;
99+
this.removeAudio = removeAudio;
100+
this.removeVideo = removeVideo;
101+
this.mediaSourceFactory = mediaSourceFactory;
102+
this.decoderFactory = decoderFactory;
103+
this.encoderFactory = encoderFactory;
104+
this.frameProcessorFactory = frameProcessorFactory;
105+
this.looper = looper;
106+
this.debugViewProvider = debugViewProvider;
107+
this.clock = clock;
108+
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
109+
}
110+
111+
public void start(
112+
MediaItem mediaItem,
113+
MuxerWrapper muxerWrapper,
114+
Listener listener,
115+
FallbackListener fallbackListener,
116+
Transformer.AsyncErrorListener asyncErrorListener) {
117+
this.muxerWrapper = muxerWrapper;
118+
119+
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
120+
trackSelector.setParameters(
121+
new DefaultTrackSelector.Parameters.Builder(context)
122+
.setForceHighestSupportedBitrate(true)
123+
.build());
124+
// Arbitrarily decrease buffers for playback so that samples start being sent earlier to the
125+
// muxer (rebuffers are less problematic for the transformation use case).
126+
DefaultLoadControl loadControl =
127+
new DefaultLoadControl.Builder()
128+
.setBufferDurationsMs(
129+
DEFAULT_MIN_BUFFER_MS,
130+
DEFAULT_MAX_BUFFER_MS,
131+
DEFAULT_BUFFER_FOR_PLAYBACK_MS / 10,
132+
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS / 10)
133+
.build();
134+
ExoPlayer.Builder playerBuilder =
135+
new ExoPlayer.Builder(
136+
context,
137+
new RenderersFactoryImpl(
138+
context,
139+
muxerWrapper,
140+
removeAudio,
141+
removeVideo,
142+
transformationRequest,
143+
mediaItem.clippingConfiguration.startsAtKeyFrame,
144+
videoEffects,
145+
frameProcessorFactory,
146+
encoderFactory,
147+
decoderFactory,
148+
fallbackListener,
149+
asyncErrorListener,
150+
debugViewProvider))
151+
.setMediaSourceFactory(mediaSourceFactory)
152+
.setTrackSelector(trackSelector)
153+
.setLoadControl(loadControl)
154+
.setLooper(looper);
155+
if (clock != Clock.DEFAULT) {
156+
// Transformer.Builder#setClock is also @VisibleForTesting, so if we're using a non-default
157+
// clock we must be in a test context.
158+
@SuppressWarnings("VisibleForTests")
159+
ExoPlayer.Builder unusedForAnnotation = playerBuilder.setClock(clock);
160+
}
161+
162+
player = playerBuilder.build();
163+
player.setMediaItem(mediaItem);
164+
player.addListener(new PlayerListener(listener));
165+
player.prepare();
166+
167+
progressState = PROGRESS_STATE_WAITING_FOR_AVAILABILITY;
168+
}
169+
170+
public @Transformer.ProgressState int getProgress(ProgressHolder progressHolder) {
171+
if (progressState == PROGRESS_STATE_AVAILABLE) {
172+
Player player = checkNotNull(this.player);
173+
long durationMs = player.getDuration();
174+
long positionMs = player.getCurrentPosition();
175+
progressHolder.progress = min((int) (positionMs * 100 / durationMs), 99);
176+
}
177+
return progressState;
178+
}
179+
180+
public void release() {
181+
progressState = PROGRESS_STATE_NO_TRANSFORMATION;
182+
if (player != null) {
183+
player.release();
184+
player = null;
185+
}
186+
}
187+
188+
private static final class RenderersFactoryImpl implements RenderersFactory {
189+
190+
private final Context context;
191+
private final MuxerWrapper muxerWrapper;
192+
private final TransformerMediaClock mediaClock;
193+
private final boolean removeAudio;
194+
private final boolean removeVideo;
195+
private final TransformationRequest transformationRequest;
196+
private final boolean clippingStartsAtKeyFrame;
197+
private final ImmutableList<Effect> videoEffects;
198+
private final FrameProcessor.Factory frameProcessorFactory;
199+
private final Codec.EncoderFactory encoderFactory;
200+
private final Codec.DecoderFactory decoderFactory;
201+
private final FallbackListener fallbackListener;
202+
private final Transformer.AsyncErrorListener asyncErrorListener;
203+
private final DebugViewProvider debugViewProvider;
204+
205+
public RenderersFactoryImpl(
206+
Context context,
207+
MuxerWrapper muxerWrapper,
208+
boolean removeAudio,
209+
boolean removeVideo,
210+
TransformationRequest transformationRequest,
211+
boolean clippingStartsAtKeyFrame,
212+
ImmutableList<Effect> videoEffects,
213+
FrameProcessor.Factory frameProcessorFactory,
214+
Codec.EncoderFactory encoderFactory,
215+
Codec.DecoderFactory decoderFactory,
216+
FallbackListener fallbackListener,
217+
Transformer.AsyncErrorListener asyncErrorListener,
218+
DebugViewProvider debugViewProvider) {
219+
this.context = context;
220+
this.muxerWrapper = muxerWrapper;
221+
this.removeAudio = removeAudio;
222+
this.removeVideo = removeVideo;
223+
this.transformationRequest = transformationRequest;
224+
this.clippingStartsAtKeyFrame = clippingStartsAtKeyFrame;
225+
this.videoEffects = videoEffects;
226+
this.frameProcessorFactory = frameProcessorFactory;
227+
this.encoderFactory = encoderFactory;
228+
this.decoderFactory = decoderFactory;
229+
this.fallbackListener = fallbackListener;
230+
this.asyncErrorListener = asyncErrorListener;
231+
this.debugViewProvider = debugViewProvider;
232+
mediaClock = new TransformerMediaClock();
233+
}
234+
235+
@Override
236+
public Renderer[] createRenderers(
237+
Handler eventHandler,
238+
VideoRendererEventListener videoRendererEventListener,
239+
AudioRendererEventListener audioRendererEventListener,
240+
TextOutput textRendererOutput,
241+
MetadataOutput metadataRendererOutput) {
242+
int rendererCount = removeAudio || removeVideo ? 1 : 2;
243+
Renderer[] renderers = new Renderer[rendererCount];
244+
int index = 0;
245+
if (!removeAudio) {
246+
renderers[index] =
247+
new TransformerAudioRenderer(
248+
muxerWrapper,
249+
mediaClock,
250+
transformationRequest,
251+
encoderFactory,
252+
decoderFactory,
253+
asyncErrorListener,
254+
fallbackListener);
255+
index++;
256+
}
257+
if (!removeVideo) {
258+
renderers[index] =
259+
new TransformerVideoRenderer(
260+
context,
261+
muxerWrapper,
262+
mediaClock,
263+
transformationRequest,
264+
clippingStartsAtKeyFrame,
265+
videoEffects,
266+
frameProcessorFactory,
267+
encoderFactory,
268+
decoderFactory,
269+
asyncErrorListener,
270+
fallbackListener,
271+
debugViewProvider);
272+
index++;
273+
}
274+
return renderers;
275+
}
276+
}
277+
278+
private final class PlayerListener implements Player.Listener {
279+
280+
private final Listener listener;
281+
282+
public PlayerListener(Listener listener) {
283+
this.listener = listener;
284+
}
285+
286+
@Override
287+
public void onPlaybackStateChanged(int state) {
288+
if (state == Player.STATE_ENDED) {
289+
listener.onEnded();
290+
}
291+
}
292+
293+
@Override
294+
public void onTimelineChanged(Timeline timeline, int reason) {
295+
if (progressState != PROGRESS_STATE_WAITING_FOR_AVAILABILITY) {
296+
return;
297+
}
298+
Timeline.Window window = new Timeline.Window();
299+
timeline.getWindow(/* windowIndex= */ 0, window);
300+
if (!window.isPlaceholder) {
301+
long durationUs = window.durationUs;
302+
// Make progress permanently unavailable if the duration is unknown, so that it doesn't jump
303+
// to a high value at the end of the transformation if the duration is set once the media is
304+
// entirely loaded.
305+
progressState =
306+
durationUs <= 0 || durationUs == C.TIME_UNSET
307+
? PROGRESS_STATE_UNAVAILABLE
308+
: PROGRESS_STATE_AVAILABLE;
309+
checkNotNull(player).play();
310+
}
311+
}
312+
313+
@Override
314+
public void onTracksChanged(Tracks tracks) {
315+
if (checkNotNull(muxerWrapper).getTrackCount() == 0) {
316+
listener.onError(new IllegalStateException("The output does not contain any tracks."));
317+
}
318+
}
319+
320+
@Override
321+
public void onPlayerError(PlaybackException error) {
322+
listener.onError(error);
323+
}
324+
}
325+
}

0 commit comments

Comments
 (0)