/*
* Copyright 2020 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.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.BaseRenderer;
import androidx.media3.exoplayer.MediaClock;
import androidx.media3.exoplayer.RendererCapabilities;
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
import com.google.errorprone.annotations.ForOverride;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/* package */ abstract class TransformerBaseRenderer extends BaseRenderer {
protected final MuxerWrapper muxerWrapper;
protected final TransformerMediaClock mediaClock;
protected final TransformationRequest transformationRequest;
protected final Transformer.AsyncErrorListener asyncErrorListener;
protected final FallbackListener fallbackListener;
private boolean isTransformationRunning;
protected long streamOffsetUs;
protected long streamStartPositionUs;
protected @MonotonicNonNull SamplePipeline samplePipeline;
public TransformerBaseRenderer(
int trackType,
MuxerWrapper muxerWrapper,
TransformerMediaClock mediaClock,
TransformationRequest transformationRequest,
Transformer.AsyncErrorListener asyncErrorListener,
FallbackListener fallbackListener) {
super(trackType);
this.muxerWrapper = muxerWrapper;
this.mediaClock = mediaClock;
this.transformationRequest = transformationRequest;
this.asyncErrorListener = asyncErrorListener;
this.fallbackListener = fallbackListener;
}
/**
* Returns whether the renderer supports the track type of the given format.
*
* @param format The format.
* @return The {@link Capabilities} for this format.
*/
@Override
public final @Capabilities int supportsFormat(Format format) {
return RendererCapabilities.create(
MimeTypes.getTrackType(format.sampleMimeType) == getTrackType()
? C.FORMAT_HANDLED
: C.FORMAT_UNSUPPORTED_TYPE);
}
@Override
public final MediaClock getMediaClock() {
return mediaClock;
}
@Override
public final boolean isReady() {
return isSourceReady();
}
@Override
public final boolean isEnded() {
return samplePipeline != null && samplePipeline.isEnded();
}
@Override
public final void render(long positionUs, long elapsedRealtimeUs) {
try {
if (!isTransformationRunning || isEnded() || !ensureConfigured()) {
return;
}
while (samplePipeline.processData() || feedPipelineFromInput()) {}
} catch (TransformationException e) {
isTransformationRunning = false;
asyncErrorListener.onTransformationException(e);
}
}
@Override
protected final void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
this.streamOffsetUs = offsetUs;
this.streamStartPositionUs = startPositionUs;
}
@Override
protected final void onEnabled(boolean joining, boolean mayRenderStartOfStream) {
muxerWrapper.registerTrack();
fallbackListener.registerTrack();
mediaClock.updateTimeForTrackType(getTrackType(), 0L);
}
@Override
protected final void onStarted() {
isTransformationRunning = true;
}
@Override
protected final void onStopped() {
isTransformationRunning = false;
}
@Override
protected final void onReset() {
if (samplePipeline != null) {
samplePipeline.release();
}
}
@ForOverride
@EnsuresNonNullIf(expression = "samplePipeline", result = true)
protected abstract boolean ensureConfigured() throws TransformationException;
/**
* Attempts to read input data and pass the input data to the sample pipeline.
*
* @return Whether it may be possible to read more data immediately by calling this method again.
* @throws TransformationException If a {@link SamplePipeline} problem occurs.
*/
@RequiresNonNull("samplePipeline")
private boolean feedPipelineFromInput() throws TransformationException {
@Nullable DecoderInputBuffer samplePipelineInputBuffer = samplePipeline.dequeueInputBuffer();
if (samplePipelineInputBuffer == null) {
return false;
}
@ReadDataResult
int result = readSource(getFormatHolder(), samplePipelineInputBuffer, /* readFlags= */ 0);
switch (result) {
case C.RESULT_BUFFER_READ:
samplePipelineInputBuffer.flip();
if (samplePipelineInputBuffer.isEndOfStream()) {
samplePipeline.queueInputBuffer();
return false;
}
mediaClock.updateTimeForTrackType(getTrackType(), samplePipelineInputBuffer.timeUs);
samplePipeline.queueInputBuffer();
return true;
case C.RESULT_FORMAT_READ:
throw new IllegalStateException("Format changes are not supported.");
case C.RESULT_NOTHING_READ:
default:
return false;
}
}
}