public final class

FakeExtractorInput

extends java.lang.Object

implements ExtractorInput

 java.lang.Object

↳androidx.media3.test.utils.FakeExtractorInput

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-test-utils', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-test-utils
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-test-utils:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

A fake ExtractorInput capable of simulating various scenarios.

Read, skip and peek errors can be simulated using FakeExtractorInput.Builder.setSimulateIOErrors(boolean). When enabled each read and skip will throw a FakeExtractorInput.SimulatedIOException unless one has already been thrown from the current position. Each peek will throw FakeExtractorInput.SimulatedIOException unless one has already been thrown from the current peek position. When a FakeExtractorInput.SimulatedIOException is thrown the read position is left unchanged and the peek position is reset back to the read position.

Partial reads and skips can be simulated using FakeExtractorInput.Builder.setSimulatePartialReads(boolean). When enabled, FakeExtractorInput.read(byte[], int, int) and FakeExtractorInput.skip(int) calls will only read or skip a single byte unless a partial read or skip has already been performed that had the same target position. For example, a first read request for 10 bytes will be partially satisfied by reading a single byte and advancing the position to 1. If the following read request attempts to read 9 bytes then it will be fully satisfied, since it has the same target position of 10.

Unknown data length can be simulated using FakeExtractorInput.Builder.setSimulateUnknownLength(boolean). When enabled FakeExtractorInput.getLength() will return C.LENGTH_UNSET rather than the length of the data.

Summary

Methods
public voidadvancePeekPosition(int length)

public booleanadvancePeekPosition(int length, boolean allowEndOfInput)

public longgetLength()

public longgetPeekPosition()

public longgetPosition()

public intpeek(byte[] target[], int offset, int length)

public voidpeekFully(byte[] target[], int offset, int length)

public booleanpeekFully(byte[] target[], int offset, int length, boolean allowEndOfInput)

public intread(byte[] buffer[], int offset, int length)

public voidreadFully(byte[] target[], int offset, int length)

public booleanreadFully(byte[] target[], int offset, int length, boolean allowEndOfInput)

public voidreset()

Resets the input to its initial state.

public voidresetPeekPosition()

public voidsetPosition(int position)

Sets the read and peek positions.

public voidsetRetryPosition(long position, java.lang.Throwable e)

public intskip(int length)

public voidskipFully(int length)

public booleanskipFully(int length, boolean allowEndOfInput)

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

Methods

public void reset()

Resets the input to its initial state.

public void setPosition(int position)

Sets the read and peek positions.

Parameters:

position: The position to set.

public int read(byte[] buffer[], int offset, int length)

public boolean readFully(byte[] target[], int offset, int length, boolean allowEndOfInput)

public void readFully(byte[] target[], int offset, int length)

public int skip(int length)

public boolean skipFully(int length, boolean allowEndOfInput)

public void skipFully(int length)

public int peek(byte[] target[], int offset, int length)

public boolean peekFully(byte[] target[], int offset, int length, boolean allowEndOfInput)

public void peekFully(byte[] target[], int offset, int length)

public boolean advancePeekPosition(int length, boolean allowEndOfInput)

public void advancePeekPosition(int length)

public void resetPeekPosition()

public long getPeekPosition()

public long getPosition()

public long getLength()

public void setRetryPosition(long position, java.lang.Throwable e)

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.test.utils;

import static com.google.common.truth.Truth.assertThat;
import static java.lang.Math.min;

import android.util.SparseBooleanArray;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.extractor.ExtractorInput;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.EOFException;
import java.io.IOException;

/**
 * A fake {@link ExtractorInput} capable of simulating various scenarios.
 *
 * <p>Read, skip and peek errors can be simulated using {@link Builder#setSimulateIOErrors}. When
 * enabled each read and skip will throw a {@link SimulatedIOException} unless one has already been
 * thrown from the current position. Each peek will throw {@link SimulatedIOException} unless one
 * has already been thrown from the current peek position. When a {@link SimulatedIOException} is
 * thrown the read position is left unchanged and the peek position is reset back to the read
 * position.
 *
 * <p>Partial reads and skips can be simulated using {@link Builder#setSimulatePartialReads}. When
 * enabled, {@link #read(byte[], int, int)} and {@link #skip(int)} calls will only read or skip a
 * single byte unless a partial read or skip has already been performed that had the same target
 * position. For example, a first read request for 10 bytes will be partially satisfied by reading a
 * single byte and advancing the position to 1. If the following read request attempts to read 9
 * bytes then it will be fully satisfied, since it has the same target position of 10.
 *
 * <p>Unknown data length can be simulated using {@link Builder#setSimulateUnknownLength}. When
 * enabled {@link #getLength()} will return {@link C#LENGTH_UNSET} rather than the length of the
 * data.
 */
@UnstableApi
public final class FakeExtractorInput implements ExtractorInput {

  /** Thrown when simulating an {@link IOException}. */
  public static final class SimulatedIOException extends IOException {

    public SimulatedIOException(String message) {
      super(message);
    }
  }

  private final byte[] data;
  private final boolean simulateUnknownLength;
  private final boolean simulatePartialReads;
  private final boolean simulateIOErrors;

  private int readPosition;
  private int peekPosition;

  private final SparseBooleanArray partiallySatisfiedTargetReadPositions;
  private final SparseBooleanArray partiallySatisfiedTargetPeekPositions;
  private final SparseBooleanArray failedReadPositions;
  private final SparseBooleanArray failedPeekPositions;

  private FakeExtractorInput(
      byte[] data,
      boolean simulateUnknownLength,
      boolean simulatePartialReads,
      boolean simulateIOErrors) {
    this.data = data;
    this.simulateUnknownLength = simulateUnknownLength;
    this.simulatePartialReads = simulatePartialReads;
    this.simulateIOErrors = simulateIOErrors;
    partiallySatisfiedTargetReadPositions = new SparseBooleanArray();
    partiallySatisfiedTargetPeekPositions = new SparseBooleanArray();
    failedReadPositions = new SparseBooleanArray();
    failedPeekPositions = new SparseBooleanArray();
  }

  /** Resets the input to its initial state. */
  public void reset() {
    readPosition = 0;
    peekPosition = 0;
    partiallySatisfiedTargetReadPositions.clear();
    partiallySatisfiedTargetPeekPositions.clear();
    failedReadPositions.clear();
    failedPeekPositions.clear();
  }

  /**
   * Sets the read and peek positions.
   *
   * @param position The position to set.
   */
  public void setPosition(int position) {
    assertThat(position).isAtLeast(0);
    assertThat(position).isAtMost(data.length);
    readPosition = position;
    peekPosition = position;
  }

  @Override
  public int read(byte[] buffer, int offset, int length) throws IOException {
    checkIOException(readPosition, failedReadPositions);
    length = getLengthToRead(readPosition, length, partiallySatisfiedTargetReadPositions);
    return readFullyInternal(buffer, offset, length, true) ? length : C.RESULT_END_OF_INPUT;
  }

  @Override
  public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
      throws IOException {
    checkIOException(readPosition, failedReadPositions);
    return readFullyInternal(target, offset, length, allowEndOfInput);
  }

  @Override
  public void readFully(byte[] target, int offset, int length) throws IOException {
    readFully(target, offset, length, false);
  }

  @Override
  public int skip(int length) throws IOException {
    checkIOException(readPosition, failedReadPositions);
    length = getLengthToRead(readPosition, length, partiallySatisfiedTargetReadPositions);
    return skipFullyInternal(length, true) ? length : C.RESULT_END_OF_INPUT;
  }

  @Override
  public boolean skipFully(int length, boolean allowEndOfInput) throws IOException {
    checkIOException(readPosition, failedReadPositions);
    return skipFullyInternal(length, allowEndOfInput);
  }

  @Override
  public void skipFully(int length) throws IOException {
    skipFully(length, false);
  }

  @Override
  public int peek(byte[] target, int offset, int length) throws IOException {
    checkIOException(peekPosition, failedPeekPositions);
    length = getLengthToRead(peekPosition, length, partiallySatisfiedTargetPeekPositions);
    return peekFullyInternal(target, offset, length, true) ? length : C.RESULT_END_OF_INPUT;
  }

  @Override
  public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
      throws IOException {
    checkIOException(peekPosition, failedPeekPositions);
    return peekFullyInternal(target, offset, length, allowEndOfInput);
  }

  @Override
  public void peekFully(byte[] target, int offset, int length) throws IOException {
    peekFully(target, offset, length, false);
  }

  @Override
  public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException {
    checkIOException(peekPosition, failedPeekPositions);
    if (!checkXFully(allowEndOfInput, peekPosition, length)) {
      return false;
    }
    peekPosition += length;
    return true;
  }

  @Override
  public void advancePeekPosition(int length) throws IOException {
    advancePeekPosition(length, false);
  }

  @Override
  public void resetPeekPosition() {
    peekPosition = readPosition;
  }

  @Override
  public long getPeekPosition() {
    return peekPosition;
  }

  @Override
  public long getPosition() {
    return readPosition;
  }

  @Override
  public long getLength() {
    return simulateUnknownLength ? C.LENGTH_UNSET : data.length;
  }

  @Override
  public <E extends Throwable> void setRetryPosition(long position, E e) throws E {
    assertThat(position >= 0).isTrue();
    readPosition = (int) position;
    throw e;
  }

  private void checkIOException(int position, SparseBooleanArray failedPositions)
      throws SimulatedIOException {
    if (simulateIOErrors && !failedPositions.get(position)) {
      failedPositions.put(position, true);
      peekPosition = readPosition;
      throw new SimulatedIOException("Simulated IO error at position: " + position);
    }
  }

  private boolean checkXFully(boolean allowEndOfInput, int position, int length)
      throws EOFException {
    if (length > 0 && position == data.length) {
      if (allowEndOfInput) {
        return false;
      }
      throw new EOFException();
    }
    if (position + length > data.length) {
      throw new EOFException(
          "Attempted to move past end of data: ("
              + position
              + " + "
              + length
              + ") > "
              + data.length);
    }
    return true;
  }

  private int getLengthToRead(
      int position, int requestedLength, SparseBooleanArray partiallySatisfiedTargetPositions) {
    if (position == data.length) {
      // If the requested length is non-zero, the end of the input will be read.
      return requestedLength == 0 ? 0 : Integer.MAX_VALUE;
    }
    int targetPosition = position + requestedLength;
    if (simulatePartialReads
        && requestedLength > 1
        && !partiallySatisfiedTargetPositions.get(targetPosition)) {
      partiallySatisfiedTargetPositions.put(targetPosition, true);
      return 1;
    }
    return min(requestedLength, data.length - position);
  }

  private boolean readFullyInternal(byte[] target, int offset, int length, boolean allowEndOfInput)
      throws EOFException {
    if (!checkXFully(allowEndOfInput, readPosition, length)) {
      return false;
    }
    System.arraycopy(data, readPosition, target, offset, length);
    readPosition += length;
    peekPosition = readPosition;
    return true;
  }

  private boolean skipFullyInternal(int length, boolean allowEndOfInput) throws EOFException {
    if (!checkXFully(allowEndOfInput, readPosition, length)) {
      return false;
    }
    readPosition += length;
    peekPosition = readPosition;
    return true;
  }

  private boolean peekFullyInternal(byte[] target, int offset, int length, boolean allowEndOfInput)
      throws EOFException {
    if (!checkXFully(allowEndOfInput, peekPosition, length)) {
      return false;
    }
    System.arraycopy(data, peekPosition, target, offset, length);
    peekPosition += length;
    return true;
  }

  /** Builder of {@link FakeExtractorInput} instances. */
  public static final class Builder {

    private byte[] data;
    private boolean simulateUnknownLength;
    private boolean simulatePartialReads;
    private boolean simulateIOErrors;

    public Builder() {
      data = Util.EMPTY_BYTE_ARRAY;
    }

    @CanIgnoreReturnValue
    public Builder setData(byte[] data) {
      this.data = data;
      return this;
    }

    @CanIgnoreReturnValue
    public Builder setSimulateUnknownLength(boolean simulateUnknownLength) {
      this.simulateUnknownLength = simulateUnknownLength;
      return this;
    }

    @CanIgnoreReturnValue
    public Builder setSimulatePartialReads(boolean simulatePartialReads) {
      this.simulatePartialReads = simulatePartialReads;
      return this;
    }

    @CanIgnoreReturnValue
    public Builder setSimulateIOErrors(boolean simulateIOErrors) {
      this.simulateIOErrors = simulateIOErrors;
      return this;
    }

    public FakeExtractorInput build() {
      return new FakeExtractorInput(
          data, simulateUnknownLength, simulatePartialReads, simulateIOErrors);
    }
  }
}