public class

FakeSampleStream

extends java.lang.Object

implements SampleStream

 java.lang.Object

↳androidx.media3.test.utils.FakeSampleStream

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

Fake SampleStream that outputs a given Format and any amount of items.

Summary

Constructors
publicFakeSampleStream(Allocator allocator, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, Format initialFormat, java.util.List<FakeSampleStream.FakeSampleStreamItem> fakeSampleStreamItems)

Creates a fake sample stream which outputs the given Format followed by the provided items.

Methods
public voidappend(java.util.List<FakeSampleStream.FakeSampleStreamItem> items)

Appends FakeSampleStreamItems to the list of items that should be written to the queue.

public voiddiscardTo(long positionUs, boolean toKeyframe)

Discards data from the queue.

public longgetLargestQueuedTimestampUs()

Returns the timestamp of the largest queued sample in the queue, or MIN_VALUE if no samples are queued.

public booleanisLoadingFinished()

Returns whether data has been written to the sample queue until the end of stream signal.

public booleanisReady()

public voidmaybeThrowError()

public intreadData(FormatHolder formatHolder, DecoderInputBuffer buffer, int readFlags)

public voidrelease()

Release the stream and its underlying sample queue.

public voidreset()

Resets the sample queue.

public booleanseekToUs(long positionUs)

Seeks the stream to a new position using already available data in the queue.

public intskipData(long positionUs)

public voidwriteData(long startPositionUs)

Writes all not yet written sample stream items to the sample queue starting at the given position.

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

Constructors

public FakeSampleStream(Allocator allocator, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, Format initialFormat, java.util.List<FakeSampleStream.FakeSampleStreamItem> fakeSampleStreamItems)

Creates a fake sample stream which outputs the given Format followed by the provided items.

Parameters:

allocator: An Allocator.
mediaSourceEventDispatcher: A to notify of media events.
drmSessionManager: A DrmSessionManager for DRM interactions.
drmEventDispatcher: A to notify of DRM events.
initialFormat: The first Format to output.
fakeSampleStreamItems: The items to output.

Methods

public void append(java.util.List<FakeSampleStream.FakeSampleStreamItem> items)

Appends FakeSampleStreamItems to the list of items that should be written to the queue.

Note that this data is only written to the queue once FakeSampleStream.writeData(long) is called.

Parameters:

items: The items to append.

public void writeData(long startPositionUs)

Writes all not yet written sample stream items to the sample queue starting at the given position.

Parameters:

startPositionUs: The start position, in microseconds.

public boolean seekToUs(long positionUs)

Seeks the stream to a new position using already available data in the queue.

Parameters:

positionUs: The new position, in microseconds.

Returns:

Whether seeking inside the available data was possible.

public void reset()

Resets the sample queue.

A new call to FakeSampleStream.writeData(long) is required to fill the queue again.

public boolean isLoadingFinished()

Returns whether data has been written to the sample queue until the end of stream signal.

public long getLargestQueuedTimestampUs()

Returns the timestamp of the largest queued sample in the queue, or MIN_VALUE if no samples are queued.

public void discardTo(long positionUs, boolean toKeyframe)

Discards data from the queue.

Parameters:

positionUs: The position to discard to, in microseconds.
toKeyframe: Whether to discard to keyframes only.

public void release()

Release the stream and its underlying sample queue.

public boolean isReady()

public void maybeThrowError()

public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, int readFlags)

public int skipData(long positionUs)

Source

/*
 * Copyright (C) 2017 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 androidx.media3.common.util.Assertions.checkNotNull;

import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.exoplayer.FormatHolder;
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.source.MediaSourceEventListener;
import androidx.media3.exoplayer.source.SampleQueue;
import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.upstream.Allocator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Fake {@link SampleStream} that outputs a given {@link Format} and any amount of {@link
 * FakeSampleStreamItem items}.
 */
@UnstableApi
public class FakeSampleStream implements SampleStream {

  /** Item to customize a return value of {@link SampleStream#readData}. */
  public static final class FakeSampleStreamItem {

    /** Item that designates the end of stream has been reached. */
    public static final FakeSampleStreamItem END_OF_STREAM_ITEM =
        sample(
            /* timeUs= */ Long.MAX_VALUE,
            C.BUFFER_FLAG_END_OF_STREAM,
            /* sampleData= */ new byte[] {});

    /** Creates an item representing the provided format. */
    public static FakeSampleStreamItem format(Format format) {
      return new FakeSampleStreamItem(format, /* sampleInfo= */ null);
    }

    /**
     * Creates an item representing a sample with the provided timestamp.
     *
     * <p>The sample will contain a single byte of data.
     *
     * @param timeUs The timestamp of the sample.
     */
    public static FakeSampleStreamItem oneByteSample(long timeUs) {
      return oneByteSample(timeUs, /* flags= */ 0);
    }

    /**
     * Creates an item representing a sample with the provided timestamp and flags.
     *
     * <p>The sample will contain a single byte of data.
     *
     * @param timeUs The timestamp of the sample.
     * @param flags The sample {@link C.BufferFlags}.
     */
    public static FakeSampleStreamItem oneByteSample(long timeUs, @C.BufferFlags int flags) {
      return sample(timeUs, flags, new byte[] {0});
    }

    /**
     * Creates an item representing a sample with the provided timestamp, flags and data.
     *
     * @param timeUs The timestamp of the sample.
     * @param flags The sample {@link C.BufferFlags}.
     * @param sampleData The sample data.
     */
    public static FakeSampleStreamItem sample(
        long timeUs, @C.BufferFlags int flags, byte[] sampleData) {
      return new FakeSampleStreamItem(
          /* format= */ null, new SampleInfo(sampleData.clone(), flags, timeUs));
    }

    @Nullable private final Format format;
    @Nullable private final SampleInfo sampleInfo;

    /**
     * Creates an instance. Exactly one of {@code format} or {@code sampleInfo} must be non-null.
     */
    private FakeSampleStreamItem(@Nullable Format format, @Nullable SampleInfo sampleInfo) {
      Assertions.checkArgument((format == null) != (sampleInfo == null));
      this.format = format;
      this.sampleInfo = sampleInfo;
    }
  }

  private final SampleQueue sampleQueue;
  @Nullable private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher;
  private final List<FakeSampleStreamItem> sampleStreamItems;

  private int sampleStreamItemsWritePosition;
  private boolean loadingFinished;
  @Nullable private Format downstreamFormat;
  @Nullable private Format notifiedDownstreamFormat;

  /**
   * Creates a fake sample stream which outputs the given {@link Format} followed by the provided
   * {@link FakeSampleStreamItem items}.
   *
   * @param allocator An {@link Allocator}.
   * @param mediaSourceEventDispatcher A {@link MediaSourceEventListener.EventDispatcher} to notify
   *     of media events.
   * @param drmSessionManager A {@link DrmSessionManager} for DRM interactions.
   * @param drmEventDispatcher A {@link DrmSessionEventListener.EventDispatcher} to notify of DRM
   *     events.
   * @param initialFormat The first {@link Format} to output.
   * @param fakeSampleStreamItems The {@link FakeSampleStreamItem items} to output.
   */
  public FakeSampleStream(
      Allocator allocator,
      @Nullable MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
      DrmSessionManager drmSessionManager,
      DrmSessionEventListener.EventDispatcher drmEventDispatcher,
      Format initialFormat,
      List<FakeSampleStreamItem> fakeSampleStreamItems) {
    this.sampleQueue = SampleQueue.createWithDrm(allocator, drmSessionManager, drmEventDispatcher);
    this.mediaSourceEventDispatcher = mediaSourceEventDispatcher;
    this.sampleStreamItems = new ArrayList<>();
    sampleStreamItems.add(FakeSampleStreamItem.format(initialFormat));
    sampleStreamItems.addAll(fakeSampleStreamItems);
  }

  /**
   * Appends {@link FakeSampleStreamItem FakeSampleStreamItems} to the list of items that should be
   * written to the queue.
   *
   * <p>Note that this data is only written to the queue once {@link #writeData(long)} is called.
   *
   * @param items The items to append.
   */
  public void append(List<FakeSampleStreamItem> items) {
    sampleStreamItems.addAll(items);
  }

  /**
   * Writes all not yet written {@link FakeSampleStreamItem sample stream items} to the sample queue
   * starting at the given position.
   *
   * @param startPositionUs The start position, in microseconds.
   */
  public void writeData(long startPositionUs) {
    if (sampleStreamItemsWritePosition == 0) {
      sampleQueue.setStartTimeUs(startPositionUs);
    }
    boolean writtenFirstFormat = false;
    @Nullable Format pendingFirstFormat = null;
    for (int i = 0; i < sampleStreamItems.size(); i++) {
      FakeSampleStreamItem fakeSampleStreamItem = sampleStreamItems.get(i);
      @Nullable FakeSampleStream.SampleInfo sampleInfo = fakeSampleStreamItem.sampleInfo;
      if (sampleInfo == null) {
        if (writtenFirstFormat) {
          sampleQueue.format(checkNotNull(fakeSampleStreamItem.format));
        } else {
          pendingFirstFormat = checkNotNull(fakeSampleStreamItem.format);
        }
        continue;
      }
      if ((sampleInfo.flags & C.BUFFER_FLAG_END_OF_STREAM) != 0) {
        loadingFinished = true;
        break;
      }
      if (sampleInfo.timeUs >= startPositionUs && i >= sampleStreamItemsWritePosition) {
        if (!writtenFirstFormat) {
          sampleQueue.format(checkNotNull(pendingFirstFormat));
          writtenFirstFormat = true;
        }
        sampleQueue.sampleData(new ParsableByteArray(sampleInfo.data), sampleInfo.data.length);
        sampleQueue.sampleMetadata(
            sampleInfo.timeUs,
            sampleInfo.flags,
            sampleInfo.data.length,
            /* offset= */ 0,
            /* cryptoData= */ null);
      }
    }
    sampleStreamItemsWritePosition = sampleStreamItems.size();
  }

  /**
   * Seeks the stream to a new position using already available data in the queue.
   *
   * @param positionUs The new position, in microseconds.
   * @return Whether seeking inside the available data was possible.
   */
  public boolean seekToUs(long positionUs) {
    return sampleQueue.seekTo(positionUs, /* allowTimeBeyondBuffer= */ false);
  }

  /**
   * Resets the sample queue.
   *
   * <p>A new call to {@link #writeData(long)} is required to fill the queue again.
   */
  public void reset() {
    sampleQueue.reset();
    sampleStreamItemsWritePosition = 0;
    loadingFinished = false;
  }

  /** Returns whether data has been written to the sample queue until the end of stream signal. */
  public boolean isLoadingFinished() {
    return loadingFinished;
  }

  /**
   * Returns the timestamp of the largest queued sample in the queue, or {@link Long#MIN_VALUE} if
   * no samples are queued.
   */
  public long getLargestQueuedTimestampUs() {
    return sampleQueue.getLargestQueuedTimestampUs();
  }

  /**
   * Discards data from the queue.
   *
   * @param positionUs The position to discard to, in microseconds.
   * @param toKeyframe Whether to discard to keyframes only.
   */
  public void discardTo(long positionUs, boolean toKeyframe) {
    sampleQueue.discardTo(positionUs, toKeyframe, /* stopAtReadPosition= */ true);
  }

  /** Release the stream and its underlying sample queue. */
  public void release() {
    sampleQueue.release();
  }

  @Override
  public boolean isReady() {
    return sampleQueue.isReady(loadingFinished);
  }

  @Override
  public void maybeThrowError() throws IOException {
    sampleQueue.maybeThrowError();
  }

  @Override
  public int readData(
      FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) {
    int result = sampleQueue.read(formatHolder, buffer, readFlags, loadingFinished);
    if (result == C.RESULT_FORMAT_READ) {
      downstreamFormat = checkNotNull(formatHolder.format);
    }
    if (result == C.RESULT_BUFFER_READ && (readFlags & FLAG_OMIT_SAMPLE_DATA) == 0) {
      maybeNotifyDownstreamFormat(buffer.timeUs);
    }
    return result;
  }

  @Override
  public int skipData(long positionUs) {
    int skipCount = sampleQueue.getSkipCount(positionUs, loadingFinished);
    sampleQueue.skip(skipCount);
    return skipCount;
  }

  private void maybeNotifyDownstreamFormat(long timeUs) {
    if (mediaSourceEventDispatcher != null
        && downstreamFormat != null
        && !downstreamFormat.equals(notifiedDownstreamFormat)) {
      mediaSourceEventDispatcher.downstreamFormatChanged(
          MimeTypes.getTrackType(downstreamFormat.sampleMimeType),
          downstreamFormat,
          C.SELECTION_REASON_UNKNOWN,
          /* trackSelectionData= */ null,
          timeUs);
      notifiedDownstreamFormat = downstreamFormat;
    }
  }

  private static class SampleInfo {
    public final byte[] data;
    public final @C.BufferFlags int flags;
    public final long timeUs;

    public SampleInfo(byte[] data, @C.BufferFlags int flags, long timeUs) {
      this.data = Arrays.copyOf(data, data.length);
      this.flags = flags;
      this.timeUs = timeUs;
    }
  }
}