public class

FakeDataSource

extends BaseDataSource

 java.lang.Object

androidx.media3.datasource.BaseDataSource

↳androidx.media3.test.utils.FakeDataSource

Gradle dependencies

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

  • groupId: androidx.media3
  • artifactId: media3-test-utils
  • version: 1.0.0-alpha03

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

Overview

A fake DataSource capable of simulating various scenarios. It uses a FakeDataSet instance which determines the response to data access calls.

Summary

Constructors
publicFakeDataSource()

publicFakeDataSource(FakeDataSet fakeDataSet)

publicFakeDataSource(FakeDataSet fakeDataSet, boolean isNetwork)

Methods
public final voidclose()

public final DataSpecgetAndClearOpenedDataSpecs()

Returns the DataSpec instances passed to FakeDataSource.open(DataSpec) since the last call to this method.

public final FakeDataSetgetDataSet()

public final UrigetUri()

public final booleanisOpened()

Returns whether the data source is currently opened.

protected voidonClosed()

Called when the source is closed.

protected voidonDataRead(int bytesRead)

Called when data is being read.

public final longopen(DataSpec dataSpec)

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

from BaseDataSourceaddTransferListener, bytesTransferred, transferEnded, transferInitializing, transferStarted
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public FakeDataSource()

public FakeDataSource(FakeDataSet fakeDataSet)

public FakeDataSource(FakeDataSet fakeDataSet, boolean isNetwork)

Methods

public final FakeDataSet getDataSet()

public final long open(DataSpec dataSpec)

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

public final Uri getUri()

public final void close()

public final DataSpec getAndClearOpenedDataSpecs()

Returns the DataSpec instances passed to FakeDataSource.open(DataSpec) since the last call to this method.

public final boolean isOpened()

Returns whether the data source is currently opened.

protected void onDataRead(int bytesRead)

Called when data is being read.

protected void onClosed()

Called when the source is closed.

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 java.lang.Math.max;
import static java.lang.Math.min;

import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.BaseDataSource;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSourceException;
import androidx.media3.datasource.DataSpec;
import androidx.media3.test.utils.FakeDataSet.FakeData;
import androidx.media3.test.utils.FakeDataSet.FakeData.Segment;
import java.io.IOException;
import java.util.ArrayList;

/**
 * A fake {@link DataSource} capable of simulating various scenarios. It uses a {@link FakeDataSet}
 * instance which determines the response to data access calls.
 */
@UnstableApi
public class FakeDataSource extends BaseDataSource {

  /** Factory to create a {@link FakeDataSource}. */
  public static class Factory implements DataSource.Factory {

    protected FakeDataSet fakeDataSet;
    protected boolean isNetwork;

    public Factory() {
      fakeDataSet = new FakeDataSet();
    }

    public final Factory setFakeDataSet(FakeDataSet fakeDataSet) {
      this.fakeDataSet = fakeDataSet;
      return this;
    }

    public final Factory setIsNetwork(boolean isNetwork) {
      this.isNetwork = isNetwork;
      return this;
    }

    @Override
    public FakeDataSource createDataSource() {
      return new FakeDataSource(fakeDataSet, isNetwork);
    }
  }

  private final FakeDataSet fakeDataSet;
  private final ArrayList<DataSpec> openedDataSpecs;

  @Nullable private Uri uri;
  private boolean openCalled;
  private boolean sourceOpened;
  @Nullable private FakeData fakeData;
  private int currentSegmentIndex;
  private long bytesRemaining;

  public FakeDataSource() {
    this(new FakeDataSet());
  }

  public FakeDataSource(FakeDataSet fakeDataSet) {
    this(fakeDataSet, /* isNetwork= */ false);
  }

  public FakeDataSource(FakeDataSet fakeDataSet, boolean isNetwork) {
    super(isNetwork);
    Assertions.checkNotNull(fakeDataSet);
    this.fakeDataSet = fakeDataSet;
    this.openedDataSpecs = new ArrayList<>();
  }

  public final FakeDataSet getDataSet() {
    return fakeDataSet;
  }

  @Override
  public final long open(DataSpec dataSpec) throws IOException {
    Assertions.checkState(!openCalled);
    openCalled = true;

    // DataSpec requires a matching close call even if open fails.
    uri = dataSpec.uri;
    openedDataSpecs.add(dataSpec);

    transferInitializing(dataSpec);
    FakeData fakeData = fakeDataSet.getData(dataSpec.uri.toString());
    if (fakeData == null) {
      throw new IOException("Data not found: " + dataSpec.uri);
    }
    this.fakeData = fakeData;

    long totalLength = 0;
    for (Segment segment : fakeData.getSegments()) {
      totalLength += segment.length;
    }

    if (totalLength == 0) {
      throw new IOException("Data is empty: " + dataSpec.uri);
    }

    if (dataSpec.position > totalLength) {
      throw new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
    }

    // Scan through the segments, configuring them for the current read.
    boolean findingCurrentSegmentIndex = true;
    currentSegmentIndex = 0;
    int scannedLength = 0;
    for (Segment segment : fakeData.getSegments()) {
      segment.bytesRead = (int) min(max(0, dataSpec.position - scannedLength), segment.length);
      scannedLength += segment.length;
      findingCurrentSegmentIndex &=
          segment.isErrorSegment()
              ? segment.exceptionCleared
              : (!segment.isActionSegment() && segment.bytesRead == segment.length);
      if (findingCurrentSegmentIndex) {
        currentSegmentIndex++;
      }
    }
    sourceOpened = true;
    transferStarted(dataSpec);
    // Configure bytesRemaining, and return.
    if (dataSpec.length == C.LENGTH_UNSET) {
      bytesRemaining = totalLength - dataSpec.position;
      return fakeData.isSimulatingUnknownLength() ? C.LENGTH_UNSET : bytesRemaining;
    } else {
      bytesRemaining = dataSpec.length;
      return bytesRemaining;
    }
  }

  @Override
  public final int read(byte[] buffer, int offset, int length) throws IOException {
    Assertions.checkState(sourceOpened);
    while (true) {
      FakeData fakeData = Util.castNonNull(this.fakeData);
      if (currentSegmentIndex == fakeData.getSegments().size() || bytesRemaining == 0) {
        return C.RESULT_END_OF_INPUT;
      }
      Segment current = fakeData.getSegments().get(currentSegmentIndex);
      if (current.isErrorSegment()) {
        if (!current.exceptionCleared) {
          current.exceptionThrown = true;
          throw (IOException) Util.castNonNull(current.exception).fillInStackTrace();
        } else {
          currentSegmentIndex++;
        }
      } else if (current.isActionSegment()) {
        currentSegmentIndex++;
        Util.castNonNull(current.action).run();
      } else {
        // Read at most bytesRemaining.
        length = (int) min(length, bytesRemaining);
        // Do not allow crossing of the segment boundary.
        length = min(length, current.length - current.bytesRead);
        // Perform the read and return.
        Assertions.checkArgument(buffer.length - offset >= length);
        if (current.data != null) {
          System.arraycopy(current.data, current.bytesRead, buffer, offset, length);
        }
        onDataRead(length);
        bytesTransferred(length);
        bytesRemaining -= length;
        current.bytesRead += length;
        if (current.bytesRead == current.length) {
          currentSegmentIndex++;
        }
        return length;
      }
    }
  }

  @Override
  @Nullable
  public final Uri getUri() {
    return uri;
  }

  @Override
  public final void close() {
    Assertions.checkState(openCalled);
    openCalled = false;
    uri = null;
    if (fakeData != null && currentSegmentIndex < fakeData.getSegments().size()) {
      Segment current = fakeData.getSegments().get(currentSegmentIndex);
      if (current.isErrorSegment() && current.exceptionThrown) {
        current.exceptionCleared = true;
      }
    }
    if (sourceOpened) {
      sourceOpened = false;
      transferEnded();
    }
    fakeData = null;
    onClosed();
  }

  /**
   * Returns the {@link DataSpec} instances passed to {@link #open(DataSpec)} since the last call to
   * this method.
   */
  public final DataSpec[] getAndClearOpenedDataSpecs() {
    DataSpec[] dataSpecs = openedDataSpecs.toArray(new DataSpec[0]);
    openedDataSpecs.clear();
    return dataSpecs;
  }

  /** Returns whether the data source is currently opened. */
  public final boolean isOpened() {
    return sourceOpened;
  }

  /** Called when data is being read. */
  protected void onDataRead(int bytesRead) throws IOException {
    // Do nothing. Can be overridden.
  }

  /** Called when the source is closed. */
  protected void onClosed() {
    // Do nothing. Can be overridden.
  }
}