Update ExoPlayer version to ToT am: 5196d1b109 am: a526f72309
Change-Id: I193d24c7fa83448a1d54662e862f839fc89f65a3
diff --git a/METADATA b/METADATA
index 998c606..62bb7a0 100644
--- a/METADATA
+++ b/METADATA
@@ -16,7 +16,7 @@
type: GIT
value: "https://github.com/google/ExoPlayer.git"
}
- version: "d33c5ac0b3248a94211d844696652742854f96c0"
- last_upgrade_date { year: 2020 month: 4 day: 9 }
+ version: "abadc768725929df0f4eb1ef0aacf53893e45d6d"
+ last_upgrade_date { year: 2020 month: 4 day: 19 }
license_type: NOTICE
}
\ No newline at end of file
diff --git a/tree/RELEASENOTES.md b/tree/RELEASENOTES.md
index 4c2284d..9daedd6 100644
--- a/tree/RELEASENOTES.md
+++ b/tree/RELEASENOTES.md
@@ -65,6 +65,11 @@
* Remove deprecated members in `DefaultTrackSelector`.
* Add `Player.DeviceComponent` and implement it for `SimpleExoPlayer` so
that the device volume can be controlled by player.
+ * Avoid throwing an exception while parsing fragmented MP4 default sample
+ values where the most-significant bit is set
+ ([#7207](https://github.com/google/ExoPlayer/issues/7207)).
+ * Add `SilenceMediaSource.Factory` to support tags
+ ([PR #7245](https://github.com/google/ExoPlayer/pull/7245)).
* Text:
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
later).
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
index be092e8..2b79071 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/DownloadTracker.java
@@ -24,6 +24,7 @@
import androidx.fragment.app.FragmentManager;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RenderersFactory;
+import com.google.android.exoplayer2.demo.Sample.UriSample;
import com.google.android.exoplayer2.offline.Download;
import com.google.android.exoplayer2.offline.DownloadCursor;
import com.google.android.exoplayer2.offline.DownloadHelper;
@@ -92,12 +93,8 @@
}
public void toggleDownload(
- FragmentManager fragmentManager,
- String name,
- Uri uri,
- String extension,
- RenderersFactory renderersFactory) {
- Download download = downloads.get(uri);
+ FragmentManager fragmentManager, UriSample sample, RenderersFactory renderersFactory) {
+ Download download = downloads.get(sample.uri);
if (download != null) {
DownloadService.sendRemoveDownload(
context, DemoDownloadService.class, download.request.id, /* foreground= */ false);
@@ -107,7 +104,9 @@
}
startDownloadDialogHelper =
new StartDownloadDialogHelper(
- fragmentManager, getDownloadHelper(uri, extension, renderersFactory), name);
+ fragmentManager,
+ getDownloadHelper(sample.uri, sample.extension, renderersFactory),
+ sample);
}
}
@@ -167,16 +166,16 @@
private final FragmentManager fragmentManager;
private final DownloadHelper downloadHelper;
- private final String name;
+ private final UriSample sample;
private TrackSelectionDialog trackSelectionDialog;
private MappedTrackInfo mappedTrackInfo;
public StartDownloadDialogHelper(
- FragmentManager fragmentManager, DownloadHelper downloadHelper, String name) {
+ FragmentManager fragmentManager, DownloadHelper downloadHelper, UriSample sample) {
this.fragmentManager = fragmentManager;
this.downloadHelper = downloadHelper;
- this.name = name;
+ this.sample = sample;
downloadHelper.prepare(this);
}
@@ -271,7 +270,7 @@
}
private DownloadRequest buildDownloadRequest() {
- return downloadHelper.getDownloadRequest(Util.getUtf8Bytes(name));
+ return downloadHelper.getDownloadRequest(Util.getUtf8Bytes(sample.name));
}
}
}
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
index 44d471b..f941234 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java
@@ -42,7 +42,6 @@
import com.google.android.exoplayer2.demo.Sample.UriSample;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
-import com.google.android.exoplayer2.offline.DownloadHelper;
import com.google.android.exoplayer2.offline.DownloadRequest;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
@@ -473,7 +472,12 @@
.getDownloadTracker()
.getDownloadRequest(mediaItem.playbackProperties.sourceUri);
if (downloadRequest != null) {
- return DownloadHelper.createMediaSource(downloadRequest, dataSourceFactory);
+ mediaItem =
+ mediaItem
+ .buildUpon()
+ .setStreamKeys(downloadRequest.streamKeys)
+ .setCustomCacheKey(downloadRequest.customCacheKey)
+ .build();
}
return mediaSourceFactory
.setDrmHttpDataSourceFactory(drmDataSourceFactory)
diff --git a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
index ebfaa76..740f016 100644
--- a/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
+++ b/tree/demos/main/src/main/java/com/google/android/exoplayer2/demo/SampleChooserActivity.java
@@ -249,16 +249,11 @@
Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG)
.show();
} else {
- UriSample uriSample = (UriSample) sample;
RenderersFactory renderersFactory =
((DemoApplication) getApplication())
.buildRenderersFactory(isNonNullAndChecked(preferExtensionDecodersMenuItem));
downloadTracker.toggleDownload(
- getSupportFragmentManager(),
- sample.name,
- uriSample.uri,
- uriSample.extension,
- renderersFactory);
+ getSupportFragmentManager(), (UriSample) sample, renderersFactory);
}
}
diff --git a/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java b/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
index 7d549be..2c25c32 100644
--- a/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
+++ b/tree/extensions/cronet/src/main/java/com/google/android/exoplayer2/ext/cronet/CronetEngineWrapper.java
@@ -166,7 +166,8 @@
private final boolean preferGMSCoreCronet;
// Multi-catch can only be used for API 19+ in this case.
- @SuppressWarnings("UseMultiCatch")
+ // incompatible types in argument.
+ @SuppressWarnings({"UseMultiCatch", "nullness:argument.type.incompatible"})
public CronetProviderComparator(boolean preferGMSCoreCronet) {
// GMSCore CronetProvider classes are only available in some configurations.
// Thus, we use reflection to copy static name.
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java
index 780d825..9f4e8be 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/C.java
@@ -998,7 +998,8 @@
* #ROLE_FLAG_DUB}, {@link #ROLE_FLAG_EMERGENCY}, {@link #ROLE_FLAG_CAPTION}, {@link
* #ROLE_FLAG_SUBTITLE}, {@link #ROLE_FLAG_SIGN}, {@link #ROLE_FLAG_DESCRIBES_VIDEO}, {@link
* #ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND}, {@link #ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY},
- * {@link #ROLE_FLAG_TRANSCRIBES_DIALOG} and {@link #ROLE_FLAG_EASY_TO_READ}.
+ * {@link #ROLE_FLAG_TRANSCRIBES_DIALOG}, {@link #ROLE_FLAG_EASY_TO_READ} and {@link
+ * #ROLE_FLAG_TRICK_PLAY}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@@ -1018,7 +1019,8 @@
ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND,
ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY,
ROLE_FLAG_TRANSCRIBES_DIALOG,
- ROLE_FLAG_EASY_TO_READ
+ ROLE_FLAG_EASY_TO_READ,
+ ROLE_FLAG_TRICK_PLAY
})
public @interface RoleFlags {}
/** Indicates a main track. */
@@ -1064,6 +1066,8 @@
public static final int ROLE_FLAG_TRANSCRIBES_DIALOG = 1 << 12;
/** Indicates the track contains a text that has been edited for ease of reading. */
public static final int ROLE_FLAG_EASY_TO_READ = 1 << 13;
+ /** Indicates the track is intended for trick play. */
+ public static final int ROLE_FLAG_TRICK_PLAY = 1 << 14;
/**
* Converts a time in microseconds to the corresponding time in milliseconds, preserving
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
index a78a2d0..f484d3f 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java
@@ -69,17 +69,47 @@
private boolean drmPlayClearContentWithoutKey;
private List<Integer> drmSessionForClearTypes;
private List<StreamKey> streamKeys;
+ @Nullable private String customCacheKey;
private List<Subtitle> subtitles;
@Nullable private Object tag;
@Nullable private MediaMetadata mediaMetadata;
/** Creates a builder. */
public Builder() {
- streamKeys = Collections.emptyList();
- subtitles = Collections.emptyList();
+ clipEndPositionMs = C.TIME_END_OF_SOURCE;
drmSessionForClearTypes = Collections.emptyList();
drmLicenseRequestHeaders = Collections.emptyMap();
- clipEndPositionMs = C.TIME_END_OF_SOURCE;
+ streamKeys = Collections.emptyList();
+ subtitles = Collections.emptyList();
+ }
+
+ private Builder(MediaItem mediaItem) {
+ this();
+ clipEndPositionMs = mediaItem.clippingProperties.endPositionMs;
+ clipRelativeToLiveWindow = mediaItem.clippingProperties.relativeToLiveWindow;
+ clipRelativeToDefaultPosition = mediaItem.clippingProperties.relativeToDefaultPosition;
+ clipStartsAtKeyFrame = mediaItem.clippingProperties.startsAtKeyFrame;
+ clipStartPositionMs = mediaItem.clippingProperties.startPositionMs;
+ mediaId = mediaItem.mediaId;
+ mediaMetadata = mediaItem.mediaMetadata;
+ @Nullable PlaybackProperties playbackProperties = mediaItem.playbackProperties;
+ if (playbackProperties != null) {
+ customCacheKey = playbackProperties.customCacheKey;
+ mimeType = playbackProperties.mimeType;
+ sourceUri = playbackProperties.sourceUri;
+ streamKeys = playbackProperties.streamKeys;
+ subtitles = playbackProperties.subtitles;
+ tag = playbackProperties.tag;
+ @Nullable DrmConfiguration drmConfiguration = playbackProperties.drmConfiguration;
+ if (drmConfiguration != null) {
+ drmLicenseUri = drmConfiguration.licenseUri;
+ drmLicenseRequestHeaders = drmConfiguration.requestHeaders;
+ drmMultiSession = drmConfiguration.multiSession;
+ drmPlayClearContentWithoutKey = drmConfiguration.playClearContentWithoutKey;
+ drmSessionForClearTypes = drmConfiguration.sessionForClearTypes;
+ drmUuid = drmConfiguration.uuid;
+ }
+ }
}
/**
@@ -297,6 +327,17 @@
}
/**
+ * Sets the optional custom cache key (only used for progressive streams).
+ *
+ * <p>If a {@link PlaybackProperties#sourceUri} is set, the custom cache key is used to create a
+ * {@link PlaybackProperties} object. Otherwise it will be ignored.
+ */
+ public Builder setCustomCacheKey(@Nullable String customCacheKey) {
+ this.customCacheKey = customCacheKey;
+ return this;
+ }
+
+ /**
* Sets the optional subtitles.
*
* <p>{@code null} or an empty {@link List} can be used for a reset.
@@ -352,6 +393,7 @@
drmSessionForClearTypes)
: null,
streamKeys,
+ customCacheKey,
subtitles,
tag);
mediaId = mediaId != null ? mediaId : sourceUri.toString();
@@ -461,6 +503,9 @@
/** Optional stream keys by which the manifest is filtered. */
public final List<StreamKey> streamKeys;
+ /** Optional custom cache key (only used for progressive streams). */
+ @Nullable public final String customCacheKey;
+
/** Optional subtitles to be sideloaded. */
public final List<Subtitle> subtitles;
@@ -476,12 +521,14 @@
@Nullable String mimeType,
@Nullable DrmConfiguration drmConfiguration,
List<StreamKey> streamKeys,
+ @Nullable String customCacheKey,
List<Subtitle> subtitles,
@Nullable Object tag) {
this.sourceUri = sourceUri;
this.mimeType = mimeType;
this.drmConfiguration = drmConfiguration;
this.streamKeys = streamKeys;
+ this.customCacheKey = customCacheKey;
this.subtitles = subtitles;
this.tag = tag;
}
@@ -500,6 +547,7 @@
&& Util.areEqual(mimeType, other.mimeType)
&& Util.areEqual(drmConfiguration, other.drmConfiguration)
&& streamKeys.equals(other.streamKeys)
+ && Util.areEqual(customCacheKey, other.customCacheKey)
&& subtitles.equals(other.subtitles)
&& Util.areEqual(tag, other.tag);
}
@@ -510,6 +558,7 @@
result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode());
result = 31 * result + (drmConfiguration == null ? 0 : drmConfiguration.hashCode());
result = 31 * result + streamKeys.hashCode();
+ result = 31 * result + (customCacheKey == null ? 0 : customCacheKey.hashCode());
result = 31 * result + subtitles.hashCode();
result = 31 * result + (tag == null ? 0 : tag.hashCode());
return result;
@@ -674,6 +723,11 @@
this.clippingProperties = clippingProperties;
}
+ /** Returns a {@link Builder} initialized with the values of this instance. */
+ public Builder buildUpon() {
+ return new Builder(this);
+ }
+
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
diff --git a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/UnknownNull.java b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/UnknownNull.java
index 8ca2e8f..0ccad43 100644
--- a/tree/library/common/src/main/java/com/google/android/exoplayer2/util/UnknownNull.java
+++ b/tree/library/common/src/main/java/com/google/android/exoplayer2/util/UnknownNull.java
@@ -23,7 +23,7 @@
import javax.annotation.meta.When;
/**
- * Annotation for specifiying unknown nullness. Useful for clearing the effects of an automatically
+ * Annotation for specifying unknown nullness. Useful for clearing the effects of an automatically
* propagated {@link Nonnull} annotation.
*/
@Nonnull(when = When.UNKNOWN)
diff --git a/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
index bb47011..adfbc60 100644
--- a/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
+++ b/tree/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java
@@ -142,6 +142,14 @@
}
@Test
+ public void builderSetCustomCacheKey_setsCustomCacheKey() {
+ MediaItem mediaItem =
+ new MediaItem.Builder().setSourceUri(URI_STRING).setCustomCacheKey("key").build();
+
+ assertThat(mediaItem.playbackProperties.customCacheKey).isEqualTo("key");
+ }
+
+ @Test
public void builderSetStreamKeys_setsStreamKeys() {
List<StreamKey> streamKeys = new ArrayList<>();
streamKeys.add(new StreamKey(1, 0, 0));
@@ -267,4 +275,40 @@
assertThat(mediaItem.mediaMetadata).isEqualTo(mediaMetadata);
}
+
+ @Test
+ public void buildUpon_equalsToOriginal() {
+ MediaItem mediaItem =
+ new MediaItem.Builder()
+ .setClipEndPositionMs(1000)
+ .setClipRelativeToDefaultPosition(true)
+ .setClipRelativeToLiveWindow(true)
+ .setClipStartPositionMs(100)
+ .setClipStartsAtKeyFrame(true)
+ .setCustomCacheKey("key")
+ .setDrmUuid(C.WIDEVINE_UUID)
+ .setDrmLicenseUri(URI_STRING + "/license")
+ .setDrmLicenseRequestHeaders(
+ Collections.singletonMap("Referer", "http://www.google.com"))
+ .setDrmMultiSession(true)
+ .setDrmPlayClearContentWithoutKey(true)
+ .setDrmSessionForClearTypes(Collections.singletonList(C.TRACK_TYPE_AUDIO))
+ .setMediaId("mediaId")
+ .setMediaMetadata(new MediaMetadata.Builder().setTitle("title").build())
+ .setMimeType(MimeTypes.APPLICATION_MP4)
+ .setSourceUri(URI_STRING)
+ .setStreamKeys(Collections.singletonList(new StreamKey(1, 0, 0)))
+ .setSubtitles(
+ Collections.singletonList(
+ new MediaItem.Subtitle(
+ Uri.parse(URI_STRING + "/en"),
+ MimeTypes.APPLICATION_TTML,
+ /* language= */ "en")))
+ .setTag(new Object())
+ .build();
+
+ MediaItem copy = mediaItem.buildUpon().build();
+
+ assertThat(copy).isEqualTo(mediaItem);
+ }
}
diff --git a/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/StreamVolumeManagerTest.java b/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/StreamVolumeManagerTest.java
index 220f331..792492d 100644
--- a/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/StreamVolumeManagerTest.java
+++ b/tree/library/core/src/androidTest/java/com/google/android/exoplayer2/StreamVolumeManagerTest.java
@@ -198,6 +198,30 @@
}
@Test
+ public void setVolumeMuted_changesMuteState() {
+ testThread.runOnMainThread(
+ () -> {
+ int minVolume = streamVolumeManager.getMinVolume();
+ int maxVolume = streamVolumeManager.getMaxVolume();
+ if (minVolume == maxVolume || minVolume > 0) {
+ return;
+ }
+
+ streamVolumeManager.setVolume(maxVolume);
+ assertThat(streamVolumeManager.isMuted()).isFalse();
+
+ streamVolumeManager.setMuted(true);
+ assertThat(streamVolumeManager.isMuted()).isTrue();
+ assertThat(testListener.lastStreamVolumeMuted).isTrue();
+
+ streamVolumeManager.setMuted(false);
+ assertThat(streamVolumeManager.isMuted()).isFalse();
+ assertThat(testListener.lastStreamVolumeMuted).isFalse();
+ assertThat(testListener.lastStreamVolume).isEqualTo(maxVolume);
+ });
+ }
+
+ @Test
public void setStreamType_notifiesStreamTypeAndVolume() {
testThread.runOnMainThread(
() -> {
@@ -250,6 +274,7 @@
@C.StreamType private int lastStreamType;
private int lastStreamVolume;
+ private boolean lastStreamVolumeMuted;
public final CountDownLatch onStreamVolumeChangedLatch;
public TestListener() {
@@ -262,8 +287,9 @@
}
@Override
- public void onStreamVolumeChanged(int streamVolume) {
+ public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) {
lastStreamVolume = streamVolume;
+ lastStreamVolumeMuted = streamMuted;
onStreamVolumeChangedLatch.countDown();
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
index 2242ebf..0344b09 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java
@@ -397,6 +397,9 @@
@Override
public void addMediaSources(int index, List<MediaSource> mediaSources) {
Assertions.checkArgument(index >= 0);
+ for (int i = 0; i < mediaSources.size(); i++) {
+ Assertions.checkArgument(mediaSources.get(i) != null);
+ }
int currentWindowIndex = getCurrentWindowIndex();
long currentPositionMs = getCurrentPosition();
Timeline oldTimeline = getCurrentTimeline();
@@ -966,10 +969,13 @@
}
private void setMediaSourcesInternal(
- List<MediaSource> mediaItems,
+ List<MediaSource> mediaSources,
int startWindowIndex,
long startPositionMs,
boolean resetToDefaultPosition) {
+ for (int i = 0; i < mediaSources.size(); i++) {
+ Assertions.checkArgument(mediaSources.get(i) != null);
+ }
int currentWindowIndex = getCurrentWindowIndexInternal();
long currentPositionMs = getCurrentPosition();
pendingOperationAcks++;
@@ -978,7 +984,7 @@
/* fromIndex= */ 0, /* toIndexExclusive= */ mediaSourceHolders.size());
}
List<MediaSourceList.MediaSourceHolder> holders =
- addMediaSourceHolders(/* index= */ 0, mediaItems);
+ addMediaSourceHolders(/* index= */ 0, mediaSources);
PlaybackInfo playbackInfo = maskTimeline();
Timeline timeline = playbackInfo.timeline;
if (!timeline.isEmpty() && startWindowIndex >= timeline.getWindowCount()) {
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java
index 518e331..f692629 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/Player.java
@@ -397,6 +397,9 @@
*/
int getDeviceVolume();
+ /** Gets whether the device is muted or not. */
+ boolean isDeviceMuted();
+
/**
* Sets the volume of the device.
*
@@ -409,6 +412,9 @@
/** Decreases the volume of the device. */
void decreaseDeviceVolume();
+
+ /** Sets the mute state of the device. */
+ void setDeviceMuted(boolean muted);
}
/**
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
index 5d98e59..67c4b88 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java
@@ -1776,6 +1776,12 @@
}
@Override
+ public boolean isDeviceMuted() {
+ verifyApplicationThread();
+ return streamVolumeManager.isMuted();
+ }
+
+ @Override
public void setDeviceVolume(int volume) {
verifyApplicationThread();
streamVolumeManager.setVolume(volume);
@@ -1793,6 +1799,12 @@
streamVolumeManager.decreaseVolume();
}
+ @Override
+ public void setDeviceMuted(boolean muted) {
+ verifyApplicationThread();
+ streamVolumeManager.setMuted(muted);
+ }
+
// Internal methods.
private void removeSurfaceCallbacks() {
@@ -2217,9 +2229,9 @@
}
@Override
- public void onStreamVolumeChanged(int streamVolume) {
+ public void onStreamVolumeChanged(int streamVolume, boolean streamMuted) {
for (DeviceListener deviceListener : deviceListeners) {
- deviceListener.onDeviceVolumeChanged(streamVolume);
+ deviceListener.onDeviceVolumeChanged(streamVolume, streamMuted);
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java
index 28f439d..59ab3f1 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/StreamVolumeManager.java
@@ -33,8 +33,8 @@
/** Called when the audio stream type is changed. */
void onStreamTypeChanged(@C.StreamType int streamType);
- /** Called when the audio stream volume is changed. */
- void onStreamVolumeChanged(int streamVolume);
+ /** Called when the audio stream volume or mute state is changed. */
+ void onStreamVolumeChanged(int streamVolume, boolean streamMuted);
}
// TODO(b/151280453): Replace the hidden intent action with an official one.
@@ -52,6 +52,7 @@
@C.StreamType private int streamType;
private int volume;
+ private boolean muted;
/** Creates a manager. */
public StreamVolumeManager(Context context, Handler eventHandler, Listener listener) {
@@ -63,7 +64,8 @@
(AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE));
streamType = C.STREAM_TYPE_DEFAULT;
- volume = audioManager.getStreamVolume(streamType);
+ volume = getVolumeFromManager(audioManager, streamType);
+ muted = getMutedFromManager(audioManager, streamType);
receiver = new VolumeChangeReceiver();
IntentFilter filter = new IntentFilter(VOLUME_CHANGED_ACTION);
@@ -102,6 +104,11 @@
return volume;
}
+ /** Gets whether the current audio stream is muted or not. */
+ public boolean isMuted() {
+ return muted;
+ }
+
/**
* Sets the volume with the given value for the current audio stream. The value should be between
* {@link #getMinVolume()} and {@link #getMaxVolume()}, otherwise it will be ignored.
@@ -138,16 +145,42 @@
updateVolumeAndNotifyIfChanged();
}
+ /** Sets the mute state of the current audio stream. */
+ public void setMuted(boolean muted) {
+ if (Util.SDK_INT >= 23) {
+ audioManager.adjustStreamVolume(
+ streamType, muted ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE, VOLUME_FLAGS);
+ } else {
+ audioManager.setStreamMute(streamType, muted);
+ }
+ updateVolumeAndNotifyIfChanged();
+ }
+
/** Releases the manager. It must be called when the manager is no longer required. */
public void release() {
applicationContext.unregisterReceiver(receiver);
}
private void updateVolumeAndNotifyIfChanged() {
- int newVolume = audioManager.getStreamVolume(streamType);
- if (volume != newVolume) {
+ int newVolume = getVolumeFromManager(audioManager, streamType);
+ boolean newMuted = getMutedFromManager(audioManager, streamType);
+ if (volume != newVolume || muted != newMuted) {
volume = newVolume;
- listener.onStreamVolumeChanged(newVolume);
+ muted = newMuted;
+ listener.onStreamVolumeChanged(newVolume, newMuted);
+ }
+ }
+
+ private static int getVolumeFromManager(AudioManager audioManager, @C.StreamType int streamType) {
+ return audioManager.getStreamVolume(streamType);
+ }
+
+ private static boolean getMutedFromManager(
+ AudioManager audioManager, @C.StreamType int streamType) {
+ if (Util.SDK_INT >= 23) {
+ return audioManager.isStreamMute(streamType);
+ } else {
+ return audioManager.getStreamVolume(streamType) == 0;
}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java
index cede01e..e9baef3 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/analytics/DefaultPlaybackSessionManager.java
@@ -24,6 +24,7 @@
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.exoplayer2.util.Supplier;
import com.google.android.exoplayer2.util.Util;
import java.util.HashMap;
import java.util.Iterator;
@@ -34,23 +35,41 @@
* Default {@link PlaybackSessionManager} which instantiates a new session for each window in the
* timeline and also for each ad within the windows.
*
- * <p>Sessions are identified by Base64-encoded, URL-safe, random strings.
+ * <p>By default, sessions are identified by Base64-encoded, URL-safe, random strings.
*/
public final class DefaultPlaybackSessionManager implements PlaybackSessionManager {
+ /** Default generator for unique session ids that are random, Based64-encoded and URL-safe. */
+ public static final Supplier<String> DEFAULT_SESSION_ID_GENERATOR =
+ DefaultPlaybackSessionManager::generateDefaultSessionId;
+
private static final Random RANDOM = new Random();
private static final int SESSION_ID_LENGTH = 12;
private final Timeline.Window window;
private final Timeline.Period period;
private final HashMap<String, SessionDescriptor> sessions;
+ private final Supplier<String> sessionIdGenerator;
private @MonotonicNonNull Listener listener;
private Timeline currentTimeline;
@Nullable private String currentSessionId;
- /** Creates session manager. */
+ /**
+ * Creates session manager with a {@link #DEFAULT_SESSION_ID_GENERATOR} to generate session ids.
+ */
public DefaultPlaybackSessionManager() {
+ this(DEFAULT_SESSION_ID_GENERATOR);
+ }
+
+ /**
+ * Creates session manager.
+ *
+ * @param sessionIdGenerator A generator for new session ids. All generated session ids must be
+ * unique.
+ */
+ public DefaultPlaybackSessionManager(Supplier<String> sessionIdGenerator) {
+ this.sessionIdGenerator = sessionIdGenerator;
window = new Timeline.Window();
period = new Timeline.Period();
sessions = new HashMap<>();
@@ -207,14 +226,14 @@
}
}
if (bestMatch == null) {
- String sessionId = generateSessionId();
+ String sessionId = sessionIdGenerator.get();
bestMatch = new SessionDescriptor(sessionId, windowIndex, mediaPeriodId);
sessions.put(sessionId, bestMatch);
}
return bestMatch;
}
- private static String generateSessionId() {
+ private static String generateDefaultSessionId() {
byte[] randomBytes = new byte[SESSION_ID_LENGTH];
RANDOM.nextBytes(randomBytes);
return Base64.encodeToString(randomBytes, Base64.URL_SAFE | Base64.NO_WRAP);
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java
index b6a063b..a9afa47 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java
@@ -80,6 +80,11 @@
}
@Override
+ protected void onFlush() {
+ flushSinkIfActive();
+ }
+
+ @Override
protected void onQueueEndOfStream() {
flushSinkIfActive();
}
@@ -201,7 +206,7 @@
}
private void reset() throws IOException {
- RandomAccessFile randomAccessFile = this.randomAccessFile;
+ @Nullable RandomAccessFile randomAccessFile = this.randomAccessFile;
if (randomAccessFile == null) {
return;
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java
index f310b6d..3d35c6a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/device/DeviceListener.java
@@ -23,6 +23,6 @@
/** Called when the device information changes. */
default void onDeviceInfoChanged(DeviceInfo deviceInfo) {}
- /** Called when the device volume changes. */
- default void onDeviceVolumeChanged(int volume) {}
+ /** Called when the device volume or mute state changes. */
+ default void onDeviceVolumeChanged(int volume, boolean muted) {}
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
index 4094f4b..736f941 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java
@@ -547,7 +547,9 @@
width = alignedSize.x;
height = alignedSize.y;
- if (frameRate == Format.NO_VALUE || frameRate <= 0) {
+ // VideoCapabilities.areSizeAndRateSupported incorrectly returns false if frameRate < 1 on some
+ // versions of Android, so we only check the size in this case [Internal ref: b/153940404].
+ if (frameRate == Format.NO_VALUE || frameRate < 1) {
return capabilities.isSizeSupported(width, height);
} else {
// The signaled frame rate may be slightly higher than the actual frame rate, so we take the
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java
index c2e0c47..2bba84a 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java
@@ -96,6 +96,8 @@
return Assertions.checkNotNull(trackGroups);
}
+ // unboxing a possibly-null reference streamPeriodIndices.get(streams[i])
+ @SuppressWarnings("nullness:unboxing.of.nullable")
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
index b2404db..ef9a965 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/ProgressiveMediaSource.java
@@ -95,13 +95,10 @@
}
/**
- * Sets the custom key that uniquely identifies the original stream. Used for cache indexing.
- * The default value is {@code null}.
- *
- * @param customCacheKey A custom key that uniquely identifies the original stream. Used for
- * cache indexing.
- * @return This factory, for convenience.
+ * @deprecated Use {@link MediaItem.Builder#setCustomCacheKey(String)} and {@link
+ * #createMediaSource(MediaItem)} instead.
*/
+ @Deprecated
public Factory setCustomCacheKey(@Nullable String customCacheKey) {
this.customCacheKey = customCacheKey;
return this;
@@ -188,7 +185,9 @@
extractorsFactory,
drmSessionManager,
loadErrorHandlingPolicy,
- customCacheKey,
+ mediaItem.playbackProperties.customCacheKey != null
+ ? mediaItem.playbackProperties.customCacheKey
+ : customCacheKey,
continueLoadingCheckIntervalBytes,
mediaItem.playbackProperties.tag != null ? mediaItem.playbackProperties.tag : tag);
}
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
index 839f683..f4fb376 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/source/SilenceMediaSource.java
@@ -33,6 +33,42 @@
/** Media source with a single period consisting of silent raw audio of a given duration. */
public final class SilenceMediaSource extends BaseMediaSource {
+ /** Factory for {@link SilenceMediaSource SilenceMediaSources}. */
+ public static final class Factory {
+
+ private long durationUs;
+ @Nullable private Object tag;
+
+ /**
+ * Sets the duration of the silent audio.
+ *
+ * @param durationUs The duration of silent audio to output, in microseconds.
+ * @return This factory, for convenience.
+ */
+ public Factory setDurationUs(long durationUs) {
+ this.durationUs = durationUs;
+ return this;
+ }
+
+ /**
+ * Sets a tag for the media source which will be published in the {@link
+ * com.google.android.exoplayer2.Timeline} of the source as {@link
+ * com.google.android.exoplayer2.Timeline.Window#tag}.
+ *
+ * @param tag A tag for the media source.
+ * @return This factory, for convenience.
+ */
+ public Factory setTag(@Nullable Object tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ /** Creates a new {@link SilenceMediaSource}. */
+ public SilenceMediaSource createMediaSource() {
+ return new SilenceMediaSource(durationUs, tag);
+ }
+ }
+
private static final int SAMPLE_RATE_HZ = 44100;
@C.PcmEncoding private static final int PCM_ENCODING = C.ENCODING_PCM_16BIT;
private static final int CHANNEL_COUNT = 2;
@@ -47,6 +83,7 @@
new byte[Util.getPcmFrameSize(PCM_ENCODING, CHANNEL_COUNT) * 1024];
private final long durationUs;
+ @Nullable private final Object tag;
/**
* Creates a new media source providing silent audio of the given duration.
@@ -54,15 +91,25 @@
* @param durationUs The duration of silent audio to output, in microseconds.
*/
public SilenceMediaSource(long durationUs) {
+ this(durationUs, /* tag= */ null);
+ }
+
+ private SilenceMediaSource(long durationUs, @Nullable Object tag) {
Assertions.checkArgument(durationUs >= 0);
this.durationUs = durationUs;
+ this.tag = tag;
}
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
refreshSourceInfo(
new SinglePeriodTimeline(
- durationUs, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false));
+ durationUs,
+ /* isSeekable= */ true,
+ /* isDynamic= */ false,
+ /* isLive= */ false,
+ /* manifest= */ null,
+ tag));
}
@Override
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
index 5596031..6682029 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java
@@ -1888,6 +1888,10 @@
int maxVideoHeight,
int maxVideoFrameRate,
int maxVideoBitrate) {
+ if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
+ // Ignore trick-play tracks for now.
+ return false;
+ }
return isSupported(formatSupport, false)
&& ((formatSupport & requiredAdaptiveSupport) != 0)
&& (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType))
@@ -1911,9 +1915,13 @@
params.viewportWidth, params.viewportHeight, params.viewportOrientationMayChange);
@Capabilities int[] trackFormatSupport = formatSupports[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
+ Format format = trackGroup.getFormat(trackIndex);
+ if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
+ // Ignore trick-play tracks for now.
+ continue;
+ }
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
- Format format = trackGroup.getFormat(trackIndex);
boolean isWithinConstraints =
selectedTrackIndices.contains(trackIndex)
&& (format.width == Format.NO_VALUE || format.width <= params.maxVideoWidth)
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java
index 7a87d7d..ffb8236 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/Clock.java
@@ -30,6 +30,13 @@
*/
Clock DEFAULT = new SystemClock();
+ /**
+ * Returns the current time in milliseconds since the Unix Epoch.
+ *
+ * @see System#currentTimeMillis()
+ */
+ long currentTimeMillis();
+
/** @see android.os.SystemClock#elapsedRealtime() */
long elapsedRealtime();
diff --git a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/SystemClock.java b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/SystemClock.java
index be52659..a094e81 100644
--- a/tree/library/core/src/main/java/com/google/android/exoplayer2/util/SystemClock.java
+++ b/tree/library/core/src/main/java/com/google/android/exoplayer2/util/SystemClock.java
@@ -26,6 +26,11 @@
/* package */ final class SystemClock implements Clock {
@Override
+ public long currentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ @Override
public long elapsedRealtime() {
return android.os.SystemClock.elapsedRealtime();
}
diff --git a/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/TeeAudioProcessorTest.java b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/TeeAudioProcessorTest.java
new file mode 100644
index 0000000..6f0a87e
--- /dev/null
+++ b/tree/library/core/src/test/java/com/google/android/exoplayer2/audio/TeeAudioProcessorTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2020 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 com.google.android.exoplayer2.audio;
+
+import static org.mockito.Mockito.verify;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.audio.AudioProcessor.AudioFormat;
+import com.google.android.exoplayer2.audio.TeeAudioProcessor.AudioBufferSink;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/** Unit tests for {@link TeeAudioProcessorTest}. */
+@RunWith(AndroidJUnit4.class)
+public final class TeeAudioProcessorTest {
+
+ private static final AudioFormat AUDIO_FORMAT =
+ new AudioFormat(/* sampleRate= */ 44100, /* channelCount= */ 2, C.ENCODING_PCM_16BIT);
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ private TeeAudioProcessor teeAudioProcessor;
+
+ @Mock private AudioBufferSink mockAudioBufferSink;
+
+ @Before
+ public void setUp() {
+ teeAudioProcessor = new TeeAudioProcessor(mockAudioBufferSink);
+ }
+
+ @Test
+ public void initialFlush_flushesSink() throws Exception {
+ teeAudioProcessor.configure(AUDIO_FORMAT);
+ teeAudioProcessor.flush();
+
+ verify(mockAudioBufferSink)
+ .flush(AUDIO_FORMAT.sampleRate, AUDIO_FORMAT.channelCount, AUDIO_FORMAT.encoding);
+ }
+}
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java
index d962374..b0689ee 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/AdaptationSet.java
@@ -50,9 +50,10 @@
*/
public final List<Descriptor> accessibilityDescriptors;
- /**
- * Supplemental properties in the adaptation set.
- */
+ /** Essential properties in the adaptation set. */
+ public final List<Descriptor> essentialProperties;
+
+ /** Supplemental properties in the adaptation set. */
public final List<Descriptor> supplementalProperties;
/**
@@ -62,21 +63,21 @@
* {@code TRACK_TYPE_*} constants.
* @param representations {@link Representation}s in the adaptation set.
* @param accessibilityDescriptors Accessibility descriptors in the adaptation set.
+ * @param essentialProperties Essential properties in the adaptation set.
* @param supplementalProperties Supplemental properties in the adaptation set.
*/
- public AdaptationSet(int id, int type, List<Representation> representations,
- List<Descriptor> accessibilityDescriptors, List<Descriptor> supplementalProperties) {
+ public AdaptationSet(
+ int id,
+ int type,
+ List<Representation> representations,
+ List<Descriptor> accessibilityDescriptors,
+ List<Descriptor> essentialProperties,
+ List<Descriptor> supplementalProperties) {
this.id = id;
this.type = type;
this.representations = Collections.unmodifiableList(representations);
- this.accessibilityDescriptors =
- accessibilityDescriptors == null
- ? Collections.emptyList()
- : Collections.unmodifiableList(accessibilityDescriptors);
- this.supplementalProperties =
- supplementalProperties == null
- ? Collections.emptyList()
- : Collections.unmodifiableList(supplementalProperties);
+ this.accessibilityDescriptors = Collections.unmodifiableList(accessibilityDescriptors);
+ this.essentialProperties = Collections.unmodifiableList(essentialProperties);
+ this.supplementalProperties = Collections.unmodifiableList(supplementalProperties);
}
-
}
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java
index 2d8909f..c21af45 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifest.java
@@ -224,9 +224,14 @@
key = keys.poll();
} while (key.periodIndex == periodIndex && key.groupIndex == adaptationSetIndex);
- copyAdaptationSets.add(new AdaptationSet(adaptationSet.id, adaptationSet.type,
- copyRepresentations, adaptationSet.accessibilityDescriptors,
- adaptationSet.supplementalProperties));
+ copyAdaptationSets.add(
+ new AdaptationSet(
+ adaptationSet.id,
+ adaptationSet.type,
+ copyRepresentations,
+ adaptationSet.accessibilityDescriptors,
+ adaptationSet.essentialProperties,
+ adaptationSet.supplementalProperties));
} while(key.periodIndex == periodIndex);
// Add back the last key which doesn't belong to the period being processed
keys.addFirst(key);
diff --git a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
index 1ceeb31..23f264e 100644
--- a/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
+++ b/tree/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java
@@ -289,6 +289,7 @@
ArrayList<Descriptor> inbandEventStreams = new ArrayList<>();
ArrayList<Descriptor> accessibilityDescriptors = new ArrayList<>();
ArrayList<Descriptor> roleDescriptors = new ArrayList<>();
+ ArrayList<Descriptor> essentialProperties = new ArrayList<>();
ArrayList<Descriptor> supplementalProperties = new ArrayList<>();
List<RepresentationInfo> representationInfos = new ArrayList<>();
@@ -317,6 +318,8 @@
audioChannels = parseAudioChannelConfiguration(xpp);
} else if (XmlPullParserUtil.isStartTag(xpp, "Accessibility")) {
accessibilityDescriptors.add(parseDescriptor(xpp, "Accessibility"));
+ } else if (XmlPullParserUtil.isStartTag(xpp, "EssentialProperty")) {
+ essentialProperties.add(parseDescriptor(xpp, "EssentialProperty"));
} else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) {
supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty"));
} else if (XmlPullParserUtil.isStartTag(xpp, "Representation")) {
@@ -334,6 +337,7 @@
language,
roleDescriptors,
accessibilityDescriptors,
+ essentialProperties,
supplementalProperties,
segmentBase,
periodDurationMs);
@@ -370,14 +374,28 @@
inbandEventStreams));
}
- return buildAdaptationSet(id, contentType, representations, accessibilityDescriptors,
+ return buildAdaptationSet(
+ id,
+ contentType,
+ representations,
+ accessibilityDescriptors,
+ essentialProperties,
supplementalProperties);
}
- protected AdaptationSet buildAdaptationSet(int id, int contentType,
- List<Representation> representations, List<Descriptor> accessibilityDescriptors,
+ protected AdaptationSet buildAdaptationSet(
+ int id,
+ int contentType,
+ List<Representation> representations,
+ List<Descriptor> accessibilityDescriptors,
+ List<Descriptor> essentialProperties,
List<Descriptor> supplementalProperties) {
- return new AdaptationSet(id, contentType, representations, accessibilityDescriptors,
+ return new AdaptationSet(
+ id,
+ contentType,
+ representations,
+ accessibilityDescriptors,
+ essentialProperties,
supplementalProperties);
}
@@ -492,6 +510,7 @@
@Nullable String adaptationSetLanguage,
List<Descriptor> adaptationSetRoleDescriptors,
List<Descriptor> adaptationSetAccessibilityDescriptors,
+ List<Descriptor> adaptationSetEssentialProperties,
List<Descriptor> adaptationSetSupplementalProperties,
@Nullable SegmentBase segmentBase,
long periodDurationMs)
@@ -509,7 +528,9 @@
String drmSchemeType = null;
ArrayList<SchemeData> drmSchemeDatas = new ArrayList<>();
ArrayList<Descriptor> inbandEventStreams = new ArrayList<>();
- ArrayList<Descriptor> supplementalProperties = new ArrayList<>();
+ ArrayList<Descriptor> essentialProperties = new ArrayList<>(adaptationSetEssentialProperties);
+ ArrayList<Descriptor> supplementalProperties =
+ new ArrayList<>(adaptationSetSupplementalProperties);
boolean seenFirstBaseUrl = false;
do {
@@ -542,6 +563,8 @@
}
} else if (XmlPullParserUtil.isStartTag(xpp, "InbandEventStream")) {
inbandEventStreams.add(parseDescriptor(xpp, "InbandEventStream"));
+ } else if (XmlPullParserUtil.isStartTag(xpp, "EssentialProperty")) {
+ essentialProperties.add(parseDescriptor(xpp, "EssentialProperty"));
} else if (XmlPullParserUtil.isStartTag(xpp, "SupplementalProperty")) {
supplementalProperties.add(parseDescriptor(xpp, "SupplementalProperty"));
} else {
@@ -563,6 +586,7 @@
adaptationSetRoleDescriptors,
adaptationSetAccessibilityDescriptors,
codecs,
+ essentialProperties,
supplementalProperties);
segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase();
@@ -583,6 +607,7 @@
List<Descriptor> roleDescriptors,
List<Descriptor> accessibilityDescriptors,
@Nullable String codecs,
+ List<Descriptor> essentialProperties,
List<Descriptor> supplementalProperties) {
@Nullable String sampleMimeType = getSampleMimeType(containerMimeType, codecs);
if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType)) {
@@ -591,6 +616,8 @@
@C.SelectionFlags int selectionFlags = parseSelectionFlagsFromRoleDescriptors(roleDescriptors);
@C.RoleFlags int roleFlags = parseRoleFlagsFromRoleDescriptors(roleDescriptors);
roleFlags |= parseRoleFlagsFromAccessibilityDescriptors(accessibilityDescriptors);
+ roleFlags |= parseRoleFlagsFromProperties(essentialProperties);
+ roleFlags |= parseRoleFlagsFromProperties(supplementalProperties);
Format.Builder formatBuilder =
new Format.Builder()
@@ -1186,6 +1213,18 @@
}
@C.RoleFlags
+ protected int parseRoleFlagsFromProperties(List<Descriptor> accessibilityDescriptors) {
+ @C.RoleFlags int result = 0;
+ for (int i = 0; i < accessibilityDescriptors.size(); i++) {
+ Descriptor descriptor = accessibilityDescriptors.get(i);
+ if ("http://dashif.org/guidelines/trickmode".equalsIgnoreCase(descriptor.schemeIdUri)) {
+ result |= C.ROLE_FLAG_TRICK_PLAY;
+ }
+ }
+ return result;
+ }
+
+ @C.RoleFlags
protected int parseDashRoleSchemeValue(@Nullable String value) {
if (value == null) {
return 0;
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java
index 92aa49d..e9e5f30 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashMediaPeriodTest.java
@@ -165,6 +165,7 @@
trackType,
Arrays.asList(representations),
/* accessibilityDescriptors= */ Collections.emptyList(),
+ /* essentialProperties= */ Collections.emptyList(),
descriptor == null ? Collections.emptyList() : Collections.singletonList(descriptor));
}
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java
index 28f15b2..3176b06 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/DashUtilTest.java
@@ -29,6 +29,7 @@
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
+import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -69,7 +70,13 @@
}
private static AdaptationSet newAdaptationSet(Representation... representations) {
- return new AdaptationSet(0, C.TRACK_TYPE_VIDEO, Arrays.asList(representations), null, null);
+ return new AdaptationSet(
+ /* id= */ 0,
+ C.TRACK_TYPE_VIDEO,
+ Arrays.asList(representations),
+ /* accessibilityDescriptors= */ Collections.emptyList(),
+ /* essentialProperties= */ Collections.emptyList(),
+ /* supplementalProperties= */ Collections.emptyList());
}
private static Representation newRepresentation(DrmInitData drmInitData) {
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
index 37b3046..4708747 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java
@@ -49,6 +49,7 @@
private static final String SAMPLE_MPD_LABELS = "mpd/sample_mpd_labels";
private static final String SAMPLE_MPD_ASSET_IDENTIFIER = "mpd/sample_mpd_asset_identifier";
private static final String SAMPLE_MPD_TEXT = "mpd/sample_mpd_text";
+ private static final String SAMPLE_MPD_TRICK_PLAY = "mpd/sample_mpd_trick_play";
private static final String NEXT_TAG_NAME = "Next";
private static final String NEXT_TAG = "<" + NEXT_TAG_NAME + "/>";
@@ -173,7 +174,7 @@
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
- Uri.parse("Https://example.com/test.mpd"),
+ Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_MPD));
ProgramInformation expectedProgramInformation =
new ProgramInformation(
@@ -201,7 +202,7 @@
DashManifestParser parser = new DashManifestParser();
DashManifest manifest =
parser.parse(
- Uri.parse("Https://example.com/test.mpd"),
+ Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(ApplicationProvider.getApplicationContext(), SAMPLE_MPD_TEXT));
List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
@@ -226,6 +227,46 @@
}
@Test
+ public void parseMediaPresentationDescription_trickPlay() throws IOException {
+ DashManifestParser parser = new DashManifestParser();
+ DashManifest manifest =
+ parser.parse(
+ Uri.parse("https://example.com/test.mpd"),
+ TestUtil.getInputStream(
+ ApplicationProvider.getApplicationContext(), SAMPLE_MPD_TRICK_PLAY));
+
+ List<AdaptationSet> adaptationSets = manifest.getPeriod(0).adaptationSets;
+
+ AdaptationSet adaptationSet = adaptationSets.get(0);
+ assertThat(adaptationSet.essentialProperties).isEmpty();
+ assertThat(adaptationSet.supplementalProperties).isEmpty();
+ assertThat(adaptationSet.representations.get(0).format.roleFlags).isEqualTo(0);
+
+ adaptationSet = adaptationSets.get(1);
+ assertThat(adaptationSet.essentialProperties).isEmpty();
+ assertThat(adaptationSet.supplementalProperties).isEmpty();
+ assertThat(adaptationSet.representations.get(0).format.roleFlags).isEqualTo(0);
+
+ adaptationSet = adaptationSets.get(2);
+ assertThat(adaptationSet.essentialProperties).hasSize(1);
+ assertThat(adaptationSet.essentialProperties.get(0).schemeIdUri)
+ .isEqualTo("http://dashif.org/guidelines/trickmode");
+ assertThat(adaptationSet.essentialProperties.get(0).value).isEqualTo("0");
+ assertThat(adaptationSet.supplementalProperties).isEmpty();
+ assertThat(adaptationSet.representations.get(0).format.roleFlags)
+ .isEqualTo(C.ROLE_FLAG_TRICK_PLAY);
+
+ adaptationSet = adaptationSets.get(3);
+ assertThat(adaptationSet.essentialProperties).isEmpty();
+ assertThat(adaptationSet.supplementalProperties).hasSize(1);
+ assertThat(adaptationSet.supplementalProperties.get(0).schemeIdUri)
+ .isEqualTo("http://dashif.org/guidelines/trickmode");
+ assertThat(adaptationSet.supplementalProperties.get(0).value).isEqualTo("1");
+ assertThat(adaptationSet.representations.get(0).format.roleFlags)
+ .isEqualTo(C.ROLE_FLAG_TRICK_PLAY);
+ }
+
+ @Test
public void parseSegmentTimeline_repeatCount() throws Exception {
DashManifestParser parser = new DashManifestParser();
XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser();
diff --git a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java
index b063c54..b260bf2 100644
--- a/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java
+++ b/tree/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestTest.java
@@ -239,6 +239,12 @@
}
private static AdaptationSet newAdaptationSet(int seed, Representation... representations) {
- return new AdaptationSet(++seed, ++seed, Arrays.asList(representations), null, null);
+ return new AdaptationSet(
+ ++seed,
+ ++seed,
+ Arrays.asList(representations),
+ /* accessibilityDescriptors= */ Collections.emptyList(),
+ /* essentialProperties= */ Collections.emptyList(),
+ /* supplementalProperties= */ Collections.emptyList());
}
}
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
index 84a92cf..359ccc1 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
@@ -665,9 +665,9 @@
private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) {
trex.setPosition(Atom.FULL_HEADER_SIZE);
int trackId = trex.readInt();
- int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
- int defaultSampleDuration = trex.readUnsignedIntToInt();
- int defaultSampleSize = trex.readUnsignedIntToInt();
+ int defaultSampleDescriptionIndex = trex.readInt() - 1;
+ int defaultSampleDuration = trex.readInt();
+ int defaultSampleSize = trex.readInt();
int defaultSampleFlags = trex.readInt();
return Pair.create(trackId, new DefaultSampleValues(defaultSampleDescriptionIndex,
@@ -753,8 +753,9 @@
}
}
- private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime,
- @Flags int flags) {
+ private static void parseTruns(
+ ContainerAtom traf, TrackBundle trackBundle, long decodeTime, @Flags int flags)
+ throws ParserException {
int trunCount = 0;
int totalSampleCount = 0;
List<LeafAtom> leafChildren = traf.leafChildren;
@@ -874,13 +875,20 @@
DefaultSampleValues defaultSampleValues = trackBundle.defaultSampleValues;
int defaultSampleDescriptionIndex =
((atomFlags & 0x02 /* default_sample_description_index_present */) != 0)
- ? tfhd.readUnsignedIntToInt() - 1 : defaultSampleValues.sampleDescriptionIndex;
- int defaultSampleDuration = ((atomFlags & 0x08 /* default_sample_duration_present */) != 0)
- ? tfhd.readUnsignedIntToInt() : defaultSampleValues.duration;
- int defaultSampleSize = ((atomFlags & 0x10 /* default_sample_size_present */) != 0)
- ? tfhd.readUnsignedIntToInt() : defaultSampleValues.size;
- int defaultSampleFlags = ((atomFlags & 0x20 /* default_sample_flags_present */) != 0)
- ? tfhd.readUnsignedIntToInt() : defaultSampleValues.flags;
+ ? tfhd.readInt() - 1
+ : defaultSampleValues.sampleDescriptionIndex;
+ int defaultSampleDuration =
+ ((atomFlags & 0x08 /* default_sample_duration_present */) != 0)
+ ? tfhd.readInt()
+ : defaultSampleValues.duration;
+ int defaultSampleSize =
+ ((atomFlags & 0x10 /* default_sample_size_present */) != 0)
+ ? tfhd.readInt()
+ : defaultSampleValues.size;
+ int defaultSampleFlags =
+ ((atomFlags & 0x20 /* default_sample_flags_present */) != 0)
+ ? tfhd.readInt()
+ : defaultSampleValues.flags;
trackBundle.fragment.header = new DefaultSampleValues(defaultSampleDescriptionIndex,
defaultSampleDuration, defaultSampleSize, defaultSampleFlags);
return trackBundle;
@@ -913,16 +921,22 @@
/**
* Parses a trun atom (defined in 14496-12).
*
- * @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into
- * which parsed data should be placed.
+ * @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into which
+ * parsed data should be placed.
* @param index Index of the track run in the fragment.
* @param decodeTime The decode time of the first sample in the fragment run.
* @param flags Flags to allow any required workaround to be executed.
* @param trun The trun atom to decode.
* @return The starting position of samples for the next run.
*/
- private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime,
- @Flags int flags, ParsableByteArray trun, int trackRunStart) {
+ private static int parseTrun(
+ TrackBundle trackBundle,
+ int index,
+ long decodeTime,
+ @Flags int flags,
+ ParsableByteArray trun,
+ int trackRunStart)
+ throws ParserException {
trun.setPosition(Atom.HEADER_SIZE);
int fullAtom = trun.readInt();
int atomFlags = Atom.parseFullAtomFlags(fullAtom);
@@ -940,7 +954,7 @@
boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0;
int firstSampleFlags = defaultSampleValues.flags;
if (firstSampleFlagsPresent) {
- firstSampleFlags = trun.readUnsignedIntToInt();
+ firstSampleFlags = trun.readInt();
}
boolean sampleDurationsPresent = (atomFlags & 0x100 /* sample_duration_present */) != 0;
@@ -975,9 +989,10 @@
long cumulativeTime = index > 0 ? fragment.nextFragmentDecodeTime : decodeTime;
for (int i = trackRunStart; i < trackRunEnd; i++) {
// Use trun values if present, otherwise tfhd, otherwise trex.
- int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
- : defaultSampleValues.duration;
- int sampleSize = sampleSizesPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.size;
+ int sampleDuration =
+ checkNonNegative(sampleDurationsPresent ? trun.readInt() : defaultSampleValues.duration);
+ int sampleSize =
+ checkNonNegative(sampleSizesPresent ? trun.readInt() : defaultSampleValues.size);
int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags
: sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags;
if (sampleCompositionTimeOffsetsPresent) {
@@ -1003,6 +1018,13 @@
return trackRunEnd;
}
+ private static int checkNonNegative(int value) throws ParserException {
+ if (value < 0) {
+ throw new ParserException("Unexpected negtive value: " + value);
+ }
+ return value;
+ }
+
private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
byte[] extendedTypeScratch) throws ParserException {
uuid.setPosition(Atom.HEADER_SIZE);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java
index 4369ff7..dcf64d9 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java
@@ -172,9 +172,8 @@
@RequiresNonNull("sampleReader")
private void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) {
- if (hasOutputFormat) {
- sampleReader.startNalUnit(position, offset, nalUnitType, pesTimeUs);
- } else {
+ sampleReader.startNalUnit(position, offset, nalUnitType, pesTimeUs, hasOutputFormat);
+ if (!hasOutputFormat) {
vps.startNalUnit(nalUnitType);
sps.startNalUnit(nalUnitType);
pps.startNalUnit(nalUnitType);
@@ -185,9 +184,8 @@
@RequiresNonNull("sampleReader")
private void nalUnitData(byte[] dataArray, int offset, int limit) {
- if (hasOutputFormat) {
- sampleReader.readNalUnitData(dataArray, offset, limit);
- } else {
+ sampleReader.readNalUnitData(dataArray, offset, limit);
+ if (!hasOutputFormat) {
vps.appendToNalUnit(dataArray, offset, limit);
sps.appendToNalUnit(dataArray, offset, limit);
pps.appendToNalUnit(dataArray, offset, limit);
@@ -198,9 +196,8 @@
@RequiresNonNull({"output", "sampleReader"})
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
- if (hasOutputFormat) {
- sampleReader.endNalUnit(position, offset);
- } else {
+ sampleReader.endNalUnit(position, offset, hasOutputFormat);
+ if (!hasOutputFormat) {
vps.endNalUnit(discardPadding);
sps.endNalUnit(discardPadding);
pps.endNalUnit(discardPadding);
@@ -454,7 +451,8 @@
writingParameterSets = false;
}
- public void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) {
+ public void startNalUnit(
+ long position, int offset, int nalUnitType, long pesTimeUs, boolean hasOutputFormat) {
isFirstSlice = false;
isFirstParameterSet = false;
nalUnitTimeUs = pesTimeUs;
@@ -464,7 +462,9 @@
if (nalUnitType >= VPS_NUT) {
if (!writingParameterSets && readingSample) {
// This is a non-VCL NAL unit, so flush the previous sample.
- outputSample(offset);
+ if (hasOutputFormat) {
+ outputSample(offset);
+ }
readingSample = false;
}
if (nalUnitType <= PPS_NUT) {
@@ -491,14 +491,14 @@
}
}
- public void endNalUnit(long position, int offset) {
+ public void endNalUnit(long position, int offset, boolean hasOutputFormat) {
if (writingParameterSets && isFirstSlice) {
// This sample has parameter sets. Reset the key-frame flag based on the first slice.
sampleIsKeyframe = nalUnitHasKeyframeData;
writingParameterSets = false;
} else if (isFirstParameterSet || isFirstSlice) {
// This NAL unit is at the start of a new sample (access unit).
- if (readingSample) {
+ if (hasOutputFormat && readingSample) {
// Output the sample ending before this NAL unit.
int nalUnitLength = (int) (position - nalUnitStartPosition);
outputSample(offset + nalUnitLength);
diff --git a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PassthroughSectionPayloadReader.java b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PassthroughSectionPayloadReader.java
index af374f6..72667e2 100644
--- a/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PassthroughSectionPayloadReader.java
+++ b/tree/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/PassthroughSectionPayloadReader.java
@@ -33,10 +33,9 @@
*/
public final class PassthroughSectionPayloadReader implements SectionPayloadReader {
- private final String mimeType;
+ private Format format;
private @MonotonicNonNull TimestampAdjuster timestampAdjuster;
private @MonotonicNonNull TrackOutput output;
- private boolean formatDeclared;
/**
* Create a new PassthroughSectionPayloadReader.
@@ -44,7 +43,7 @@
* @param mimeType The MIME type set as {@link Format#sampleMimeType} on the created output track.
*/
public PassthroughSectionPayloadReader(String mimeType) {
- this.mimeType = mimeType;
+ this.format = new Format.Builder().setSampleMimeType(mimeType).build();
}
@Override
@@ -55,22 +54,22 @@
this.timestampAdjuster = timestampAdjuster;
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
+ // Eagerly output an incomplete format (missing timestamp offset) to ensure source preparation
+ // is not blocked waiting for potentially sparse metadata.
+ output.format(format);
}
@Override
public void consume(ParsableByteArray sectionData) {
assertInitialized();
- if (!formatDeclared) {
- if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) {
- // There is not enough information to initialize the timestamp adjuster.
- return;
- }
- output.format(
- new Format.Builder()
- .setSampleMimeType(mimeType)
- .setSubsampleOffsetUs(timestampAdjuster.getTimestampOffsetUs())
- .build());
- formatDeclared = true;
+ long subsampleOffsetUs = timestampAdjuster.getTimestampOffsetUs();
+ if (subsampleOffsetUs == C.TIME_UNSET) {
+ // Don't output samples without a known subsample offset.
+ return;
+ }
+ if (subsampleOffsetUs != format.subsampleOffsetUs) {
+ format = format.buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
+ output.format(format);
}
int sampleSize = sectionData.bytesLeft();
output.sampleData(sectionData, sampleSize);
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java
index 554c032..5622137 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/mp3/Mp3ExtractorTest.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.mp3;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.ExtractorAsserts;
import org.junit.Test;
@@ -46,4 +47,22 @@
public void trimmedMp3Sample() throws Exception {
ExtractorAsserts.assertBehavior(Mp3Extractor::new, "mp3/play-trimmed.mp3");
}
+
+ @Test
+ public void mp3SampleWithId3Enabled() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ Mp3Extractor::new,
+ /* file= */ "mp3/bear-id3.mp3",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "mp3/bear-id3-enabled");
+ }
+
+ @Test
+ public void mp3SampleWithId3Disabled() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ () -> new Mp3Extractor(Mp3Extractor.FLAG_DISABLE_ID3_METADATA),
+ /* file= */ "mp3/bear-id3.mp3",
+ ApplicationProvider.getApplicationContext(),
+ /* dumpFilesPrefix= */ "mp3/bear-id3-disabled");
+ }
}
diff --git a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java
index 794df91..d040c22 100644
--- a/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java
+++ b/tree/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java
@@ -36,6 +36,7 @@
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,11 +55,22 @@
}
@Test
+ public void sampleWithH265() throws Exception {
+ ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_h265.ts");
+ }
+
+ @Test
+ @Ignore
+ // TODO(internal: b/153539929) Re-enable when ExtractorAsserts is less strict around repeated
+ // formats and seeking.
public void sampleWithScte35() throws Exception {
ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_scte35.ts");
}
@Test
+ @Ignore
+ // TODO(internal: b/153539929) Re-enable when ExtractorAsserts is less strict around repeated
+ // formats and seeking.
public void sampleWithAit() throws Exception {
ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_ait.ts");
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
index 0b587fd..c269691 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
@@ -647,7 +647,7 @@
checkInBounds();
Segment segment = playlist.segments.get((int) getCurrentIndex());
Uri chunkUri = UriUtil.resolveToUri(playlist.baseUri, segment.url);
- return new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength);
+ return new DataSpec(chunkUri, segment.byteRangeOffset, segment.byteRangeLength);
}
@Override
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
index d397dc5..3a2285a 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
@@ -94,8 +94,8 @@
DataSpec dataSpec =
new DataSpec(
UriUtil.resolveToUri(mediaPlaylist.baseUri, mediaSegment.url),
- mediaSegment.byterangeOffset,
- mediaSegment.byterangeLength);
+ mediaSegment.byteRangeOffset,
+ mediaSegment.byteRangeLength);
boolean mediaSegmentEncrypted = mediaSegmentKey != null;
@Nullable
byte[] mediaSegmentIv =
@@ -118,7 +118,7 @@
: null;
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
initDataSpec =
- new DataSpec(initSegmentUri, initSegment.byterangeOffset, initSegment.byterangeLength);
+ new DataSpec(initSegmentUri, initSegment.byteRangeOffset, initSegment.byteRangeLength);
initDataSource = buildDataSource(dataSource, initSegmentKey, initSegmentIv);
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java
index 7bf982b..d172aa2 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/offline/HlsDownloader.java
@@ -141,7 +141,7 @@
}
}
Uri segmentUri = UriUtil.resolveToUri(baseUri, segment.url);
- DataSpec dataSpec = new DataSpec(segmentUri, segment.byterangeOffset, segment.byterangeLength);
+ DataSpec dataSpec = new DataSpec(segmentUri, segment.byteRangeOffset, segment.byteRangeLength);
out.add(new Segment(startTimeUs, dataSpec));
}
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
index 58f500c..be771b9 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
@@ -70,30 +70,28 @@
* encrypted.
*/
@Nullable public final String encryptionIV;
- /**
- * The segment's byte range offset, as defined by #EXT-X-BYTERANGE.
- */
- public final long byterangeOffset;
+ /** The segment's byte range offset, as defined by #EXT-X-BYTERANGE. */
+ public final long byteRangeOffset;
/**
* The segment's byte range length, as defined by #EXT-X-BYTERANGE, or {@link C#LENGTH_UNSET} if
* no byte range is specified.
*/
- public final long byterangeLength;
+ public final long byteRangeLength;
/** Whether the segment is tagged with #EXT-X-GAP. */
public final boolean hasGapTag;
/**
* @param uri See {@link #url}.
- * @param byterangeOffset See {@link #byterangeOffset}.
- * @param byterangeLength See {@link #byterangeLength}.
+ * @param byteRangeOffset See {@link #byteRangeOffset}.
+ * @param byteRangeLength See {@link #byteRangeLength}.
* @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}.
* @param encryptionIV See {@link #encryptionIV}.
*/
public Segment(
String uri,
- long byterangeOffset,
- long byterangeLength,
+ long byteRangeOffset,
+ long byteRangeLength,
@Nullable String fullSegmentEncryptionKeyUri,
@Nullable String encryptionIV) {
this(
@@ -106,8 +104,8 @@
/* drmInitData= */ null,
fullSegmentEncryptionKeyUri,
encryptionIV,
- byterangeOffset,
- byterangeLength,
+ byteRangeOffset,
+ byteRangeLength,
/* hasGapTag= */ false);
}
@@ -121,8 +119,8 @@
* @param drmInitData See {@link #drmInitData}.
* @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}.
* @param encryptionIV See {@link #encryptionIV}.
- * @param byterangeOffset See {@link #byterangeOffset}.
- * @param byterangeLength See {@link #byterangeLength}.
+ * @param byteRangeOffset See {@link #byteRangeOffset}.
+ * @param byteRangeLength See {@link #byteRangeLength}.
* @param hasGapTag See {@link #hasGapTag}.
*/
public Segment(
@@ -135,8 +133,8 @@
@Nullable DrmInitData drmInitData,
@Nullable String fullSegmentEncryptionKeyUri,
@Nullable String encryptionIV,
- long byterangeOffset,
- long byterangeLength,
+ long byteRangeOffset,
+ long byteRangeLength,
boolean hasGapTag) {
this.url = url;
this.initializationSegment = initializationSegment;
@@ -147,8 +145,8 @@
this.drmInitData = drmInitData;
this.fullSegmentEncryptionKeyUri = fullSegmentEncryptionKeyUri;
this.encryptionIV = encryptionIV;
- this.byterangeOffset = byterangeOffset;
- this.byterangeLength = byterangeLength;
+ this.byteRangeOffset = byteRangeOffset;
+ this.byteRangeLength = byteRangeLength;
this.hasGapTag = hasGapTag;
}
diff --git a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
index 8a0d190..77e541f 100644
--- a/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
+++ b/tree/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
@@ -69,6 +69,8 @@
private static final String TAG_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE";
private static final String TAG_DEFINE = "#EXT-X-DEFINE";
private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF";
+ private static final String TAG_I_FRAME_STREAM_INF = "#EXT-X-I-FRAME-STREAM-INF";
+ private static final String TAG_IFRAME = "#EXT-X-I-FRAMES-ONLY";
private static final String TAG_MEDIA = "#EXT-X-MEDIA";
private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION";
private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY";
@@ -281,6 +283,7 @@
// We expose all tags through the playlist.
tags.add(line);
}
+ boolean isIFrameOnlyVariant = line.startsWith(TAG_I_FRAME_STREAM_INF);
if (line.startsWith(TAG_DEFINE)) {
variableDefinitions.put(
@@ -301,8 +304,9 @@
String scheme = parseEncryptionScheme(method);
sessionKeyDrmInitData.add(new DrmInitData(scheme, schemeData));
}
- } else if (line.startsWith(TAG_STREAM_INF)) {
+ } else if (line.startsWith(TAG_STREAM_INF) || isIFrameOnlyVariant) {
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
+ int roleFlags = isIFrameOnlyVariant ? C.ROLE_FLAG_TRICK_PLAY : 0;
int peakBitrate = parseIntAttr(line, REGEX_BANDWIDTH);
int averageBitrate = parseOptionalIntAttr(line, REGEX_AVERAGE_BANDWIDTH, -1);
String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions);
@@ -335,13 +339,18 @@
parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions);
String closedCaptionsGroupId =
parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions);
- if (!iterator.hasNext()) {
- throw new ParserException("#EXT-X-STREAM-INF tag must be followed by another line");
+ Uri uri;
+ if (isIFrameOnlyVariant) {
+ uri =
+ UriUtil.resolveToUri(baseUri, parseStringAttr(line, REGEX_URI, variableDefinitions));
+ } else if (!iterator.hasNext()) {
+ throw new ParserException("#EXT-X-STREAM-INF must be followed by another line");
+ } else {
+ // The following line contains #EXT-X-STREAM-INF's URI.
+ line = replaceVariableReferences(iterator.next(), variableDefinitions);
+ uri = UriUtil.resolveToUri(baseUri, line);
}
- line =
- replaceVariableReferences(
- iterator.next(), variableDefinitions); // #EXT-X-STREAM-INF's URI.
- Uri uri = UriUtil.resolveToUri(baseUri, line);
+
Format format =
new Format.Builder()
.setId(variants.size())
@@ -352,6 +361,7 @@
.setWidth(width)
.setHeight(height)
.setFrameRate(frameRate)
+ .setRoleFlags(roleFlags)
.build();
Variant variant =
new Variant(
@@ -558,8 +568,9 @@
long targetDurationUs = C.TIME_UNSET;
boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments;
boolean hasEndTag = false;
- Segment initializationSegment = null;
+ @Nullable Segment initializationSegment = null;
HashMap<String, String> variableDefinitions = new HashMap<>();
+ HashMap<String, Segment> urlToInferredInitSegment = new HashMap<>();
List<Segment> segments = new ArrayList<>();
List<String> tags = new ArrayList<>();
@@ -572,6 +583,7 @@
long segmentStartTimeUs = 0;
long segmentByteRangeOffset = 0;
long segmentByteRangeLength = C.LENGTH_UNSET;
+ boolean isIFrameOnly = false;
long segmentMediaSequence = 0;
boolean hasGapTag = false;
@@ -598,6 +610,8 @@
} else if ("EVENT".equals(playlistTypeString)) {
playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_EVENT;
}
+ } else if (line.equals(TAG_IFRAME)) {
+ isIFrameOnly = true;
} else if (line.startsWith(TAG_START)) {
startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND);
} else if (line.startsWith(TAG_INIT_SEGMENT)) {
@@ -715,8 +729,25 @@
}
segmentMediaSequence++;
+ String segmentUri = replaceVariableReferences(line, variableDefinitions);
+ @Nullable Segment inferredInitSegment = urlToInferredInitSegment.get(segmentUri);
if (segmentByteRangeLength == C.LENGTH_UNSET) {
+ // The segment is not byte range defined.
segmentByteRangeOffset = 0;
+ } else if (isIFrameOnly && initializationSegment == null && inferredInitSegment == null) {
+ // The segment is a resource byte range without an initialization segment.
+ // As per RFC 8216, Section 4.3.3.6, we assume the initialization section exists in the
+ // bytes preceding the first segment in this segment's URL.
+ // We assume the implicit initialization segment is unencrypted, since there's no way for
+ // the playlist to provide an initialization vector for it.
+ inferredInitSegment =
+ new Segment(
+ segmentUri,
+ /* byteRangeOffset= */ 0,
+ segmentByteRangeOffset,
+ /* fullSegmentEncryptionKeyUri= */ null,
+ /* encryptionIV= */ null);
+ urlToInferredInitSegment.put(segmentUri, inferredInitSegment);
}
if (cachedDrmInitData == null && !currentSchemeDatas.isEmpty()) {
@@ -733,8 +764,8 @@
segments.add(
new Segment(
- replaceVariableReferences(line, variableDefinitions),
- initializationSegment,
+ segmentUri,
+ initializationSegment != null ? initializationSegment : inferredInitSegment,
segmentTitle,
segmentDurationUs,
relativeDiscontinuitySequence,
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
index 55aa4b3..05bc3ba 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMasterPlaylistParserTest.java
@@ -25,6 +25,7 @@
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry;
+import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -207,6 +208,22 @@
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",NAME=\"English\",URI=\"a1/index.m3u8\"\n"
+ "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,URI=\"s1/en/prog_index.m3u8\"\n";
+ private static final String PLAYLIST_WITH_IFRAME_VARIANTS =
+ "#EXTM3U\n"
+ + "#EXT-X-VERSION:5\n"
+ + "#EXT-X-MEDIA:URI=\"AUDIO_English/index.m3u8\",TYPE=AUDIO,GROUP-ID=\"audio-aac\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES\n"
+ + "#EXT-X-MEDIA:URI=\"AUDIO_Spanish/index.m3u8\",TYPE=AUDIO,GROUP-ID=\"audio-aac\",LANGUAGE=\"es\",NAME=\"Spanish\",AUTOSELECT=YES\n"
+ + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID=\"cc1\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,INSTREAM-ID=\"CC1\"\n"
+ + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=400000,RESOLUTION=480x320,CODECS=\"mp4a.40.2,avc1.640015\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
+ + "400000/index.m3u8\n"
+ + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1000000,RESOLUTION=848x480,CODECS=\"mp4a.40.2,avc1.64001f\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
+ + "1000000/index.m3u8\n"
+ + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=3220000,RESOLUTION=1280x720,CODECS=\"mp4a.40.2,avc1.64001f\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
+ + "3220000/index.m3u8\n"
+ + "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=8940000,RESOLUTION=1920x1080,CODECS=\"mp4a.40.2,avc1.640028\",AUDIO=\"audio-aac\",CLOSED-CAPTIONS=\"cc1\"\n"
+ + "8940000/index.m3u8\n"
+ + "#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=1313400,RESOLUTION=1920x1080,CODECS=\"avc1.640028\",URI=\"iframe_1313400/index.m3u8\"\n";
+
@Test
public void parseMasterPlaylist_withSimple_success() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
@@ -407,6 +424,19 @@
.isEqualTo(createExtXMediaMetadata(/* groupId= */ "aud3", /* name= */ "English"));
}
+ @Test
+ public void testIFrameVariant() throws IOException {
+ HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_IFRAME_VARIANTS);
+ assertThat(playlist.variants).hasSize(5);
+ for (int i = 0; i < 4; i++) {
+ assertThat(playlist.variants.get(i).format.roleFlags).isEqualTo(0);
+ }
+ Variant iFramesOnlyVariant = playlist.variants.get(4);
+ assertThat(iFramesOnlyVariant.format.bitrate).isEqualTo(1313400);
+ assertThat(iFramesOnlyVariant.format.roleFlags & C.ROLE_FLAG_TRICK_PLAY)
+ .isEqualTo(C.ROLE_FLAG_TRICK_PLAY);
+ }
+
private static Metadata createExtXStreamInfMetadata(HlsTrackMetadataEntry.VariantInfo... infos) {
return new Metadata(
new HlsTrackMetadataEntry(/* groupId= */ null, /* name= */ null, Arrays.asList(infos)));
diff --git a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
index ce5ff84..dd8a32b 100644
--- a/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
+++ b/tree/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.fail;
import android.net.Uri;
+import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
@@ -96,8 +97,8 @@
assertThat(segment.title).isEqualTo("");
assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isNull();
- assertThat(segment.byterangeLength).isEqualTo(51370);
- assertThat(segment.byterangeOffset).isEqualTo(0);
+ assertThat(segment.byteRangeLength).isEqualTo(51370);
+ assertThat(segment.byteRangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2679.ts");
segment = segments.get(1);
@@ -107,8 +108,8 @@
assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2680");
assertThat(segment.encryptionIV).isEqualTo("0x1566B");
- assertThat(segment.byterangeLength).isEqualTo(51501);
- assertThat(segment.byterangeOffset).isEqualTo(2147483648L);
+ assertThat(segment.byteRangeLength).isEqualTo(51501);
+ assertThat(segment.byteRangeOffset).isEqualTo(2147483648L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2680.ts");
segment = segments.get(2);
@@ -117,8 +118,8 @@
assertThat(segment.title).isEqualTo("segment title .,:/# with interesting chars");
assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isEqualTo(null);
- assertThat(segment.byterangeLength).isEqualTo(51501);
- assertThat(segment.byterangeOffset).isEqualTo(2147535149L);
+ assertThat(segment.byteRangeLength).isEqualTo(51501);
+ assertThat(segment.byteRangeOffset).isEqualTo(2147535149L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2681.ts");
segment = segments.get(3);
@@ -130,8 +131,8 @@
// 0xA7A == 2682.
assertThat(segment.encryptionIV).isNotNull();
assertThat(Util.toUpperInvariant(segment.encryptionIV)).isEqualTo("A7A");
- assertThat(segment.byterangeLength).isEqualTo(51740);
- assertThat(segment.byterangeOffset).isEqualTo(2147586650L);
+ assertThat(segment.byteRangeLength).isEqualTo(51740);
+ assertThat(segment.byteRangeOffset).isEqualTo(2147586650L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts");
segment = segments.get(4);
@@ -143,8 +144,8 @@
// 0xA7B == 2683.
assertThat(segment.encryptionIV).isNotNull();
assertThat(Util.toUpperInvariant(segment.encryptionIV)).isEqualTo("A7B");
- assertThat(segment.byterangeLength).isEqualTo(C.LENGTH_UNSET);
- assertThat(segment.byterangeOffset).isEqualTo(0);
+ assertThat(segment.byteRangeLength).isEqualTo(C.LENGTH_UNSET);
+ assertThat(segment.byteRangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts");
}
@@ -368,6 +369,40 @@
}
@Test
+ public void noExplicitInitSegmentInIFrameOnly_infersInitSegment() throws IOException {
+ Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
+ String playlistString =
+ "#EXTM3U\n"
+ + "#EXT-X-TARGETDURATION:5\n"
+ + "#EXT-X-I-FRAMES-ONLY\n"
+ + "#EXTINF:5.005,\n"
+ + "#EXT-X-BYTERANGE:100@300\n"
+ + "segment1.ts\n"
+ + "#EXTINF:5.005,\n"
+ + "#EXT-X-BYTERANGE:100@400\n"
+ + "segment2.ts\n"
+ + "#EXTINF:5.005,\n"
+ + "#EXT-X-BYTERANGE:100@400\n"
+ + "segment1.ts\n";
+ InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
+ HlsMediaPlaylist playlist =
+ (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
+ List<Segment> segments = playlist.segments;
+ @Nullable Segment initializationSegment = segments.get(0).initializationSegment;
+ assertThat(initializationSegment.url).isEqualTo("segment1.ts");
+ assertThat(initializationSegment.byteRangeOffset).isEqualTo(0);
+ assertThat(initializationSegment.byteRangeLength).isEqualTo(300);
+ initializationSegment = segments.get(1).initializationSegment;
+ assertThat(initializationSegment.url).isEqualTo("segment2.ts");
+ assertThat(initializationSegment.byteRangeOffset).isEqualTo(0);
+ assertThat(initializationSegment.byteRangeLength).isEqualTo(400);
+ initializationSegment = segments.get(2).initializationSegment;
+ assertThat(initializationSegment.url).isEqualTo("segment1.ts");
+ assertThat(initializationSegment.byteRangeOffset).isEqualTo(0);
+ assertThat(initializationSegment.byteRangeLength).isEqualTo(300);
+ }
+
+ @Test
public void encryptedMapTag() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test3.m3u8");
String playlistString =
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
index 775045c..ee9f3f9 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleView.java
@@ -23,8 +23,8 @@
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.CaptioningManager;
+import android.widget.FrameLayout;
import androidx.annotation.Dimension;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -39,7 +39,7 @@
import java.util.List;
/** A view for displaying subtitle {@link Cue}s. */
-public final class SubtitleView extends ViewGroup implements TextOutput {
+public final class SubtitleView extends FrameLayout implements TextOutput {
/**
* The default fractional text size.
@@ -116,13 +116,6 @@
output.onCues(cues != null ? cues : Collections.emptyList());
}
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- if (changed) {
- innerSubtitleView.layout(l, t, r, b);
- }
- }
-
/**
* Set the type of {@link View} used to display subtitles.
*
diff --git a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java
index 670314e..3a5560b 100644
--- a/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java
+++ b/tree/library/ui/src/main/java/com/google/android/exoplayer2/ui/SubtitleWebView.java
@@ -25,8 +25,8 @@
import android.util.AttributeSet;
import android.util.Base64;
import android.view.MotionEvent;
-import android.view.ViewGroup;
import android.webkit.WebView;
+import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.CaptionStyleCompat;
@@ -45,7 +45,7 @@
* <p>NOTE: This is currently extremely experimental and doesn't support most {@link Cue} styling
* properties.
*/
-/* package */ final class SubtitleWebView extends ViewGroup implements SubtitleView.Output {
+/* package */ final class SubtitleWebView extends FrameLayout implements SubtitleView.Output {
private final WebView webView;
@@ -145,13 +145,6 @@
updateWebView();
}
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- if (changed) {
- webView.layout(l, t, r, b);
- }
- }
-
private void updateWebView() {
StringBuilder html = new StringBuilder();
html.append("<html><body>")
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-disabled.0.dump b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.0.dump
new file mode 100644
index 0000000..a80bf5e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.0.dump
@@ -0,0 +1,487 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-disabled.1.dump b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.1.dump
new file mode 100644
index 0000000..27e36eb
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.1.dump
@@ -0,0 +1,339 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 25344
+ sample count = 80
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ sample 0:
+ time = 943000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 1:
+ time = 967000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 2:
+ time = 991000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 3:
+ time = 1015000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 4:
+ time = 1039000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 5:
+ time = 1063000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 6:
+ time = 1087000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 7:
+ time = 1111000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 8:
+ time = 1135000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 9:
+ time = 1159000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 10:
+ time = 1183000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 11:
+ time = 1207000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 12:
+ time = 1231000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 13:
+ time = 1255000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 14:
+ time = 1279000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 15:
+ time = 1303000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 16:
+ time = 1327000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 17:
+ time = 1351000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 18:
+ time = 1375000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 19:
+ time = 1399000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 20:
+ time = 1423000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 21:
+ time = 1447000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 22:
+ time = 1471000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 23:
+ time = 1495000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 24:
+ time = 1519000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 25:
+ time = 1543000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 26:
+ time = 1567000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 27:
+ time = 1591000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 28:
+ time = 1615000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 29:
+ time = 1639000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 30:
+ time = 1663000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 31:
+ time = 1687000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 32:
+ time = 1711000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 33:
+ time = 1735000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 34:
+ time = 1759000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 35:
+ time = 1783000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 36:
+ time = 1807000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 37:
+ time = 1831000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 38:
+ time = 1855000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 39:
+ time = 1879000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 40:
+ time = 1903000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 41:
+ time = 1927000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 42:
+ time = 1951000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 43:
+ time = 1975000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 44:
+ time = 1999000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 45:
+ time = 2023000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 46:
+ time = 2047000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 47:
+ time = 2071000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 48:
+ time = 2095000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 49:
+ time = 2119000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 50:
+ time = 2143000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 51:
+ time = 2167000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 52:
+ time = 2191000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 53:
+ time = 2215000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 54:
+ time = 2239000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 55:
+ time = 2263000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 56:
+ time = 2287000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 57:
+ time = 2311000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 58:
+ time = 2335000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 59:
+ time = 2359000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 60:
+ time = 2383000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 61:
+ time = 2407000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 62:
+ time = 2431000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 63:
+ time = 2455000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 64:
+ time = 2479000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 65:
+ time = 2503000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 66:
+ time = 2527000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 67:
+ time = 2551000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 68:
+ time = 2575000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 69:
+ time = 2599000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 70:
+ time = 2623000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 71:
+ time = 2647000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 72:
+ time = 2671000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 73:
+ time = 2695000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 74:
+ time = 2719000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 75:
+ time = 2743000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 76:
+ time = 2767000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 77:
+ time = 2791000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 78:
+ time = 2815000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 79:
+ time = 2839000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-disabled.2.dump b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.2.dump
new file mode 100644
index 0000000..356e7d9
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.2.dump
@@ -0,0 +1,187 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 12624
+ sample count = 42
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ sample 0:
+ time = 1879000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 1:
+ time = 1903000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 2:
+ time = 1927000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 3:
+ time = 1951000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 4:
+ time = 1975000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 5:
+ time = 1999000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 6:
+ time = 2023000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 7:
+ time = 2047000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 8:
+ time = 2071000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 9:
+ time = 2095000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 10:
+ time = 2119000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 11:
+ time = 2143000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 12:
+ time = 2167000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 13:
+ time = 2191000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 14:
+ time = 2215000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 15:
+ time = 2239000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 16:
+ time = 2263000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 17:
+ time = 2287000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 18:
+ time = 2311000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 19:
+ time = 2335000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 20:
+ time = 2359000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 21:
+ time = 2383000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 22:
+ time = 2407000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 23:
+ time = 2431000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 24:
+ time = 2455000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 25:
+ time = 2479000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 26:
+ time = 2503000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 27:
+ time = 2527000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 28:
+ time = 2551000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 29:
+ time = 2575000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 30:
+ time = 2599000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 31:
+ time = 2623000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 32:
+ time = 2647000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 33:
+ time = 2671000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 34:
+ time = 2695000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 35:
+ time = 2719000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 36:
+ time = 2743000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 37:
+ time = 2767000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 38:
+ time = 2791000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 39:
+ time = 2815000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 40:
+ time = 2839000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 41:
+ time = 2863000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-disabled.3.dump b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.3.dump
new file mode 100644
index 0000000..44c9375
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.3.dump
@@ -0,0 +1,19 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-disabled.unknown_length.dump b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.unknown_length.dump
new file mode 100644
index 0000000..a80bf5e
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-disabled.unknown_length.dump
@@ -0,0 +1,487 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-enabled.0.dump b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.0.dump
new file mode 100644
index 0000000..c252057
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.0.dump
@@ -0,0 +1,488 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-enabled.1.dump b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.1.dump
new file mode 100644
index 0000000..76fcbc0
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.1.dump
@@ -0,0 +1,340 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 25344
+ sample count = 80
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description]
+ sample 0:
+ time = 943000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 1:
+ time = 967000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 2:
+ time = 991000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 3:
+ time = 1015000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 4:
+ time = 1039000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 5:
+ time = 1063000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 6:
+ time = 1087000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 7:
+ time = 1111000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 8:
+ time = 1135000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 9:
+ time = 1159000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 10:
+ time = 1183000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 11:
+ time = 1207000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 12:
+ time = 1231000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 13:
+ time = 1255000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 14:
+ time = 1279000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 15:
+ time = 1303000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 16:
+ time = 1327000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 17:
+ time = 1351000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 18:
+ time = 1375000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 19:
+ time = 1399000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 20:
+ time = 1423000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 21:
+ time = 1447000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 22:
+ time = 1471000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 23:
+ time = 1495000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 24:
+ time = 1519000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 25:
+ time = 1543000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 26:
+ time = 1567000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 27:
+ time = 1591000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 28:
+ time = 1615000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 29:
+ time = 1639000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 30:
+ time = 1663000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 31:
+ time = 1687000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 32:
+ time = 1711000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 33:
+ time = 1735000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 34:
+ time = 1759000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 35:
+ time = 1783000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 36:
+ time = 1807000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 37:
+ time = 1831000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 38:
+ time = 1855000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 39:
+ time = 1879000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 40:
+ time = 1903000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 41:
+ time = 1927000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 42:
+ time = 1951000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 43:
+ time = 1975000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 44:
+ time = 1999000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 45:
+ time = 2023000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 46:
+ time = 2047000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 47:
+ time = 2071000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 48:
+ time = 2095000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 49:
+ time = 2119000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 50:
+ time = 2143000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 51:
+ time = 2167000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 52:
+ time = 2191000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 53:
+ time = 2215000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 54:
+ time = 2239000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 55:
+ time = 2263000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 56:
+ time = 2287000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 57:
+ time = 2311000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 58:
+ time = 2335000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 59:
+ time = 2359000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 60:
+ time = 2383000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 61:
+ time = 2407000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 62:
+ time = 2431000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 63:
+ time = 2455000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 64:
+ time = 2479000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 65:
+ time = 2503000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 66:
+ time = 2527000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 67:
+ time = 2551000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 68:
+ time = 2575000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 69:
+ time = 2599000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 70:
+ time = 2623000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 71:
+ time = 2647000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 72:
+ time = 2671000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 73:
+ time = 2695000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 74:
+ time = 2719000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 75:
+ time = 2743000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 76:
+ time = 2767000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 77:
+ time = 2791000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 78:
+ time = 2815000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 79:
+ time = 2839000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-enabled.2.dump b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.2.dump
new file mode 100644
index 0000000..4f9b29d
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.2.dump
@@ -0,0 +1,188 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 12624
+ sample count = 42
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description]
+ sample 0:
+ time = 1879000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 1:
+ time = 1903000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 2:
+ time = 1927000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 3:
+ time = 1951000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 4:
+ time = 1975000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 5:
+ time = 1999000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 6:
+ time = 2023000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 7:
+ time = 2047000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 8:
+ time = 2071000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 9:
+ time = 2095000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 10:
+ time = 2119000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 11:
+ time = 2143000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 12:
+ time = 2167000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 13:
+ time = 2191000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 14:
+ time = 2215000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 15:
+ time = 2239000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 16:
+ time = 2263000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 17:
+ time = 2287000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 18:
+ time = 2311000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 19:
+ time = 2335000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 20:
+ time = 2359000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 21:
+ time = 2383000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 22:
+ time = 2407000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 23:
+ time = 2431000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 24:
+ time = 2455000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 25:
+ time = 2479000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 26:
+ time = 2503000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 27:
+ time = 2527000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 28:
+ time = 2551000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 29:
+ time = 2575000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 30:
+ time = 2599000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 31:
+ time = 2623000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 32:
+ time = 2647000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 33:
+ time = 2671000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 34:
+ time = 2695000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 35:
+ time = 2719000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 36:
+ time = 2743000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 37:
+ time = 2767000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 38:
+ time = 2791000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 39:
+ time = 2815000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 40:
+ time = 2839000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 41:
+ time = 2863000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-enabled.3.dump b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.3.dump
new file mode 100644
index 0000000..2209656
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.3.dump
@@ -0,0 +1,20 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description]
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3-enabled.unknown_length.dump b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.unknown_length.dump
new file mode 100644
index 0000000..c252057
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3-enabled.unknown_length.dump
@@ -0,0 +1,488 @@
+seekMap:
+ isSeekable = true
+ duration = 2808000
+ getPosition(0) = [[timeUs=0, position=39740]]
+ getPosition(1) = [[timeUs=0, position=39740]]
+ getPosition(1404000) = [[timeUs=1404000, position=58820]]
+ getPosition(2808000) = [[timeUs=2808000, position=77900]]
+numberOfTracks = 1
+track 0:
+ total output bytes = 38160
+ sample count = 117
+ format 0:
+ sampleMimeType = audio/mpeg
+ maxInputSize = 4096
+ channelCount = 2
+ sampleRate = 48000
+ encoderDelay = 576
+ encoderPadding = 576
+ metadata = entries=[TIT2: description=null: value=Test title, TPE1: description=null: value=Test Artist, TALB: description=null: value=Test Album, TXXX: description=Test description: value=Test user info, COMM: language=eng, description=Test description, WXXX: url=Test URL, TSSE: description=null: value=Lavf58.29.100, MLLT, PRIV: owner=test@gmail.com, UNKN, GEOB: mimeType=test/mime, filename=Testfilename.txt, description=Test description, CHAP, CHAP, CTOC, APIC: mimeType=image/jpeg, description=Test description]
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 96, hash 1F161542
+ sample 1:
+ time = 24000
+ flags = 1
+ data = length 768, hash CD1DC50F
+ sample 2:
+ time = 48000
+ flags = 1
+ data = length 336, hash 3F64124B
+ sample 3:
+ time = 72000
+ flags = 1
+ data = length 336, hash 8FFED94E
+ sample 4:
+ time = 96000
+ flags = 1
+ data = length 288, hash 9CD77D47
+ sample 5:
+ time = 120000
+ flags = 1
+ data = length 384, hash 24607BB5
+ sample 6:
+ time = 144000
+ flags = 1
+ data = length 480, hash 4937EBAB
+ sample 7:
+ time = 168000
+ flags = 1
+ data = length 336, hash 546342B1
+ sample 8:
+ time = 192000
+ flags = 1
+ data = length 336, hash 79E0923F
+ sample 9:
+ time = 216000
+ flags = 1
+ data = length 336, hash AB1F3948
+ sample 10:
+ time = 240000
+ flags = 1
+ data = length 336, hash C3A4D888
+ sample 11:
+ time = 264000
+ flags = 1
+ data = length 288, hash 7867DA45
+ sample 12:
+ time = 288000
+ flags = 1
+ data = length 336, hash B1240B73
+ sample 13:
+ time = 312000
+ flags = 1
+ data = length 336, hash 94CFCD35
+ sample 14:
+ time = 336000
+ flags = 1
+ data = length 288, hash 94F412C
+ sample 15:
+ time = 360000
+ flags = 1
+ data = length 336, hash A1D9FF41
+ sample 16:
+ time = 384000
+ flags = 1
+ data = length 288, hash 2A8DA21B
+ sample 17:
+ time = 408000
+ flags = 1
+ data = length 336, hash 6A429CE
+ sample 18:
+ time = 432000
+ flags = 1
+ data = length 336, hash 68853982
+ sample 19:
+ time = 456000
+ flags = 1
+ data = length 384, hash 1D6F779C
+ sample 20:
+ time = 480000
+ flags = 1
+ data = length 480, hash 6B31EBEE
+ sample 21:
+ time = 504000
+ flags = 1
+ data = length 336, hash 888335BE
+ sample 22:
+ time = 528000
+ flags = 1
+ data = length 336, hash 6072AC8B
+ sample 23:
+ time = 552000
+ flags = 1
+ data = length 336, hash C9D24234
+ sample 24:
+ time = 576000
+ flags = 1
+ data = length 288, hash 52BF4D1E
+ sample 25:
+ time = 600000
+ flags = 1
+ data = length 336, hash F93F4F0
+ sample 26:
+ time = 624000
+ flags = 1
+ data = length 336, hash 8617688A
+ sample 27:
+ time = 648000
+ flags = 1
+ data = length 480, hash FAB0D31B
+ sample 28:
+ time = 672000
+ flags = 1
+ data = length 384, hash FA4B53E2
+ sample 29:
+ time = 696000
+ flags = 1
+ data = length 336, hash 8C435F6A
+ sample 30:
+ time = 720000
+ flags = 1
+ data = length 336, hash 60D3F80C
+ sample 31:
+ time = 744000
+ flags = 1
+ data = length 336, hash DC15B68B
+ sample 32:
+ time = 768000
+ flags = 1
+ data = length 288, hash FF3DF141
+ sample 33:
+ time = 792000
+ flags = 1
+ data = length 336, hash A64B3042
+ sample 34:
+ time = 816000
+ flags = 1
+ data = length 336, hash ACA622A1
+ sample 35:
+ time = 840000
+ flags = 1
+ data = length 288, hash 3E34B8D4
+ sample 36:
+ time = 864000
+ flags = 1
+ data = length 288, hash 9B96F72A
+ sample 37:
+ time = 888000
+ flags = 1
+ data = length 336, hash E917C122
+ sample 38:
+ time = 912000
+ flags = 1
+ data = length 336, hash 10ED1470
+ sample 39:
+ time = 936000
+ flags = 1
+ data = length 288, hash 706B8A7C
+ sample 40:
+ time = 960000
+ flags = 1
+ data = length 336, hash 71FFE4A0
+ sample 41:
+ time = 984000
+ flags = 1
+ data = length 336, hash D4160463
+ sample 42:
+ time = 1008000
+ flags = 1
+ data = length 336, hash EC557B14
+ sample 43:
+ time = 1032000
+ flags = 1
+ data = length 288, hash 5598CF8B
+ sample 44:
+ time = 1056000
+ flags = 1
+ data = length 336, hash 7E0AB41
+ sample 45:
+ time = 1080000
+ flags = 1
+ data = length 336, hash 1C585FEF
+ sample 46:
+ time = 1104000
+ flags = 1
+ data = length 336, hash A4A4855E
+ sample 47:
+ time = 1128000
+ flags = 1
+ data = length 336, hash CECA51D3
+ sample 48:
+ time = 1152000
+ flags = 1
+ data = length 288, hash 2D362DC5
+ sample 49:
+ time = 1176000
+ flags = 1
+ data = length 336, hash 9EB2609D
+ sample 50:
+ time = 1200000
+ flags = 1
+ data = length 336, hash 28FFB3FE
+ sample 51:
+ time = 1224000
+ flags = 1
+ data = length 288, hash 2AA2D216
+ sample 52:
+ time = 1248000
+ flags = 1
+ data = length 336, hash CDBC7032
+ sample 53:
+ time = 1272000
+ flags = 1
+ data = length 336, hash 25B13FE7
+ sample 54:
+ time = 1296000
+ flags = 1
+ data = length 336, hash DB6BB1E
+ sample 55:
+ time = 1320000
+ flags = 1
+ data = length 336, hash EBE951F4
+ sample 56:
+ time = 1344000
+ flags = 1
+ data = length 288, hash 9E2EBFF7
+ sample 57:
+ time = 1368000
+ flags = 1
+ data = length 336, hash 36A7D455
+ sample 58:
+ time = 1392000
+ flags = 1
+ data = length 336, hash 84545F8C
+ sample 59:
+ time = 1416000
+ flags = 1
+ data = length 336, hash F66F3045
+ sample 60:
+ time = 1440000
+ flags = 1
+ data = length 576, hash 5AB089EA
+ sample 61:
+ time = 1464000
+ flags = 1
+ data = length 336, hash 8868086
+ sample 62:
+ time = 1488000
+ flags = 1
+ data = length 336, hash D5EB6D63
+ sample 63:
+ time = 1512000
+ flags = 1
+ data = length 288, hash 7A5374B7
+ sample 64:
+ time = 1536000
+ flags = 1
+ data = length 336, hash BEB27A75
+ sample 65:
+ time = 1560000
+ flags = 1
+ data = length 336, hash E251E0FD
+ sample 66:
+ time = 1584000
+ flags = 1
+ data = length 288, hash D54C970
+ sample 67:
+ time = 1608000
+ flags = 1
+ data = length 336, hash 52C473B9
+ sample 68:
+ time = 1632000
+ flags = 1
+ data = length 336, hash F5F13334
+ sample 69:
+ time = 1656000
+ flags = 1
+ data = length 480, hash A5F1E987
+ sample 70:
+ time = 1680000
+ flags = 1
+ data = length 288, hash 453A1267
+ sample 71:
+ time = 1704000
+ flags = 1
+ data = length 288, hash 7C6C2EA9
+ sample 72:
+ time = 1728000
+ flags = 1
+ data = length 336, hash F4BFECA4
+ sample 73:
+ time = 1752000
+ flags = 1
+ data = length 336, hash 751A395A
+ sample 74:
+ time = 1776000
+ flags = 1
+ data = length 336, hash EE38DB02
+ sample 75:
+ time = 1800000
+ flags = 1
+ data = length 336, hash F18837E2
+ sample 76:
+ time = 1824000
+ flags = 1
+ data = length 336, hash ED36B78E
+ sample 77:
+ time = 1848000
+ flags = 1
+ data = length 336, hash B3D28289
+ sample 78:
+ time = 1872000
+ flags = 1
+ data = length 288, hash 8BDE28E1
+ sample 79:
+ time = 1896000
+ flags = 1
+ data = length 336, hash CFD5E966
+ sample 80:
+ time = 1920000
+ flags = 1
+ data = length 288, hash DC08E267
+ sample 81:
+ time = 1944000
+ flags = 1
+ data = length 336, hash 6530CB78
+ sample 82:
+ time = 1968000
+ flags = 1
+ data = length 336, hash 6CC6636E
+ sample 83:
+ time = 1992000
+ flags = 1
+ data = length 336, hash 613047C1
+ sample 84:
+ time = 2016000
+ flags = 1
+ data = length 288, hash CDC747BF
+ sample 85:
+ time = 2040000
+ flags = 1
+ data = length 336, hash AF22AA74
+ sample 86:
+ time = 2064000
+ flags = 1
+ data = length 384, hash 82F326AA
+ sample 87:
+ time = 2088000
+ flags = 1
+ data = length 384, hash EDA26C4D
+ sample 88:
+ time = 2112000
+ flags = 1
+ data = length 336, hash 94C643DC
+ sample 89:
+ time = 2136000
+ flags = 1
+ data = length 288, hash CB5D9C40
+ sample 90:
+ time = 2160000
+ flags = 1
+ data = length 336, hash 1E69DE3F
+ sample 91:
+ time = 2184000
+ flags = 1
+ data = length 336, hash 7E472219
+ sample 92:
+ time = 2208000
+ flags = 1
+ data = length 336, hash DA47B9FA
+ sample 93:
+ time = 2232000
+ flags = 1
+ data = length 336, hash DD0ABB7C
+ sample 94:
+ time = 2256000
+ flags = 1
+ data = length 288, hash DBF93FAC
+ sample 95:
+ time = 2280000
+ flags = 1
+ data = length 336, hash 243F4B2
+ sample 96:
+ time = 2304000
+ flags = 1
+ data = length 336, hash 2E881490
+ sample 97:
+ time = 2328000
+ flags = 1
+ data = length 288, hash 1C28C8BE
+ sample 98:
+ time = 2352000
+ flags = 1
+ data = length 336, hash C73E5D30
+ sample 99:
+ time = 2376000
+ flags = 1
+ data = length 288, hash 98B5BFF6
+ sample 100:
+ time = 2400000
+ flags = 1
+ data = length 336, hash E0135533
+ sample 101:
+ time = 2424000
+ flags = 1
+ data = length 336, hash D13C9DBC
+ sample 102:
+ time = 2448000
+ flags = 1
+ data = length 336, hash 63D524CA
+ sample 103:
+ time = 2472000
+ flags = 1
+ data = length 288, hash A28514C3
+ sample 104:
+ time = 2496000
+ flags = 1
+ data = length 336, hash 72B647FF
+ sample 105:
+ time = 2520000
+ flags = 1
+ data = length 336, hash 8F740AB1
+ sample 106:
+ time = 2544000
+ flags = 1
+ data = length 336, hash 5E3C7E93
+ sample 107:
+ time = 2568000
+ flags = 1
+ data = length 336, hash 121B913B
+ sample 108:
+ time = 2592000
+ flags = 1
+ data = length 336, hash 578FCCF2
+ sample 109:
+ time = 2616000
+ flags = 1
+ data = length 336, hash 5B5823DE
+ sample 110:
+ time = 2640000
+ flags = 1
+ data = length 384, hash D8B83F78
+ sample 111:
+ time = 2664000
+ flags = 1
+ data = length 240, hash E649682F
+ sample 112:
+ time = 2688000
+ flags = 1
+ data = length 96, hash C559A6F4
+ sample 113:
+ time = 2712000
+ flags = 1
+ data = length 96, hash 792796BC
+ sample 114:
+ time = 2736000
+ flags = 1
+ data = length 120, hash 8172CD0E
+ sample 115:
+ time = 2760000
+ flags = 1
+ data = length 120, hash F562B52F
+ sample 116:
+ time = 2784000
+ flags = 1
+ data = length 96, hash FF8D5B98
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/mp3/bear-id3.mp3 b/tree/testdata/src/test/assets/mp3/bear-id3.mp3
new file mode 100644
index 0000000..9bd4f72
--- /dev/null
+++ b/tree/testdata/src/test/assets/mp3/bear-id3.mp3
Binary files differ
diff --git a/tree/testdata/src/test/assets/mpd/sample_mpd_trick_play b/tree/testdata/src/test/assets/mpd/sample_mpd_trick_play
new file mode 100644
index 0000000..b35c906
--- /dev/null
+++ b/tree/testdata/src/test/assets/mpd/sample_mpd_trick_play
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<MPD type="static" duration="1s" mediaPresentationDuration="PT1S">
+ <Period>
+ <SegmentTemplate startNumber="0" timescale="1000" media="sq/$Number$">
+ <SegmentTimeline>
+ <S d="1000"/>
+ </SegmentTimeline>
+ </SegmentTemplate>
+ <AdaptationSet id="0" mimeType="video/mp4" subsegmentAlignment="true">
+ <Representation id="0" codecs="avc1.42c01f" bandwidth="128000">
+ <BaseURL>https://test.com/0</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="1" mimeType="video/mp4" subsegmentAlignment="true">
+ <Representation id="0" codecs="avc1.42c01f" bandwidth="128000">
+ <BaseURL>https://test.com/0</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="2" mimeType="video/mp4" subsegmentAlignment="true">
+ <EssentialProperty schemeIdUri="http://dashif.org/guidelines/trickmode" value="0"/>
+ <Representation id="0" codecs="avc1.42c01f" bandwidth="128000">
+ <BaseURL>https://test.com/0</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ <AdaptationSet id="3" mimeType="video/mp4" subsegmentAlignment="true">
+ <SupplementalProperty schemeIdUri="http://dashif.org/guidelines/trickmode" value="1"/>
+ <Representation id="0" codecs="avc1.42c01f" bandwidth="128000">
+ <BaseURL>https://test.com/0</BaseURL>
+ </Representation>
+ </AdaptationSet>
+ </Period>
+</MPD>
diff --git a/tree/testdata/src/test/assets/ts/sample_h265.ts b/tree/testdata/src/test/assets/ts/sample_h265.ts
new file mode 100644
index 0000000..483010f
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h265.ts
Binary files differ
diff --git a/tree/testdata/src/test/assets/ts/sample_h265.ts.0.dump b/tree/testdata/src/test/assets/ts/sample_h265.ts.0.dump
new file mode 100644
index 0000000..1c06207
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h265.ts.0.dump
@@ -0,0 +1,141 @@
+seekMap:
+ isSeekable = true
+ duration = 900000
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(450000) = [[timeUs=450000, position=11421]]
+ getPosition(900000) = [[timeUs=900000, position=23030]]
+numberOfTracks = 2
+track 256:
+ total output bytes = 19364
+ sample count = 29
+ format 0:
+ id = 1/256
+ sampleMimeType = video/hevc
+ width = 854
+ height = 480
+ initializationData:
+ data = length 83, hash 7F428
+ sample 0:
+ time = 66666
+ flags = 1
+ data = length 2510, hash 796A98BE
+ sample 1:
+ time = 100000
+ flags = 0
+ data = length 1219, hash 131AA4E4
+ sample 2:
+ time = 266666
+ flags = 0
+ data = length 7810, hash 3F881DB9
+ sample 3:
+ time = 200000
+ flags = 0
+ data = length 2306, hash 9A77959C
+ sample 4:
+ time = 133333
+ flags = 0
+ data = length 1058, hash B887F7EF
+ sample 5:
+ time = 166666
+ flags = 0
+ data = length 98, hash D95BF6E3
+ sample 6:
+ time = 233333
+ flags = 0
+ data = length 61, hash 574C41C3
+ sample 7:
+ time = 433333
+ flags = 0
+ data = length 296, hash E92DB288
+ sample 8:
+ time = 366666
+ flags = 0
+ data = length 137, hash 586DADD6
+ sample 9:
+ time = 300000
+ flags = 0
+ data = length 218, hash 91E82C9F
+ sample 10:
+ time = 333333
+ flags = 0
+ data = length 177, hash 4A4FEEC0
+ sample 11:
+ time = 400000
+ flags = 0
+ data = length 82, hash 2E2ADD8
+ sample 12:
+ time = 533333
+ flags = 0
+ data = length 290, hash 63CF7D90
+ sample 13:
+ time = 500000
+ flags = 0
+ data = length 268, hash E8CBAC11
+ sample 14:
+ time = 466666
+ flags = 0
+ data = length 178, hash C5B1613E
+ sample 15:
+ time = 566666
+ flags = 0
+ data = length 271, hash 76652FC5
+ sample 16:
+ time = 733333
+ flags = 0
+ data = length 257, hash 960B5DF4
+ sample 17:
+ time = 666666
+ flags = 0
+ data = length 206, hash 87358D00
+ sample 18:
+ time = 600000
+ flags = 0
+ data = length 130, hash 4D7A038D
+ sample 19:
+ time = 633333
+ flags = 0
+ data = length 114, hash 2B3C616E
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 95, hash 37D79559
+ sample 21:
+ time = 900000
+ flags = 0
+ data = length 233, hash 80308C9E
+ sample 22:
+ time = 833333
+ flags = 0
+ data = length 203, hash E70BA5F2
+ sample 23:
+ time = 766666
+ flags = 0
+ data = length 95, hash BA6FA2D3
+ sample 24:
+ time = 800000
+ flags = 0
+ data = length 103, hash 51291041
+ sample 25:
+ time = 866666
+ flags = 0
+ data = length 111, hash EE9DCFC9
+ sample 26:
+ time = 1033333
+ flags = 0
+ data = length 253, hash D0CEFBA7
+ sample 27:
+ time = 966666
+ flags = 0
+ data = length 134, hash 8802EEF0
+ sample 28:
+ time = 933333
+ flags = 0
+ data = length 80, hash C635D9C2
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h265.ts.1.dump b/tree/testdata/src/test/assets/ts/sample_h265.ts.1.dump
new file mode 100644
index 0000000..95d8d9b
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h265.ts.1.dump
@@ -0,0 +1,105 @@
+seekMap:
+ isSeekable = true
+ duration = 900000
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(450000) = [[timeUs=450000, position=11421]]
+ getPosition(900000) = [[timeUs=900000, position=23030]]
+numberOfTracks = 2
+track 256:
+ total output bytes = 3806
+ sample count = 20
+ format 0:
+ id = 1/256
+ sampleMimeType = video/hevc
+ width = 854
+ height = 480
+ initializationData:
+ data = length 83, hash 7F428
+ sample 0:
+ time = 300000
+ flags = 0
+ data = length 218, hash 91E82C9F
+ sample 1:
+ time = 333333
+ flags = 0
+ data = length 177, hash 4A4FEEC0
+ sample 2:
+ time = 400000
+ flags = 0
+ data = length 82, hash 2E2ADD8
+ sample 3:
+ time = 533333
+ flags = 0
+ data = length 290, hash 63CF7D90
+ sample 4:
+ time = 500000
+ flags = 0
+ data = length 268, hash E8CBAC11
+ sample 5:
+ time = 466666
+ flags = 0
+ data = length 178, hash C5B1613E
+ sample 6:
+ time = 566666
+ flags = 0
+ data = length 271, hash 76652FC5
+ sample 7:
+ time = 733333
+ flags = 0
+ data = length 257, hash 960B5DF4
+ sample 8:
+ time = 666666
+ flags = 0
+ data = length 206, hash 87358D00
+ sample 9:
+ time = 600000
+ flags = 0
+ data = length 130, hash 4D7A038D
+ sample 10:
+ time = 633333
+ flags = 0
+ data = length 114, hash 2B3C616E
+ sample 11:
+ time = 700000
+ flags = 0
+ data = length 95, hash 37D79559
+ sample 12:
+ time = 900000
+ flags = 0
+ data = length 233, hash 80308C9E
+ sample 13:
+ time = 833333
+ flags = 0
+ data = length 203, hash E70BA5F2
+ sample 14:
+ time = 766666
+ flags = 0
+ data = length 95, hash BA6FA2D3
+ sample 15:
+ time = 800000
+ flags = 0
+ data = length 103, hash 51291041
+ sample 16:
+ time = 866666
+ flags = 0
+ data = length 111, hash EE9DCFC9
+ sample 17:
+ time = 1033333
+ flags = 0
+ data = length 253, hash D0CEFBA7
+ sample 18:
+ time = 966666
+ flags = 0
+ data = length 134, hash 8802EEF0
+ sample 19:
+ time = 933333
+ flags = 0
+ data = length 80, hash C635D9C2
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h265.ts.2.dump b/tree/testdata/src/test/assets/ts/sample_h265.ts.2.dump
new file mode 100644
index 0000000..9080f5c
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h265.ts.2.dump
@@ -0,0 +1,69 @@
+seekMap:
+ isSeekable = true
+ duration = 900000
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(450000) = [[timeUs=450000, position=11421]]
+ getPosition(900000) = [[timeUs=900000, position=23030]]
+numberOfTracks = 2
+track 256:
+ total output bytes = 1796
+ sample count = 11
+ format 0:
+ id = 1/256
+ sampleMimeType = video/hevc
+ width = 854
+ height = 480
+ initializationData:
+ data = length 83, hash 7F428
+ sample 0:
+ time = 600000
+ flags = 0
+ data = length 130, hash 4D7A038D
+ sample 1:
+ time = 633333
+ flags = 0
+ data = length 114, hash 2B3C616E
+ sample 2:
+ time = 700000
+ flags = 0
+ data = length 95, hash 37D79559
+ sample 3:
+ time = 900000
+ flags = 0
+ data = length 233, hash 80308C9E
+ sample 4:
+ time = 833333
+ flags = 0
+ data = length 203, hash E70BA5F2
+ sample 5:
+ time = 766666
+ flags = 0
+ data = length 95, hash BA6FA2D3
+ sample 6:
+ time = 800000
+ flags = 0
+ data = length 103, hash 51291041
+ sample 7:
+ time = 866666
+ flags = 0
+ data = length 111, hash EE9DCFC9
+ sample 8:
+ time = 1033333
+ flags = 0
+ data = length 253, hash D0CEFBA7
+ sample 9:
+ time = 966666
+ flags = 0
+ data = length 134, hash 8802EEF0
+ sample 10:
+ time = 933333
+ flags = 0
+ data = length 80, hash C635D9C2
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h265.ts.3.dump b/tree/testdata/src/test/assets/ts/sample_h265.ts.3.dump
new file mode 100644
index 0000000..5fe7bce
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h265.ts.3.dump
@@ -0,0 +1,33 @@
+seekMap:
+ isSeekable = true
+ duration = 900000
+ getPosition(0) = [[timeUs=0, position=0]]
+ getPosition(1) = [[timeUs=1, position=0]]
+ getPosition(450000) = [[timeUs=450000, position=11421]]
+ getPosition(900000) = [[timeUs=900000, position=23030]]
+numberOfTracks = 2
+track 256:
+ total output bytes = 396
+ sample count = 2
+ format 0:
+ id = 1/256
+ sampleMimeType = video/hevc
+ width = 854
+ height = 480
+ initializationData:
+ data = length 83, hash 7F428
+ sample 0:
+ time = 966666
+ flags = 0
+ data = length 134, hash 8802EEF0
+ sample 1:
+ time = 933333
+ flags = 0
+ data = length 80, hash C635D9C2
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testdata/src/test/assets/ts/sample_h265.ts.unknown_length.dump b/tree/testdata/src/test/assets/ts/sample_h265.ts.unknown_length.dump
new file mode 100644
index 0000000..733be6f
--- /dev/null
+++ b/tree/testdata/src/test/assets/ts/sample_h265.ts.unknown_length.dump
@@ -0,0 +1,138 @@
+seekMap:
+ isSeekable = false
+ duration = UNSET TIME
+ getPosition(0) = [[timeUs=0, position=0]]
+numberOfTracks = 2
+track 256:
+ total output bytes = 19364
+ sample count = 29
+ format 0:
+ id = 1/256
+ sampleMimeType = video/hevc
+ width = 854
+ height = 480
+ initializationData:
+ data = length 83, hash 7F428
+ sample 0:
+ time = 66666
+ flags = 1
+ data = length 2510, hash 796A98BE
+ sample 1:
+ time = 100000
+ flags = 0
+ data = length 1219, hash 131AA4E4
+ sample 2:
+ time = 266666
+ flags = 0
+ data = length 7810, hash 3F881DB9
+ sample 3:
+ time = 200000
+ flags = 0
+ data = length 2306, hash 9A77959C
+ sample 4:
+ time = 133333
+ flags = 0
+ data = length 1058, hash B887F7EF
+ sample 5:
+ time = 166666
+ flags = 0
+ data = length 98, hash D95BF6E3
+ sample 6:
+ time = 233333
+ flags = 0
+ data = length 61, hash 574C41C3
+ sample 7:
+ time = 433333
+ flags = 0
+ data = length 296, hash E92DB288
+ sample 8:
+ time = 366666
+ flags = 0
+ data = length 137, hash 586DADD6
+ sample 9:
+ time = 300000
+ flags = 0
+ data = length 218, hash 91E82C9F
+ sample 10:
+ time = 333333
+ flags = 0
+ data = length 177, hash 4A4FEEC0
+ sample 11:
+ time = 400000
+ flags = 0
+ data = length 82, hash 2E2ADD8
+ sample 12:
+ time = 533333
+ flags = 0
+ data = length 290, hash 63CF7D90
+ sample 13:
+ time = 500000
+ flags = 0
+ data = length 268, hash E8CBAC11
+ sample 14:
+ time = 466666
+ flags = 0
+ data = length 178, hash C5B1613E
+ sample 15:
+ time = 566666
+ flags = 0
+ data = length 271, hash 76652FC5
+ sample 16:
+ time = 733333
+ flags = 0
+ data = length 257, hash 960B5DF4
+ sample 17:
+ time = 666666
+ flags = 0
+ data = length 206, hash 87358D00
+ sample 18:
+ time = 600000
+ flags = 0
+ data = length 130, hash 4D7A038D
+ sample 19:
+ time = 633333
+ flags = 0
+ data = length 114, hash 2B3C616E
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 95, hash 37D79559
+ sample 21:
+ time = 900000
+ flags = 0
+ data = length 233, hash 80308C9E
+ sample 22:
+ time = 833333
+ flags = 0
+ data = length 203, hash E70BA5F2
+ sample 23:
+ time = 766666
+ flags = 0
+ data = length 95, hash BA6FA2D3
+ sample 24:
+ time = 800000
+ flags = 0
+ data = length 103, hash 51291041
+ sample 25:
+ time = 866666
+ flags = 0
+ data = length 111, hash EE9DCFC9
+ sample 26:
+ time = 1033333
+ flags = 0
+ data = length 253, hash D0CEFBA7
+ sample 27:
+ time = 966666
+ flags = 0
+ data = length 134, hash 8802EEF0
+ sample 28:
+ time = 933333
+ flags = 0
+ data = length 80, hash C635D9C2
+track 8448:
+ total output bytes = 0
+ sample count = 0
+ format 0:
+ id = 1/8448
+ sampleMimeType = application/cea-608
+tracksEnded = true
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java
index dff69c1..269a992 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/ExtractorAsserts.java
@@ -236,86 +236,6 @@
return extractorOutput;
}
- /**
- * Calls {@link #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)} with all
- * possible combinations of "simulate" parameters.
- *
- * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor}
- * class which is to be tested.
- * @param sampleFile The path to the input sample.
- * @param context To be used to load the sample file.
- * @param expectedThrowable Expected {@link Throwable} class.
- * @throws IOException If reading from the input fails.
- * @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)
- */
- public static void assertThrows(
- ExtractorFactory factory,
- String sampleFile,
- Context context,
- Class<? extends Throwable> expectedThrowable)
- throws IOException {
- byte[] fileData = TestUtil.getByteArray(context, sampleFile);
- assertThrows(factory, fileData, expectedThrowable);
- }
-
- /**
- * Calls {@link #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)} with all
- * possible combinations of "simulate" parameters.
- *
- * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor}
- * class which is to be tested.
- * @param fileData Content of the input file.
- * @param expectedThrowable Expected {@link Throwable} class.
- * @throws IOException If reading from the input fails.
- * @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)
- */
- private static void assertThrows(
- ExtractorFactory factory, byte[] fileData, Class<? extends Throwable> expectedThrowable)
- throws IOException {
- assertThrows(factory.create(), fileData, expectedThrowable, false, false, false);
- assertThrows(factory.create(), fileData, expectedThrowable, true, false, false);
- assertThrows(factory.create(), fileData, expectedThrowable, false, true, false);
- assertThrows(factory.create(), fileData, expectedThrowable, true, true, false);
- assertThrows(factory.create(), fileData, expectedThrowable, false, false, true);
- assertThrows(factory.create(), fileData, expectedThrowable, true, false, true);
- assertThrows(factory.create(), fileData, expectedThrowable, false, true, true);
- assertThrows(factory.create(), fileData, expectedThrowable, true, true, true);
- }
-
- /**
- * Asserts {@code extractor} throws {@code expectedThrowable} while consuming {@code fileData}.
- *
- * @param extractor The {@link Extractor} to be tested.
- * @param fileData Content of the input file.
- * @param expectedThrowable Expected {@link Throwable} class.
- * @param simulateIOErrors If true simulates IOErrors.
- * @param simulateUnknownLength If true simulates unknown input length.
- * @param simulatePartialReads If true simulates partial reads.
- * @throws IOException If reading from the input fails.
- */
- private static void assertThrows(
- Extractor extractor,
- byte[] fileData,
- Class<? extends Throwable> expectedThrowable,
- boolean simulateIOErrors,
- boolean simulateUnknownLength,
- boolean simulatePartialReads)
- throws IOException {
- FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData)
- .setSimulateIOErrors(simulateIOErrors)
- .setSimulateUnknownLength(simulateUnknownLength)
- .setSimulatePartialReads(simulatePartialReads).build();
- try {
- consumeTestData(extractor, input, 0, true);
- throw new AssertionError(expectedThrowable.getSimpleName() + " expected but not thrown");
- } catch (Throwable throwable) {
- if (expectedThrowable.equals(throwable.getClass())) {
- return; // Pass!
- }
- throw throwable;
- }
- }
-
private ExtractorAsserts() {}
private static FakeExtractorOutput consumeTestData(
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java
index c3c4d5c..ee30b90 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java
@@ -18,6 +18,7 @@
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
+import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
@@ -29,16 +30,31 @@
private final List<Long> wakeUpTimes;
private final List<HandlerMessageData> handlerMessages;
+ private final long bootTimeMs;
- private long currentTimeMs;
+ @GuardedBy("this")
+ private long timeSinceBootMs;
/**
- * Create {@link FakeClock} with an arbitrary initial timestamp.
+ * Creates a fake clock assuming the system was booted exactly at time {@code 0} (the Unix Epoch)
+ * and {@code initialTimeMs} milliseconds have passed since system boot.
*
- * @param initialTimeMs Initial timestamp in milliseconds.
+ * @param initialTimeMs The initial elapsed time since the boot time, in milliseconds.
*/
public FakeClock(long initialTimeMs) {
- this.currentTimeMs = initialTimeMs;
+ this(/* bootTimeMs= */ 0, initialTimeMs);
+ }
+
+ /**
+ * Creates a fake clock specifying when the system was booted and how much time has passed since
+ * then.
+ *
+ * @param bootTimeMs The time the system was booted since the Unix Epoch, in milliseconds.
+ * @param initialTimeMs The initial elapsed time since the boot time, in milliseconds.
+ */
+ public FakeClock(long bootTimeMs, long initialTimeMs) {
+ this.bootTimeMs = bootTimeMs;
+ this.timeSinceBootMs = initialTimeMs;
this.wakeUpTimes = new ArrayList<>();
this.handlerMessages = new ArrayList<>();
}
@@ -49,23 +65,28 @@
* @param timeDiffMs The amount of time to add to the timestamp in milliseconds.
*/
public synchronized void advanceTime(long timeDiffMs) {
- currentTimeMs += timeDiffMs;
+ timeSinceBootMs += timeDiffMs;
for (Long wakeUpTime : wakeUpTimes) {
- if (wakeUpTime <= currentTimeMs) {
+ if (wakeUpTime <= timeSinceBootMs) {
notifyAll();
break;
}
}
for (int i = handlerMessages.size() - 1; i >= 0; i--) {
- if (handlerMessages.get(i).maybeSendToTarget(currentTimeMs)) {
+ if (handlerMessages.get(i).maybeSendToTarget(timeSinceBootMs)) {
handlerMessages.remove(i);
}
}
}
@Override
+ public synchronized long currentTimeMillis() {
+ return bootTimeMs + timeSinceBootMs;
+ }
+
+ @Override
public synchronized long elapsedRealtime() {
- return currentTimeMs;
+ return timeSinceBootMs;
}
@Override
@@ -78,9 +99,9 @@
if (sleepTimeMs <= 0) {
return;
}
- Long wakeUpTimeMs = currentTimeMs + sleepTimeMs;
+ Long wakeUpTimeMs = timeSinceBootMs + sleepTimeMs;
wakeUpTimes.add(wakeUpTimeMs);
- while (currentTimeMs < wakeUpTimeMs) {
+ while (timeSinceBootMs < wakeUpTimeMs) {
try {
wait();
} catch (InterruptedException e) {
@@ -98,7 +119,7 @@
/** Adds a handler post to list of pending messages. */
protected synchronized boolean addHandlerMessageAtTime(
HandlerWrapper handler, Runnable runnable, long timeMs) {
- if (timeMs <= currentTimeMs) {
+ if (timeMs <= timeSinceBootMs) {
return handler.post(runnable);
}
handlerMessages.add(new HandlerMessageData(timeMs, handler, runnable));
@@ -108,7 +129,7 @@
/** Adds an empty handler message to list of pending messages. */
protected synchronized boolean addHandlerMessageAtTime(
HandlerWrapper handler, int message, long timeMs) {
- if (timeMs <= currentTimeMs) {
+ if (timeMs <= timeSinceBootMs) {
return handler.sendEmptyMessage(message);
}
handlerMessages.add(new HandlerMessageData(timeMs, handler, message));
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java
index 46b5b5f..b3c6bf7 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackOutput.java
@@ -23,6 +23,7 @@
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.Dumper.Dumpable;
import com.google.android.exoplayer2.upstream.DataReader;
+import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Function;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
@@ -44,6 +45,7 @@
private byte[] sampleData;
private int formatCount;
+ private boolean receivedSampleInFormat;
@Nullable public Format lastFormat;
@@ -52,6 +54,7 @@
dumpables = new ArrayList<>();
sampleData = Util.EMPTY_BYTE_ARRAY;
formatCount = 0;
+ receivedSampleInFormat = true;
}
public void clear() {
@@ -59,10 +62,12 @@
dumpables.clear();
sampleData = Util.EMPTY_BYTE_ARRAY;
formatCount = 0;
+ receivedSampleInFormat = true;
}
@Override
public void format(Format format) {
+ receivedSampleInFormat = false;
addFormat(format);
}
@@ -95,6 +100,7 @@
int size,
int offset,
@Nullable CryptoData cryptoData) {
+ receivedSampleInFormat = true;
if (lastFormat == null) {
throw new IllegalStateException("TrackOutput must receive format before sampleMetadata");
}
diff --git a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java
index d0dc1df..ad36635 100644
--- a/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java
+++ b/tree/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestExoPlayer.java
@@ -41,6 +41,7 @@
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Utilities to write unit/integration tests with a SimpleExoPlayer instance that uses fake
@@ -77,7 +78,7 @@
@Nullable private Renderer[] renderers;
@Nullable private RenderersFactory renderersFactory;
private boolean useLazyPreparation;
- private Looper looper;
+ private @MonotonicNonNull Looper looper;
public Builder(Context context) {
this.context = context;
@@ -85,7 +86,10 @@
trackSelector = new DefaultTrackSelector(context);
loadControl = new DefaultLoadControl();
bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
- looper = Assertions.checkNotNull(Looper.myLooper());
+ @Nullable Looper myLooper = Looper.myLooper();
+ if (myLooper != null) {
+ looper = myLooper;
+ }
}
/**
@@ -234,7 +238,11 @@
return this;
}
- /** Returns the {@link Looper} that will be used by the player. */
+ /**
+ * Returns the {@link Looper} that will be used by the player, or null if no {@link Looper} has
+ * been set yet and no default is available.
+ */
+ @Nullable
public Looper getLooper() {
return looper;
}
@@ -245,6 +253,8 @@
* @return The built {@link ExoPlayerTestRunner}.
*/
public SimpleExoPlayer build() {
+ Assertions.checkNotNull(
+ looper, "TestExoPlayer builder run on a thread without Looper and no Looper specified.");
// Do not update renderersFactory and renderers here, otherwise their getters may
// return different values before and after build() is called, making them confusing.
RenderersFactory playerRenderersFactory = renderersFactory;
diff --git a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java
index 666e45c..41cbf8a 100644
--- a/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java
+++ b/tree/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java
@@ -36,6 +36,25 @@
private static final long TIMEOUT_MS = 10000;
@Test
+ public void currentTimeMillis_withoutBootTime() {
+ FakeClock fakeClock = new FakeClock(/* initialTimeMs= */ 10);
+ assertThat(fakeClock.currentTimeMillis()).isEqualTo(10);
+ }
+
+ @Test
+ public void currentTimeMillis_withBootTime() {
+ FakeClock fakeClock = new FakeClock(/* bootTimeMs= */ 150, /* initialTimeMs= */ 200);
+ assertThat(fakeClock.currentTimeMillis()).isEqualTo(350);
+ }
+
+ @Test
+ public void currentTimeMillis_advanceTime_currentTimeHasAdvanced() {
+ FakeClock fakeClock = new FakeClock(/* bootTimeMs= */ 100, /* initialTimeMs= */ 50);
+ fakeClock.advanceTime(/* timeDiffMs */ 250);
+ assertThat(fakeClock.currentTimeMillis()).isEqualTo(400);
+ }
+
+ @Test
public void testAdvanceTime() {
FakeClock fakeClock = new FakeClock(2000);
assertThat(fakeClock.elapsedRealtime()).isEqualTo(2000);