Subclasses:
FakeAdaptiveMediaSource
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 MediaSource that provides a given timeline. Creating the period will return a FakeMediaPeriod with a TrackGroupArray using the given Formats.
Summary
Methods |
---|
public void | assertMediaPeriodCreated(MediaSource.MediaPeriodId mediaPeriodId)
Assert that a media period for the given id has been created. |
public void | assertReleased()
Assert that the source and all periods have been released. |
protected MediaPeriod | createMediaPeriod(MediaSource.MediaPeriodId id, TrackGroupArray trackGroupArray, Allocator allocator, MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher, DrmSessionManager drmSessionManager, DrmSessionEventListener.EventDispatcher drmEventDispatcher, TransferListener transferListener)
Creates a MediaPeriod for this media source. |
public MediaPeriod | createPeriod(MediaSource.MediaPeriodId id, Allocator allocator, long startPositionUs)
|
public static FakeMediaSource | createWithWindowId(java.lang.Object windowId)
Convenience method to create a FakeMediaSource with the given window id. |
public java.util.List<MediaSource.MediaPeriodId> | getCreatedMediaPeriods()
Returns a list of s, with one element for each created media period. |
public Timeline | getInitialTimeline()
|
public MediaItem | getMediaItem()
|
protected Timeline | getTimeline()
|
public boolean | isPrepared()
Returns whether the source is currently prepared. |
public boolean | isSingleWindow()
|
public void | maybeThrowSourceInfoRefreshError()
|
protected abstract void | prepareSourceInternal(TransferListener mediaTransferListener)
Starts source preparation and enables the source, see MediaSource.prepareSource(MediaSource.MediaSourceCaller, TransferListener, PlayerId). |
protected void | releaseMediaPeriod(MediaPeriod mediaPeriod)
Releases a media period created by FakeMediaSource. |
public void | releasePeriod(MediaPeriod mediaPeriod)
|
protected abstract void | releaseSourceInternal()
Releases the source, see MediaSource.releaseSource(MediaSource.MediaSourceCaller). |
public synchronized void | setAllowPreparation(boolean allowPreparation)
Sets whether the next call to BaseMediaSource.prepareSource(MediaSource.MediaSourceCaller, TransferListener, PlayerId) is allowed to finish. |
public void | setNewSourceInfo(Timeline newTimeline)
Sets a new timeline. |
public synchronized void | setNewSourceInfo(Timeline newTimeline, boolean sendManifestLoadEvents)
Sets a new timeline. |
from BaseMediaSource | addDrmEventListener, addEventListener, createDrmEventDispatcher, createDrmEventDispatcher, createEventDispatcher, createEventDispatcher, createEventDispatcher, disable, disableInternal, enable, enableInternal, getPlayerId, isEnabled, prepareSource, refreshSourceInfo, releaseSource, removeDrmEventListener, removeEventListener |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Fields
public static final
MediaItem FAKE_MEDIA_ITEMThe media item used by the fake media source.
Constructors
Creates a FakeMediaSource with a default FakeTimeline.
Creates a FakeMediaSource. This media source creates FakeMediaPeriods with a
TrackGroupArray using the given Formats. The provided Timeline may be
null to prevent an immediate source info refresh message when preparing the media source. It
can be manually set later using FakeMediaSource.setNewSourceInfo(Timeline).
Creates a FakeMediaSource. This media source creates FakeMediaPeriods with a
TrackGroupArray using the given Formats. It passes drmSessionManager
into the created periods. The provided Timeline may be null to prevent an immediate
source info refresh message when preparing the media source. It can be manually set later using
FakeMediaSource.setNewSourceInfo(Timeline).
Creates a FakeMediaSource. This media source creates FakeMediaPeriods with a
TrackGroupArray using the given Formats. It passes drmSessionManager
and trackDataFactory into the created periods. The provided Timeline may be
null to prevent an immediate source info refresh message when preparing the media source. It
can be manually set later using FakeMediaSource.setNewSourceInfo(Timeline).
Creates a FakeMediaSource. This media source creates FakeMediaPeriods with the
provided TrackGroupArray, DrmSessionManager and FakeMediaPeriod.TrackDataFactory. The provided Timeline may be null to prevent an
immediate source info refresh message when preparing the media source. It can be manually set
later using FakeMediaSource.setNewSourceInfo(Timeline).
Methods
public static
FakeMediaSource createWithWindowId(java.lang.Object windowId)
Convenience method to create a FakeMediaSource with the given window id.
public synchronized void
setAllowPreparation(boolean allowPreparation)
Sets whether the next call to BaseMediaSource.prepareSource(MediaSource.MediaSourceCaller, TransferListener, PlayerId) is allowed to finish. If not allowed, a
later call to this method with allowPreparation set to true will finish the
preparation.
Parameters:
allowPreparation: Whether preparation is allowed to finish.
public boolean
isSingleWindow()
protected abstract void
prepareSourceInternal(
TransferListener mediaTransferListener)
Starts source preparation and enables the source, see MediaSource.prepareSource(MediaSource.MediaSourceCaller, TransferListener, PlayerId). This method is called at most once until the next call to BaseMediaSource.releaseSourceInternal().
Parameters:
mediaTransferListener: The transfer listener which should be informed of any media data
transfers. May be null if no listener is available. Note that this listener should usually
be only informed of transfers related to the media loads and not of auxiliary loads for
manifests and other data.
public void
maybeThrowSourceInfoRefreshError()
protected abstract void
releaseSourceInternal()
Releases the source, see MediaSource.releaseSource(MediaSource.MediaSourceCaller). This method is called
exactly once after each call to BaseMediaSource.prepareSourceInternal(TransferListener).
public void
setNewSourceInfo(
Timeline newTimeline)
Sets a new timeline. If the source is already prepared, this triggers a source info refresh
message being sent to the listener.
Parameters:
newTimeline: The new Timeline.
public synchronized void
setNewSourceInfo(
Timeline newTimeline, boolean sendManifestLoadEvents)
Sets a new timeline. If the source is already prepared, this triggers a source info refresh
message being sent to the listener.
Must only be called if preparation is allowed.
Parameters:
newTimeline: The new Timeline.
sendManifestLoadEvents: Whether to treat this as a manifest refresh and send manifest
load events to listeners.
public boolean
isPrepared()
Returns whether the source is currently prepared.
public void
assertReleased()
Assert that the source and all periods have been released.
Assert that a media period for the given id has been created.
public java.util.List<MediaSource.MediaPeriodId>
getCreatedMediaPeriods()
Returns a list of s, with one element for each created media period.
Creates a MediaPeriod for this media source.
Parameters:
id: The identifier of the period.
trackGroupArray: The TrackGroupArray supported by the media period.
allocator: An Allocator from which to obtain media buffer allocations.
mediaSourceEventDispatcher: An to
dispatch media source events.
drmEventDispatcher: An to dispatch DRM
events.
transferListener: The transfer listener which should be informed of any data transfers.
May be null if no listener is available.
Returns:
A new FakeMediaPeriod.
protected void
releaseMediaPeriod(
MediaPeriod mediaPeriod)
Releases a media period created by FakeMediaSource.
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 static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.castNonNull;
import static com.google.common.truth.Truth.assertThat;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Period;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.TransferListener;
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
import androidx.media3.exoplayer.drm.DrmSessionManager;
import androidx.media3.exoplayer.source.BaseMediaSource;
import androidx.media3.exoplayer.source.ForwardingTimeline;
import androidx.media3.exoplayer.source.LoadEventInfo;
import androidx.media3.exoplayer.source.MediaLoadData;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.MediaSourceEventListener;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.test.utils.FakeMediaPeriod.TrackDataFactory;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Fake {@link MediaSource} that provides a given timeline. Creating the period will return a {@link
* FakeMediaPeriod} with a {@link TrackGroupArray} using the given {@link Format}s.
*/
@UnstableApi
public class FakeMediaSource extends BaseMediaSource {
/** A forwarding timeline to provide an initial timeline for fake multi window sources. */
public static class InitialTimeline extends ForwardingTimeline {
public InitialTimeline(Timeline timeline) {
super(timeline);
}
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
Window childWindow = timeline.getWindow(windowIndex, window, defaultPositionProjectionUs);
childWindow.isDynamic = true;
childWindow.isSeekable = false;
return childWindow;
}
}
/** Convenience method to create a {@link FakeMediaSource} with the given window id. */
public static FakeMediaSource createWithWindowId(Object windowId) {
return new FakeMediaSource(
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(/* periodCount= */ 1, windowId)));
}
/** The media item used by the fake media source. */
public static final MediaItem FAKE_MEDIA_ITEM =
new MediaItem.Builder().setMediaId("FakeMediaSource").setUri("http://manifest.uri").build();
private static final DataSpec FAKE_DATA_SPEC =
new DataSpec(castNonNull(FAKE_MEDIA_ITEM.localConfiguration).uri);
private static final int MANIFEST_LOAD_BYTES = 100;
private final TrackGroupArray trackGroupArray;
@Nullable private final FakeMediaPeriod.TrackDataFactory trackDataFactory;
private final ArrayList<MediaPeriod> activeMediaPeriods;
private final ArrayList<MediaPeriodId> createdMediaPeriods;
private final DrmSessionManager drmSessionManager;
private boolean preparationAllowed;
private @MonotonicNonNull Timeline timeline;
private boolean preparedSource;
private boolean releasedSource;
@Nullable private Handler sourceInfoRefreshHandler;
@Nullable private TransferListener transferListener;
/** Creates a {@link FakeMediaSource} with a default {@link FakeTimeline}. */
public FakeMediaSource() {
this(new FakeTimeline());
}
/**
* Creates a {@link FakeMediaSource}. This media source creates {@link FakeMediaPeriod}s with a
* {@link TrackGroupArray} using the given {@link Format}s. The provided {@link Timeline} may be
* null to prevent an immediate source info refresh message when preparing the media source. It
* can be manually set later using {@link #setNewSourceInfo(Timeline)}.
*/
public FakeMediaSource(@Nullable Timeline timeline, Format... formats) {
this(timeline, DrmSessionManager.DRM_UNSUPPORTED, formats);
}
/**
* Creates a {@link FakeMediaSource}. This media source creates {@link FakeMediaPeriod}s with a
* {@link TrackGroupArray} using the given {@link Format}s. It passes {@code drmSessionManager}
* into the created periods. The provided {@link Timeline} may be null to prevent an immediate
* source info refresh message when preparing the media source. It can be manually set later using
* {@link #setNewSourceInfo(Timeline)}.
*/
public FakeMediaSource(
@Nullable Timeline timeline, DrmSessionManager drmSessionManager, Format... formats) {
this(timeline, drmSessionManager, /* trackDataFactory= */ null, buildTrackGroupArray(formats));
}
/**
* Creates a {@link FakeMediaSource}. This media source creates {@link FakeMediaPeriod}s with a
* {@link TrackGroupArray} using the given {@link Format}s. It passes {@code drmSessionManager}
* and {@code trackDataFactory} into the created periods. The provided {@link Timeline} may be
* null to prevent an immediate source info refresh message when preparing the media source. It
* can be manually set later using {@link #setNewSourceInfo(Timeline)}.
*/
public FakeMediaSource(
@Nullable Timeline timeline,
DrmSessionManager drmSessionManager,
@Nullable FakeMediaPeriod.TrackDataFactory trackDataFactory,
Format... formats) {
this(timeline, drmSessionManager, trackDataFactory, buildTrackGroupArray(formats));
}
/**
* Creates a {@link FakeMediaSource}. This media source creates {@link FakeMediaPeriod}s with the
* provided {@link TrackGroupArray}, {@link DrmSessionManager} and {@link
* FakeMediaPeriod.TrackDataFactory}. The provided {@link Timeline} may be null to prevent an
* immediate source info refresh message when preparing the media source. It can be manually set
* later using {@link #setNewSourceInfo(Timeline)}.
*/
public FakeMediaSource(
@Nullable Timeline timeline,
DrmSessionManager drmSessionManager,
@Nullable FakeMediaPeriod.TrackDataFactory trackDataFactory,
TrackGroupArray trackGroupArray) {
if (timeline != null) {
this.timeline = timeline;
}
this.trackGroupArray = trackGroupArray;
this.activeMediaPeriods = new ArrayList<>();
this.createdMediaPeriods = new ArrayList<>();
this.drmSessionManager = drmSessionManager;
this.trackDataFactory = trackDataFactory;
preparationAllowed = true;
}
/**
* Sets whether the next call to {@link #prepareSource} is allowed to finish. If not allowed, a
* later call to this method with {@code allowPreparation} set to true will finish the
* preparation.
*
* @param allowPreparation Whether preparation is allowed to finish.
*/
public synchronized void setAllowPreparation(boolean allowPreparation) {
preparationAllowed = allowPreparation;
if (allowPreparation && sourceInfoRefreshHandler != null) {
sourceInfoRefreshHandler.post(
() -> finishSourcePreparation(/* sendManifestLoadEvents= */ true));
}
}
@Nullable
protected Timeline getTimeline() {
return timeline;
}
@Override
public MediaItem getMediaItem() {
if (timeline == null || timeline.isEmpty()) {
return FAKE_MEDIA_ITEM;
}
return timeline.getWindow(0, new Timeline.Window()).mediaItem;
}
@Override
@Nullable
public Timeline getInitialTimeline() {
return timeline == null || timeline.isEmpty() || timeline.getWindowCount() == 1
? null
: new InitialTimeline(timeline);
}
@Override
public boolean isSingleWindow() {
return timeline == null || timeline.isEmpty() || timeline.getWindowCount() == 1;
}
@Override
public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
assertThat(preparedSource).isFalse();
transferListener = mediaTransferListener;
drmSessionManager.prepare();
drmSessionManager.setPlayer(
/* playbackLooper= */ checkNotNull(Looper.myLooper()), getPlayerId());
preparedSource = true;
releasedSource = false;
sourceInfoRefreshHandler = Util.createHandlerForCurrentLooper();
if (preparationAllowed && timeline != null) {
finishSourcePreparation(/* sendManifestLoadEvents= */ true);
}
}
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
assertThat(preparedSource).isTrue();
}
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
assertThat(preparedSource).isTrue();
assertThat(releasedSource).isFalse();
int periodIndex = castNonNull(timeline).getIndexOfPeriod(id.periodUid);
Assertions.checkArgument(periodIndex != C.INDEX_UNSET);
Period period = timeline.getPeriod(periodIndex, new Period());
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher =
createEventDispatcher(period.windowIndex, id, period.getPositionInWindowMs());
DrmSessionEventListener.EventDispatcher drmEventDispatcher =
createDrmEventDispatcher(period.windowIndex, id);
MediaPeriod mediaPeriod =
createMediaPeriod(
id,
trackGroupArray,
allocator,
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
transferListener);
activeMediaPeriods.add(mediaPeriod);
createdMediaPeriods.add(id);
return mediaPeriod;
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
assertThat(preparedSource).isTrue();
assertThat(releasedSource).isFalse();
assertThat(activeMediaPeriods.remove(mediaPeriod)).isTrue();
releaseMediaPeriod(mediaPeriod);
}
@Override
protected void releaseSourceInternal() {
assertThat(preparedSource).isTrue();
assertThat(releasedSource).isFalse();
assertThat(activeMediaPeriods.isEmpty()).isTrue();
drmSessionManager.release();
releasedSource = true;
preparedSource = false;
castNonNull(sourceInfoRefreshHandler).removeCallbacksAndMessages(null);
sourceInfoRefreshHandler = null;
}
/**
* Sets a new timeline. If the source is already prepared, this triggers a source info refresh
* message being sent to the listener.
*
* @param newTimeline The new {@link Timeline}.
*/
public void setNewSourceInfo(Timeline newTimeline) {
setNewSourceInfo(newTimeline, /* sendManifestLoadEvents= */ true);
}
/**
* Sets a new timeline. If the source is already prepared, this triggers a source info refresh
* message being sent to the listener.
*
* <p>Must only be called if preparation is {@link #setAllowPreparation(boolean) allowed}.
*
* @param newTimeline The new {@link Timeline}.
* @param sendManifestLoadEvents Whether to treat this as a manifest refresh and send manifest
* load events to listeners.
*/
public synchronized void setNewSourceInfo(Timeline newTimeline, boolean sendManifestLoadEvents) {
checkState(preparationAllowed);
if (sourceInfoRefreshHandler != null) {
sourceInfoRefreshHandler.post(
() -> {
assertThat(releasedSource).isFalse();
assertThat(preparedSource).isTrue();
timeline = newTimeline;
finishSourcePreparation(sendManifestLoadEvents);
});
} else {
timeline = newTimeline;
}
}
/** Returns whether the source is currently prepared. */
public boolean isPrepared() {
return preparedSource;
}
/** Assert that the source and all periods have been released. */
public void assertReleased() {
assertThat(releasedSource || !preparedSource).isTrue();
}
/** Assert that a media period for the given id has been created. */
public void assertMediaPeriodCreated(MediaPeriodId mediaPeriodId) {
assertThat(createdMediaPeriods).contains(mediaPeriodId);
}
/** Returns a list of {@link MediaPeriodId}s, with one element for each created media period. */
public List<MediaPeriodId> getCreatedMediaPeriods() {
return createdMediaPeriods;
}
/**
* Creates a {@link MediaPeriod} for this media source.
*
* @param id The identifier of the period.
* @param trackGroupArray The {@link TrackGroupArray} supported by the media period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param mediaSourceEventDispatcher An {@link MediaSourceEventListener.EventDispatcher} to
* dispatch media source events.
* @param drmEventDispatcher An {@link MediaSourceEventListener.EventDispatcher} to dispatch DRM
* events.
* @param transferListener The transfer listener which should be informed of any data transfers.
* May be null if no listener is available.
* @return A new {@link FakeMediaPeriod}.
*/
@RequiresNonNull("this.timeline")
protected MediaPeriod createMediaPeriod(
MediaPeriodId id,
TrackGroupArray trackGroupArray,
Allocator allocator,
MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher,
DrmSessionManager drmSessionManager,
DrmSessionEventListener.EventDispatcher drmEventDispatcher,
@Nullable TransferListener transferListener) {
long positionInWindowUs =
timeline.getPeriodByUid(id.periodUid, new Period()).getPositionInWindowUs();
long defaultFirstSampleTimeUs = positionInWindowUs >= 0 || id.isAd() ? 0 : -positionInWindowUs;
return new FakeMediaPeriod(
trackGroupArray,
allocator,
trackDataFactory != null
? trackDataFactory
: TrackDataFactory.singleSampleWithTimeUs(defaultFirstSampleTimeUs),
mediaSourceEventDispatcher,
drmSessionManager,
drmEventDispatcher,
/* deferOnPrepared= */ false);
}
/**
* Releases a media period created by {@link #createMediaPeriod(MediaPeriodId, TrackGroupArray,
* Allocator, MediaSourceEventListener.EventDispatcher, DrmSessionManager,
* DrmSessionEventListener.EventDispatcher, TransferListener)}.
*/
protected void releaseMediaPeriod(MediaPeriod mediaPeriod) {
((FakeMediaPeriod) mediaPeriod).release();
}
private void finishSourcePreparation(boolean sendManifestLoadEvents) {
refreshSourceInfo(Assertions.checkStateNotNull(timeline));
if (!timeline.isEmpty() && sendManifestLoadEvents) {
MediaLoadData mediaLoadData =
new MediaLoadData(
C.DATA_TYPE_MANIFEST,
C.TRACK_TYPE_UNKNOWN,
/* trackFormat= */ null,
C.SELECTION_REASON_UNKNOWN,
/* trackSelectionData= */ null,
/* mediaStartTimeMs= */ C.TIME_UNSET,
/* mediaEndTimeMs = */ C.TIME_UNSET);
long elapsedRealTimeMs = SystemClock.elapsedRealtime();
MediaSourceEventListener.EventDispatcher eventDispatcher =
createEventDispatcher(/* mediaPeriodId= */ null);
long loadTaskId = LoadEventInfo.getNewId();
eventDispatcher.loadStarted(
new LoadEventInfo(
loadTaskId,
FAKE_DATA_SPEC,
FAKE_DATA_SPEC.uri,
/* responseHeaders= */ ImmutableMap.of(),
elapsedRealTimeMs,
/* loadDurationMs= */ 0,
/* bytesLoaded= */ 0),
mediaLoadData);
eventDispatcher.loadCompleted(
new LoadEventInfo(
loadTaskId,
FAKE_DATA_SPEC,
FAKE_DATA_SPEC.uri,
/* responseHeaders= */ ImmutableMap.of(),
elapsedRealTimeMs,
/* loadDurationMs= */ 0,
/* bytesLoaded= */ MANIFEST_LOAD_BYTES),
mediaLoadData);
}
}
private static TrackGroupArray buildTrackGroupArray(Format... formats) {
TrackGroup[] trackGroups = new TrackGroup[formats.length];
for (int i = 0; i < formats.length; i++) {
trackGroups[i] = new TrackGroup(formats[i]);
}
return new TrackGroupArray(trackGroups);
}
}