public abstract class

BaseRenderer

extends java.lang.Object

implements Renderer, RendererCapabilities

 java.lang.Object

↳androidx.media3.exoplayer.BaseRenderer

Subclasses:

TextRenderer, MediaCodecRenderer, MediaCodecVideoRenderer, DecoderVideoRenderer, CameraMotionRenderer, DecoderAudioRenderer<T>, MediaCodecAudioRenderer, MetadataRenderer, FakeAudioRenderer, FakeVideoRenderer, FakeRenderer, FakeMediaClockRenderer

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.0.0-alpha03'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer
  • version: 1.0.0-alpha03

Artifact androidx.media3:media3-exoplayer:1.0.0-alpha03 it located at Google repository (https://maven.google.com/)

Overview

An abstract base class suitable for most Renderer implementations.

Summary

Constructors
publicBaseRenderer(int trackType)

Methods
protected final ExoPlaybackExceptioncreateRendererException(java.lang.Throwable cause, Format format, boolean isRecoverable, int errorCode)

Creates an ExoPlaybackException of type ExoPlaybackException.TYPE_RENDERER for this renderer.

protected final ExoPlaybackExceptioncreateRendererException(java.lang.Throwable cause, Format format, int errorCode)

Creates an ExoPlaybackException of type ExoPlaybackException.TYPE_RENDERER for this renderer.

public final voiddisable()

public final voidenable(RendererConfiguration configuration, Format formats[], SampleStream stream, long positionUs, boolean joining, boolean mayRenderStartOfStream, long startPositionUs, long offsetUs)

public final RendererCapabilitiesgetCapabilities()

protected final RendererConfigurationgetConfiguration()

Returns the configuration set when the renderer was most recently enabled.

protected final FormatHoldergetFormatHolder()

Returns a clear FormatHolder.

protected final intgetIndex()

Returns the index of the renderer within the player.

protected final longgetLastResetPositionUs()

Returns the position passed to the most recent call to BaseRenderer.enable(RendererConfiguration, Format[], SampleStream, long, boolean, boolean, long, long) or BaseRenderer.resetPosition(long).

public MediaClockgetMediaClock()

protected final PlayerIdgetPlayerId()

Returns the PlayerId of the player using this renderer.

public final longgetReadingPositionUs()

public final intgetState()

public final SampleStreamgetStream()

protected final FormatgetStreamFormats()

Returns the formats of the currently enabled stream.

public final intgetTrackType()

public voidhandleMessage(int messageType, java.lang.Object message)

public final booleanhasReadStreamToEnd()

public final voidinit(int index, PlayerId playerId)

public final booleanisCurrentStreamFinal()

protected final booleanisSourceReady()

Returns whether the upstream source is ready.

public final voidmaybeThrowStreamError()

protected voidonDisabled()

Called when the renderer is disabled.

protected voidonEnabled(boolean joining, boolean mayRenderStartOfStream)

Called when the renderer is enabled.

protected voidonPositionReset(long positionUs, boolean joining)

Called when the position is reset.

protected voidonReset()

Called when the renderer is reset.

protected voidonStarted()

Called when the renderer is started.

protected voidonStopped()

Called when the renderer is stopped.

protected voidonStreamChanged(Format formats[], long startPositionUs, long offsetUs)

Called when the renderer's stream has changed.

protected final intreadSource(FormatHolder formatHolder, DecoderInputBuffer buffer, int readFlags)

Reads from the enabled upstream source.

public final voidreplaceStream(Format formats[], SampleStream stream, long startPositionUs, long offsetUs)

public final voidreset()

public final voidresetPosition(long positionUs)

public final voidsetCurrentStreamFinal()

protected intskipSource(long positionUs)

Attempts to skip to the keyframe before the specified position, or to the end of the stream if positionUs is beyond it.

public final voidstart()

public final voidstop()

public intsupportsMixedMimeTypeAdaptation()

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public BaseRenderer(int trackType)

Parameters:

trackType: The track type that the renderer handles. One of the C TRACK_TYPE_* constants.

Methods

public final int getTrackType()

public final RendererCapabilities getCapabilities()

public final void init(int index, PlayerId playerId)

public MediaClock getMediaClock()

public final int getState()

public final void enable(RendererConfiguration configuration, Format formats[], SampleStream stream, long positionUs, boolean joining, boolean mayRenderStartOfStream, long startPositionUs, long offsetUs)

public final void start()

public final void replaceStream(Format formats[], SampleStream stream, long startPositionUs, long offsetUs)

public final SampleStream getStream()

public final boolean hasReadStreamToEnd()

public final long getReadingPositionUs()

public final void setCurrentStreamFinal()

public final boolean isCurrentStreamFinal()

public final void maybeThrowStreamError()

public final void resetPosition(long positionUs)

public final void stop()

public final void disable()

public final void reset()

public int supportsMixedMimeTypeAdaptation()

public void handleMessage(int messageType, java.lang.Object message)

protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)

Called when the renderer is enabled.

The default implementation is a no-op.

Parameters:

joining: Whether this renderer is being enabled to join an ongoing playback.
mayRenderStartOfStream: Whether this renderer is allowed to render the start of the stream even if the state is not Renderer.STATE_STARTED yet.

protected void onStreamChanged(Format formats[], long startPositionUs, long offsetUs)

Called when the renderer's stream has changed. This occurs when the renderer is enabled after BaseRenderer.onEnabled(boolean, boolean) has been called, and also when the stream has been replaced whilst the renderer is enabled or started.

The default implementation is a no-op.

Parameters:

formats: The enabled formats.
startPositionUs: The start position of the new stream in renderer time (microseconds).
offsetUs: The offset that will be added to the timestamps of buffers read via BaseRenderer.readSource(FormatHolder, DecoderInputBuffer, int) so that decoder input buffers have monotonically increasing timestamps.

protected void onPositionReset(long positionUs, boolean joining)

Called when the position is reset. This occurs when the renderer is enabled after BaseRenderer.onStreamChanged(Format[], long, long) has been called, and also when a position discontinuity is encountered.

After a position reset, the renderer's SampleStream is guaranteed to provide samples starting from a key frame.

The default implementation is a no-op.

Parameters:

positionUs: The new playback position in microseconds.
joining: Whether this renderer is being enabled to join an ongoing playback.

protected void onStarted()

Called when the renderer is started.

The default implementation is a no-op.

protected void onStopped()

Called when the renderer is stopped.

The default implementation is a no-op.

protected void onDisabled()

Called when the renderer is disabled.

The default implementation is a no-op.

protected void onReset()

Called when the renderer is reset.

The default implementation is a no-op.

protected final long getLastResetPositionUs()

Returns the position passed to the most recent call to BaseRenderer.enable(RendererConfiguration, Format[], SampleStream, long, boolean, boolean, long, long) or BaseRenderer.resetPosition(long).

protected final FormatHolder getFormatHolder()

Returns a clear FormatHolder.

protected final Format getStreamFormats()

Returns the formats of the currently enabled stream.

This method may be called when the renderer is in the following states: Renderer.STATE_ENABLED, Renderer.STATE_STARTED.

protected final RendererConfiguration getConfiguration()

Returns the configuration set when the renderer was most recently enabled.

This method may be called when the renderer is in the following states: Renderer.STATE_ENABLED, Renderer.STATE_STARTED.

protected final int getIndex()

Returns the index of the renderer within the player.

Must only be used after the renderer has been initialized by the player.

protected final PlayerId getPlayerId()

Returns the PlayerId of the player using this renderer.

Must only be used after the renderer has been initialized by the player.

protected final ExoPlaybackException createRendererException(java.lang.Throwable cause, Format format, int errorCode)

Creates an ExoPlaybackException of type ExoPlaybackException.TYPE_RENDERER for this renderer.

Parameters:

cause: The cause of the exception.
format: The current format used by the renderer. May be null.
errorCode: A to identify the cause of the playback failure.

Returns:

The created instance, in which ExoPlaybackException.isRecoverable is false.

protected final ExoPlaybackException createRendererException(java.lang.Throwable cause, Format format, boolean isRecoverable, int errorCode)

Creates an ExoPlaybackException of type ExoPlaybackException.TYPE_RENDERER for this renderer.

Parameters:

cause: The cause of the exception.
format: The current format used by the renderer. May be null.
isRecoverable: If the error is recoverable by disabling and re-enabling the renderer.
errorCode: A to identify the cause of the playback failure.

Returns:

The created instance.

protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer, int readFlags)

Reads from the enabled upstream source. If the upstream source has been read to the end then C.RESULT_BUFFER_READ is only returned if BaseRenderer.setCurrentStreamFinal() has been called. C.RESULT_NOTHING_READ is returned otherwise.

This method may be called when the renderer is in the following states: Renderer.STATE_ENABLED, Renderer.STATE_STARTED.

Parameters:

formatHolder: A FormatHolder to populate in the case of reading a format.
buffer: A DecoderInputBuffer to populate in the case of reading a sample or the end of the stream. If the end of the stream has been reached, the C.BUFFER_FLAG_END_OF_STREAM flag will be set on the buffer.
readFlags: Flags controlling the behavior of this read operation.

Returns:

The result of the read operation.

protected int skipSource(long positionUs)

Attempts to skip to the keyframe before the specified position, or to the end of the stream if positionUs is beyond it.

This method may be called when the renderer is in the following states: Renderer.STATE_ENABLED, Renderer.STATE_STARTED.

Parameters:

positionUs: The position in microseconds.

Returns:

The number of samples that were skipped.

protected final boolean isSourceReady()

Returns whether the upstream source is ready.

This method may be called when the renderer is in the following states: Renderer.STATE_ENABLED, Renderer.STATE_STARTED.

Source

/*
 * Copyright (C) 2016 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.exoplayer;

import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.Math.max;

import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.decoder.DecoderInputBuffer.InsufficientCapacityException;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
import androidx.media3.exoplayer.source.SampleStream.ReadFlags;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/** An abstract base class suitable for most {@link Renderer} implementations. */
@UnstableApi
public abstract class BaseRenderer implements Renderer, RendererCapabilities {

  private final @C.TrackType int trackType;
  private final FormatHolder formatHolder;

  @Nullable private RendererConfiguration configuration;
  private int index;
  private @MonotonicNonNull PlayerId playerId;
  private int state;
  @Nullable private SampleStream stream;
  @Nullable private Format[] streamFormats;
  private long streamOffsetUs;
  private long lastResetPositionUs;
  private long readingPositionUs;
  private boolean streamIsFinal;
  private boolean throwRendererExceptionIsExecuting;

  /**
   * @param trackType The track type that the renderer handles. One of the {@link C} {@code
   *     TRACK_TYPE_*} constants.
   */
  public BaseRenderer(@C.TrackType int trackType) {
    this.trackType = trackType;
    formatHolder = new FormatHolder();
    readingPositionUs = C.TIME_END_OF_SOURCE;
  }

  @Override
  public final @C.TrackType int getTrackType() {
    return trackType;
  }

  @Override
  public final RendererCapabilities getCapabilities() {
    return this;
  }

  @Override
  public final void init(int index, PlayerId playerId) {
    this.index = index;
    this.playerId = playerId;
  }

  @Override
  @Nullable
  public MediaClock getMediaClock() {
    return null;
  }

  @Override
  public final int getState() {
    return state;
  }

  @Override
  public final void enable(
      RendererConfiguration configuration,
      Format[] formats,
      SampleStream stream,
      long positionUs,
      boolean joining,
      boolean mayRenderStartOfStream,
      long startPositionUs,
      long offsetUs)
      throws ExoPlaybackException {
    Assertions.checkState(state == STATE_DISABLED);
    this.configuration = configuration;
    state = STATE_ENABLED;
    onEnabled(joining, mayRenderStartOfStream);
    replaceStream(formats, stream, startPositionUs, offsetUs);
    resetPosition(positionUs, joining);
  }

  @Override
  public final void start() throws ExoPlaybackException {
    Assertions.checkState(state == STATE_ENABLED);
    state = STATE_STARTED;
    onStarted();
  }

  @Override
  public final void replaceStream(
      Format[] formats, SampleStream stream, long startPositionUs, long offsetUs)
      throws ExoPlaybackException {
    Assertions.checkState(!streamIsFinal);
    this.stream = stream;
    if (readingPositionUs == C.TIME_END_OF_SOURCE) {
      readingPositionUs = startPositionUs;
    }
    streamFormats = formats;
    streamOffsetUs = offsetUs;
    onStreamChanged(formats, startPositionUs, offsetUs);
  }

  @Override
  @Nullable
  public final SampleStream getStream() {
    return stream;
  }

  @Override
  public final boolean hasReadStreamToEnd() {
    return readingPositionUs == C.TIME_END_OF_SOURCE;
  }

  @Override
  public final long getReadingPositionUs() {
    return readingPositionUs;
  }

  @Override
  public final void setCurrentStreamFinal() {
    streamIsFinal = true;
  }

  @Override
  public final boolean isCurrentStreamFinal() {
    return streamIsFinal;
  }

  @Override
  public final void maybeThrowStreamError() throws IOException {
    Assertions.checkNotNull(stream).maybeThrowError();
  }

  @Override
  public final void resetPosition(long positionUs) throws ExoPlaybackException {
    resetPosition(positionUs, /* joining= */ false);
  }

  private void resetPosition(long positionUs, boolean joining) throws ExoPlaybackException {
    streamIsFinal = false;
    lastResetPositionUs = positionUs;
    readingPositionUs = positionUs;
    onPositionReset(positionUs, joining);
  }

  @Override
  public final void stop() {
    Assertions.checkState(state == STATE_STARTED);
    state = STATE_ENABLED;
    onStopped();
  }

  @Override
  public final void disable() {
    Assertions.checkState(state == STATE_ENABLED);
    formatHolder.clear();
    state = STATE_DISABLED;
    stream = null;
    streamFormats = null;
    streamIsFinal = false;
    onDisabled();
  }

  @Override
  public final void reset() {
    Assertions.checkState(state == STATE_DISABLED);
    formatHolder.clear();
    onReset();
  }

  // RendererCapabilities implementation.

  @Override
  public @AdaptiveSupport int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
    return ADAPTIVE_NOT_SUPPORTED;
  }

  // PlayerMessage.Target implementation.

  @Override
  public void handleMessage(@MessageType int messageType, @Nullable Object message)
      throws ExoPlaybackException {
    // Do nothing.
  }

  // Methods to be overridden by subclasses.

  /**
   * Called when the renderer is enabled.
   *
   * <p>The default implementation is a no-op.
   *
   * @param joining Whether this renderer is being enabled to join an ongoing playback.
   * @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
   *     stream even if the state is not {@link #STATE_STARTED} yet.
   * @throws ExoPlaybackException If an error occurs.
   */
  protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
      throws ExoPlaybackException {
    // Do nothing.
  }

  /**
   * Called when the renderer's stream has changed. This occurs when the renderer is enabled after
   * {@link #onEnabled(boolean, boolean)} has been called, and also when the stream has been
   * replaced whilst the renderer is enabled or started.
   *
   * <p>The default implementation is a no-op.
   *
   * @param formats The enabled formats.
   * @param startPositionUs The start position of the new stream in renderer time (microseconds).
   * @param offsetUs The offset that will be added to the timestamps of buffers read via {@link
   *     #readSource} so that decoder input buffers have monotonically increasing timestamps.
   * @throws ExoPlaybackException If an error occurs.
   */
  protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
      throws ExoPlaybackException {
    // Do nothing.
  }

  /**
   * Called when the position is reset. This occurs when the renderer is enabled after {@link
   * #onStreamChanged(Format[], long, long)} has been called, and also when a position discontinuity
   * is encountered.
   *
   * <p>After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples
   * starting from a key frame.
   *
   * <p>The default implementation is a no-op.
   *
   * @param positionUs The new playback position in microseconds.
   * @param joining Whether this renderer is being enabled to join an ongoing playback.
   * @throws ExoPlaybackException If an error occurs.
   */
  protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
    // Do nothing.
  }

  /**
   * Called when the renderer is started.
   *
   * <p>The default implementation is a no-op.
   *
   * @throws ExoPlaybackException If an error occurs.
   */
  protected void onStarted() throws ExoPlaybackException {
    // Do nothing.
  }

  /**
   * Called when the renderer is stopped.
   *
   * <p>The default implementation is a no-op.
   */
  protected void onStopped() {
    // Do nothing.
  }

  /**
   * Called when the renderer is disabled.
   *
   * <p>The default implementation is a no-op.
   */
  protected void onDisabled() {
    // Do nothing.
  }

  /**
   * Called when the renderer is reset.
   *
   * <p>The default implementation is a no-op.
   */
  protected void onReset() {
    // Do nothing.
  }

  // Methods to be called by subclasses.

  /**
   * Returns the position passed to the most recent call to {@link #enable} or {@link
   * #resetPosition}.
   */
  protected final long getLastResetPositionUs() {
    return lastResetPositionUs;
  }

  /** Returns a clear {@link FormatHolder}. */
  protected final FormatHolder getFormatHolder() {
    formatHolder.clear();
    return formatHolder;
  }

  /**
   * Returns the formats of the currently enabled stream.
   *
   * <p>This method may be called when the renderer is in the following states: {@link
   * #STATE_ENABLED}, {@link #STATE_STARTED}.
   */
  protected final Format[] getStreamFormats() {
    return Assertions.checkNotNull(streamFormats);
  }

  /**
   * Returns the configuration set when the renderer was most recently enabled.
   *
   * <p>This method may be called when the renderer is in the following states: {@link
   * #STATE_ENABLED}, {@link #STATE_STARTED}.
   */
  protected final RendererConfiguration getConfiguration() {
    return Assertions.checkNotNull(configuration);
  }

  /**
   * Returns the index of the renderer within the player.
   *
   * <p>Must only be used after the renderer has been initialized by the player.
   */
  protected final int getIndex() {
    return index;
  }

  /**
   * Returns the {@link PlayerId} of the player using this renderer.
   *
   * <p>Must only be used after the renderer has been initialized by the player.
   */
  protected final PlayerId getPlayerId() {
    return checkNotNull(playerId);
  }

  /**
   * Creates an {@link ExoPlaybackException} of type {@link ExoPlaybackException#TYPE_RENDERER} for
   * this renderer.
   *
   * @param cause The cause of the exception.
   * @param format The current format used by the renderer. May be null.
   * @param errorCode A {@link PlaybackException.ErrorCode} to identify the cause of the playback
   *     failure.
   * @return The created instance, in which {@link ExoPlaybackException#isRecoverable} is {@code
   *     false}.
   */
  protected final ExoPlaybackException createRendererException(
      Throwable cause, @Nullable Format format, @PlaybackException.ErrorCode int errorCode) {
    return createRendererException(cause, format, /* isRecoverable= */ false, errorCode);
  }

  /**
   * Creates an {@link ExoPlaybackException} of type {@link ExoPlaybackException#TYPE_RENDERER} for
   * this renderer.
   *
   * @param cause The cause of the exception.
   * @param format The current format used by the renderer. May be null.
   * @param isRecoverable If the error is recoverable by disabling and re-enabling the renderer.
   * @param errorCode A {@link PlaybackException.ErrorCode} to identify the cause of the playback
   *     failure.
   * @return The created instance.
   */
  protected final ExoPlaybackException createRendererException(
      Throwable cause,
      @Nullable Format format,
      boolean isRecoverable,
      @PlaybackException.ErrorCode int errorCode) {
    @C.FormatSupport int formatSupport = C.FORMAT_HANDLED;
    if (format != null && !throwRendererExceptionIsExecuting) {
      // Prevent recursive re-entry from subclass supportsFormat implementations.
      throwRendererExceptionIsExecuting = true;
      try {
        formatSupport = RendererCapabilities.getFormatSupport(supportsFormat(format));
      } catch (ExoPlaybackException e) {
        // Ignore, we are already failing.
      } finally {
        throwRendererExceptionIsExecuting = false;
      }
    }
    return ExoPlaybackException.createForRenderer(
        cause, getName(), getIndex(), format, formatSupport, isRecoverable, errorCode);
  }

  /**
   * Reads from the enabled upstream source. If the upstream source has been read to the end then
   * {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamFinal()} has been
   * called. {@link C#RESULT_NOTHING_READ} is returned otherwise.
   *
   * <p>This method may be called when the renderer is in the following states: {@link
   * #STATE_ENABLED}, {@link #STATE_STARTED}.
   *
   * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
   * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
   *     end of the stream. If the end of the stream has been reached, the {@link
   *     C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
   * @param readFlags Flags controlling the behavior of this read operation.
   * @return The {@link ReadDataResult result} of the read operation.
   * @throws InsufficientCapacityException If the {@code buffer} has insufficient capacity to hold
   *     the data of a sample being read. The buffer {@link DecoderInputBuffer#timeUs timestamp} and
   *     flags are populated if this exception is thrown, but the read position is not advanced.
   */
  protected final @ReadDataResult int readSource(
      FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) {
    @ReadDataResult
    int result = Assertions.checkNotNull(stream).readData(formatHolder, buffer, readFlags);
    if (result == C.RESULT_BUFFER_READ) {
      if (buffer.isEndOfStream()) {
        readingPositionUs = C.TIME_END_OF_SOURCE;
        return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ;
      }
      buffer.timeUs += streamOffsetUs;
      readingPositionUs = max(readingPositionUs, buffer.timeUs);
    } else if (result == C.RESULT_FORMAT_READ) {
      Format format = Assertions.checkNotNull(formatHolder.format);
      if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
        format =
            format
                .buildUpon()
                .setSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs)
                .build();
        formatHolder.format = format;
      }
    }
    return result;
  }

  /**
   * Attempts to skip to the keyframe before the specified position, or to the end of the stream if
   * {@code positionUs} is beyond it.
   *
   * <p>This method may be called when the renderer is in the following states: {@link
   * #STATE_ENABLED}, {@link #STATE_STARTED}.
   *
   * @param positionUs The position in microseconds.
   * @return The number of samples that were skipped.
   */
  protected int skipSource(long positionUs) {
    return Assertions.checkNotNull(stream).skipData(positionUs - streamOffsetUs);
  }

  /**
   * Returns whether the upstream source is ready.
   *
   * <p>This method may be called when the renderer is in the following states: {@link
   * #STATE_ENABLED}, {@link #STATE_STARTED}.
   */
  protected final boolean isSourceReady() {
    return hasReadStreamToEnd() ? streamIsFinal : Assertions.checkNotNull(stream).isReady();
  }
}