diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 1d6df24d99..066614ec72 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -2,6 +2,7 @@
### dev-v2 (not yet released) ###
+* Optimize seeking in FMP4.
* Match codecs starting with "mp4a" to different Audio MimeTypes
([#3779](https://github.com/google/ExoPlayer/issues/3779)).
* Moved initial bitrate estimate from `AdaptiveTrackSelection` to
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
index 1cce86c525..d30355699c 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java
@@ -134,8 +134,6 @@ public final class FragmentedMp4Extractor implements Extractor {
private final ParsableByteArray nalStartCode;
private final ParsableByteArray nalPrefix;
private final ParsableByteArray nalBuffer;
- private final ParsableByteArray encryptionSignalByte;
- private final ParsableByteArray defaultInitializationVector;
// Adjusts sample timestamps.
private final TimestampAdjuster timestampAdjuster;
@@ -154,6 +152,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private ParsableByteArray atomData;
private long endOfMdatPosition;
private int pendingMetadataSampleBytes;
+ private long pendingSeekTimeUs;
private long durationUs;
private long segmentIndexEarliestPresentationTimeUs;
@@ -246,13 +245,12 @@ public final class FragmentedMp4Extractor implements Extractor {
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5);
nalBuffer = new ParsableByteArray();
- encryptionSignalByte = new ParsableByteArray(1);
- defaultInitializationVector = new ParsableByteArray();
extendedTypeScratch = new byte[16];
containerAtoms = new Stack<>();
pendingMetadataSampleInfos = new ArrayDeque<>();
trackBundles = new SparseArray<>();
durationUs = C.TIME_UNSET;
+ pendingSeekTimeUs = C.TIME_UNSET;
segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
enterReadingAtomHeaderState();
}
@@ -282,6 +280,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
pendingMetadataSampleInfos.clear();
pendingMetadataSampleBytes = 0;
+ pendingSeekTimeUs = timeUs;
containerAtoms.clear();
enterReadingAtomHeaderState();
}
@@ -516,6 +515,14 @@ public final class FragmentedMp4Extractor implements Extractor {
trackBundles.valueAt(i).updateDrmInitData(drmInitData);
}
}
+ // If we have a pending seek, advance tracks to their preceding sync frames.
+ if (pendingSeekTimeUs != C.TIME_UNSET) {
+ int trackCount = trackBundles.size();
+ for (int i = 0; i < trackCount; i++) {
+ trackBundles.valueAt(i).seek(pendingSeekTimeUs);
+ }
+ pendingSeekTimeUs = C.TIME_UNSET;
+ }
}
private void maybeInitExtraTracks() {
@@ -1097,16 +1104,18 @@ public final class FragmentedMp4Extractor implements Extractor {
}
/**
- * Attempts to extract the next sample in the current mdat atom.
- *
- * If there are no more samples in the current mdat atom then the parser state is transitioned
+ * Attempts to read the next sample in the current mdat atom. The read sample may be output or
+ * skipped.
+ *
+ *
If there are no more samples in the current mdat atom then the parser state is transitioned
* to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
- *
- * It is possible for a sample to be extracted in part in the case that an exception is thrown. In
- * this case the method can be called again to extract the remainder of the sample.
+ *
+ *
It is possible for a sample to be partially read in the case that an exception is thrown. In
+ * this case the method can be called again to read the remainder of the sample.
*
* @param input The {@link ExtractorInput} from which to read data.
- * @return Whether a sample was extracted.
+ * @return Whether a sample was read. The read sample may have been output or skipped. False
+ * indicates that there are no samples left to read in the current mdat.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
@@ -1138,18 +1147,26 @@ public final class FragmentedMp4Extractor implements Extractor {
input.skipFully(bytesToSkip);
this.currentTrackBundle = currentTrackBundle;
}
+
sampleSize = currentTrackBundle.fragment
.sampleSizeTable[currentTrackBundle.currentSampleIndex];
+
+ if (currentTrackBundle.currentSampleIndex < currentTrackBundle.firstSampleToOutputIndex) {
+ input.skipFully(sampleSize);
+ currentTrackBundle.skipSampleEncryptionData();
+ if (!currentTrackBundle.next()) {
+ currentTrackBundle = null;
+ }
+ parserState = STATE_READING_SAMPLE_START;
+ return true;
+ }
+
if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE);
}
- if (currentTrackBundle.fragment.definesEncryptionData) {
- sampleBytesWritten = appendSampleEncryptionData(currentTrackBundle);
- sampleSize += sampleBytesWritten;
- } else {
- sampleBytesWritten = 0;
- }
+ sampleBytesWritten = currentTrackBundle.outputSampleEncryptionData();
+ sampleSize += sampleBytesWritten;
parserState = STATE_READING_SAMPLE_CONTINUE;
sampleCurrentNalBytesRemaining = 0;
}
@@ -1237,13 +1254,7 @@ public final class FragmentedMp4Extractor implements Extractor {
// After we have the sampleTimeUs, we can commit all the pending metadata samples
outputPendingMetadataSamples(sampleTimeUs);
-
- currentTrackBundle.currentSampleIndex++;
- currentTrackBundle.currentSampleInTrackRun++;
- if (currentTrackBundle.currentSampleInTrackRun
- == fragment.trunLength[currentTrackBundle.currentTrackRunIndex]) {
- currentTrackBundle.currentTrackRunIndex++;
- currentTrackBundle.currentSampleInTrackRun = 0;
+ if (!currentTrackBundle.next()) {
currentTrackBundle = null;
}
parserState = STATE_READING_SAMPLE_START;
@@ -1286,57 +1297,6 @@ public final class FragmentedMp4Extractor implements Extractor {
return nextTrackBundle;
}
- /**
- * Appends the corresponding encryption data to the {@link TrackOutput} contained in the given
- * {@link TrackBundle}.
- *
- * @param trackBundle The {@link TrackBundle} that contains the {@link Track} for which the
- * Sample encryption data must be output.
- * @return The number of written bytes.
- */
- private int appendSampleEncryptionData(TrackBundle trackBundle) {
- TrackFragment trackFragment = trackBundle.fragment;
- int sampleDescriptionIndex = trackFragment.header.sampleDescriptionIndex;
- TrackEncryptionBox encryptionBox = trackFragment.trackEncryptionBox != null
- ? trackFragment.trackEncryptionBox
- : trackBundle.track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex);
-
- ParsableByteArray initializationVectorData;
- int vectorSize;
- if (encryptionBox.initializationVectorSize != 0) {
- initializationVectorData = trackFragment.sampleEncryptionData;
- vectorSize = encryptionBox.initializationVectorSize;
- } else {
- // The default initialization vector should be used.
- byte[] initVectorData = encryptionBox.defaultInitializationVector;
- defaultInitializationVector.reset(initVectorData, initVectorData.length);
- initializationVectorData = defaultInitializationVector;
- vectorSize = initVectorData.length;
- }
-
- boolean subsampleEncryption = trackFragment
- .sampleHasSubsampleEncryptionTable[trackBundle.currentSampleIndex];
-
- // Write the signal byte, containing the vector size and the subsample encryption flag.
- encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0));
- encryptionSignalByte.setPosition(0);
- TrackOutput output = trackBundle.output;
- output.sampleData(encryptionSignalByte, 1);
- // Write the vector.
- output.sampleData(initializationVectorData, vectorSize);
- // If we don't have subsample encryption data, we're done.
- if (!subsampleEncryption) {
- return 1 + vectorSize;
- }
- // Write the subsample encryption data.
- ParsableByteArray subsampleEncryptionData = trackFragment.sampleEncryptionData;
- int subsampleCount = subsampleEncryptionData.readUnsignedShort();
- subsampleEncryptionData.skipBytes(-2);
- int subsampleDataLength = 2 + 6 * subsampleCount;
- output.sampleData(subsampleEncryptionData, subsampleDataLength);
- return 1 + vectorSize + subsampleDataLength;
- }
-
/** Returns DrmInitData from leaf atoms. */
private static DrmInitData getDrmInitDataFromAtoms(List leafChildren) {
ArrayList schemeDatas = null;
@@ -1397,18 +1357,24 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private static final class TrackBundle {
- public final TrackFragment fragment;
public final TrackOutput output;
+ public final TrackFragment fragment;
public Track track;
public DefaultSampleValues defaultSampleValues;
public int currentSampleIndex;
public int currentSampleInTrackRun;
public int currentTrackRunIndex;
+ public int firstSampleToOutputIndex;
+
+ private final ParsableByteArray encryptionSignalByte;
+ private final ParsableByteArray defaultInitializationVector;
public TrackBundle(TrackOutput output) {
- fragment = new TrackFragment();
this.output = output;
+ fragment = new TrackFragment();
+ encryptionSignalByte = new ParsableByteArray(1);
+ defaultInitializationVector = new ParsableByteArray();
}
public void init(Track track, DefaultSampleValues defaultSampleValues) {
@@ -1418,13 +1384,6 @@ public final class FragmentedMp4Extractor implements Extractor {
reset();
}
- public void reset() {
- fragment.reset();
- currentSampleIndex = 0;
- currentTrackRunIndex = 0;
- currentSampleInTrackRun = 0;
- }
-
public void updateDrmInitData(DrmInitData drmInitData) {
TrackEncryptionBox encryptionBox =
track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex);
@@ -1432,6 +1391,120 @@ public final class FragmentedMp4Extractor implements Extractor {
output.format(track.format.copyWithDrmInitData(drmInitData.copyWithSchemeType(schemeType)));
}
+ /** Resets the current fragment and sample indices. */
+ public void reset() {
+ fragment.reset();
+ currentSampleIndex = 0;
+ currentTrackRunIndex = 0;
+ currentSampleInTrackRun = 0;
+ firstSampleToOutputIndex = 0;
+ }
+
+ /**
+ * Advances {@link #firstSampleToOutputIndex} to point to the sync sample before the specified
+ * seek time in the current fragment.
+ *
+ * @param timeUs The seek time, in microseconds.
+ */
+ public void seek(long timeUs) {
+ long timeMs = C.usToMs(timeUs);
+ int searchIndex = currentSampleIndex;
+ while (searchIndex < fragment.sampleCount
+ && fragment.getSamplePresentationTime(searchIndex) < timeMs) {
+ if (fragment.sampleIsSyncFrameTable[searchIndex]) {
+ firstSampleToOutputIndex = searchIndex;
+ }
+ searchIndex++;
+ }
+ }
+
+ /**
+ * Advances the indices in the bundle to point to the next sample in the current fragment. If
+ * the current sample is the last one in the current fragment, then the advanced state will be
+ * {@code currentSampleIndex == fragment.sampleCount}, {@code currentTrackRunIndex ==
+ * fragment.trunCount} and {@code #currentSampleInTrackRun == 0}.
+ *
+ * @return Whether the next sample is in the same track run as the previous one.
+ */
+ public boolean next() {
+ currentSampleIndex++;
+ currentSampleInTrackRun++;
+ if (currentSampleInTrackRun == fragment.trunLength[currentTrackRunIndex]) {
+ currentTrackRunIndex++;
+ currentSampleInTrackRun = 0;
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Outputs the encryption data for the current sample.
+ *
+ * @return The number of written bytes.
+ */
+ public int outputSampleEncryptionData() {
+ if (!fragment.definesEncryptionData) {
+ return 0;
+ }
+
+ TrackEncryptionBox encryptionBox = getEncryptionBox();
+ ParsableByteArray initializationVectorData;
+ int vectorSize;
+ if (encryptionBox.initializationVectorSize != 0) {
+ initializationVectorData = fragment.sampleEncryptionData;
+ vectorSize = encryptionBox.initializationVectorSize;
+ } else {
+ // The default initialization vector should be used.
+ byte[] initVectorData = encryptionBox.defaultInitializationVector;
+ defaultInitializationVector.reset(initVectorData, initVectorData.length);
+ initializationVectorData = defaultInitializationVector;
+ vectorSize = initVectorData.length;
+ }
+
+ boolean subsampleEncryption = fragment.sampleHasSubsampleEncryptionTable[currentSampleIndex];
+
+ // Write the signal byte, containing the vector size and the subsample encryption flag.
+ encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0));
+ encryptionSignalByte.setPosition(0);
+ output.sampleData(encryptionSignalByte, 1);
+ // Write the vector.
+ output.sampleData(initializationVectorData, vectorSize);
+ // If we don't have subsample encryption data, we're done.
+ if (!subsampleEncryption) {
+ return 1 + vectorSize;
+ }
+ // Write the subsample encryption data.
+ ParsableByteArray subsampleEncryptionData = fragment.sampleEncryptionData;
+ int subsampleCount = subsampleEncryptionData.readUnsignedShort();
+ subsampleEncryptionData.skipBytes(-2);
+ int subsampleDataLength = 2 + 6 * subsampleCount;
+ output.sampleData(subsampleEncryptionData, subsampleDataLength);
+ return 1 + vectorSize + subsampleDataLength;
+ }
+
+ /** Skips the encryption data for the current sample. */
+ private void skipSampleEncryptionData() {
+ if (!fragment.definesEncryptionData) {
+ return;
+ }
+
+ ParsableByteArray sampleEncryptionData = fragment.sampleEncryptionData;
+ TrackEncryptionBox encryptionBox = getEncryptionBox();
+ if (encryptionBox.initializationVectorSize != 0) {
+ sampleEncryptionData.skipBytes(encryptionBox.initializationVectorSize);
+ }
+ if (fragment.sampleHasSubsampleEncryptionTable[currentSampleIndex]) {
+ sampleEncryptionData.skipBytes(6 * sampleEncryptionData.readUnsignedShort());
+ }
+ }
+
+ private TrackEncryptionBox getEncryptionBox() {
+ int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
+ return fragment.trackEncryptionBox != null
+ ? fragment.trackEncryptionBox
+ : track.getSampleDescriptionEncryptionBox(sampleDescriptionIndex);
+ }
+
}
}
diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4 b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4
new file mode 100644
index 0000000000..592aa725b2
Binary files /dev/null and b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4 differ
diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump
new file mode 100644
index 0000000000..04e2f6f0a0
--- /dev/null
+++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.0.dump
@@ -0,0 +1,361 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ format:
+ bitrate = -1
+ id = 1
+ containerMimeType = null
+ sampleMimeType = video/avc
+ maxInputSize = -1
+ width = 1080
+ height = 720
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = -1
+ sampleRate = -1
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = null
+ drmInitData = -
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ total output bytes = 85933
+ sample count = 30
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ format:
+ bitrate = -1
+ id = 2
+ containerMimeType = null
+ sampleMimeType = audio/mp4a-latm
+ maxInputSize = -1
+ width = -1
+ height = -1
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = und
+ drmInitData = -
+ initializationData:
+ data = length 5, hash 2B7623A
+ total output bytes = 18257
+ sample count = 46
+ sample 0:
+ time = 0
+ flags = 1
+ data = length 18, hash 96519432
+ sample 1:
+ time = 23000
+ flags = 1
+ data = length 4, hash EE9DF
+ sample 2:
+ time = 46000
+ flags = 1
+ data = length 4, hash EEDBF
+ sample 3:
+ time = 69000
+ flags = 1
+ data = length 157, hash E2F078F4
+ sample 4:
+ time = 92000
+ flags = 1
+ data = length 371, hash B9471F94
+ sample 5:
+ time = 116000
+ flags = 1
+ data = length 373, hash 2AB265CB
+ sample 6:
+ time = 139000
+ flags = 1
+ data = length 402, hash 1295477C
+ sample 7:
+ time = 162000
+ flags = 1
+ data = length 455, hash 2D8146C8
+ sample 8:
+ time = 185000
+ flags = 1
+ data = length 434, hash F2C5D287
+ sample 9:
+ time = 208000
+ flags = 1
+ data = length 450, hash 84143FCD
+ sample 10:
+ time = 232000
+ flags = 1
+ data = length 429, hash EF769D50
+ sample 11:
+ time = 255000
+ flags = 1
+ data = length 450, hash EC3DE692
+ sample 12:
+ time = 278000
+ flags = 1
+ data = length 447, hash 3E519E13
+ sample 13:
+ time = 301000
+ flags = 1
+ data = length 457, hash 1E4F23A0
+ sample 14:
+ time = 325000
+ flags = 1
+ data = length 447, hash A439EA97
+ sample 15:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 16:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 17:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 18:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 19:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 20:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 21:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 22:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 23:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 24:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 25:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 26:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 27:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 28:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 29:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 30:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 31:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 32:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 33:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 34:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 35:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 36:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 37:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 38:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 39:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 40:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 41:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 42:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 43:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 44:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 45:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump
new file mode 100644
index 0000000000..48a7623a7d
--- /dev/null
+++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.1.dump
@@ -0,0 +1,301 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ format:
+ bitrate = -1
+ id = 1
+ containerMimeType = null
+ sampleMimeType = video/avc
+ maxInputSize = -1
+ width = 1080
+ height = 720
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = -1
+ sampleRate = -1
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = null
+ drmInitData = -
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ total output bytes = 85933
+ sample count = 30
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ format:
+ bitrate = -1
+ id = 2
+ containerMimeType = null
+ sampleMimeType = audio/mp4a-latm
+ maxInputSize = -1
+ width = -1
+ height = -1
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = und
+ drmInitData = -
+ initializationData:
+ data = length 5, hash 2B7623A
+ total output bytes = 13359
+ sample count = 31
+ sample 0:
+ time = 348000
+ flags = 1
+ data = length 456, hash 1E9034C6
+ sample 1:
+ time = 371000
+ flags = 1
+ data = length 398, hash 99DB7345
+ sample 2:
+ time = 394000
+ flags = 1
+ data = length 474, hash 3F05F10A
+ sample 3:
+ time = 417000
+ flags = 1
+ data = length 416, hash C105EE09
+ sample 4:
+ time = 441000
+ flags = 1
+ data = length 454, hash 5FDBE458
+ sample 5:
+ time = 464000
+ flags = 1
+ data = length 438, hash 41A93AC3
+ sample 6:
+ time = 487000
+ flags = 1
+ data = length 443, hash 10FDA652
+ sample 7:
+ time = 510000
+ flags = 1
+ data = length 412, hash 1F791E25
+ sample 8:
+ time = 534000
+ flags = 1
+ data = length 482, hash A6D983D
+ sample 9:
+ time = 557000
+ flags = 1
+ data = length 386, hash BED7392F
+ sample 10:
+ time = 580000
+ flags = 1
+ data = length 463, hash 5309F8C9
+ sample 11:
+ time = 603000
+ flags = 1
+ data = length 394, hash 21C7321F
+ sample 12:
+ time = 626000
+ flags = 1
+ data = length 489, hash 71B4730D
+ sample 13:
+ time = 650000
+ flags = 1
+ data = length 403, hash D9C6DE89
+ sample 14:
+ time = 673000
+ flags = 1
+ data = length 447, hash 9B14B73B
+ sample 15:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 16:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 17:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 18:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 19:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 20:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 21:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 22:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 23:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 24:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 25:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 26:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 27:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 28:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 29:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 30:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump
new file mode 100644
index 0000000000..7522891e14
--- /dev/null
+++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.2.dump
@@ -0,0 +1,241 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ format:
+ bitrate = -1
+ id = 1
+ containerMimeType = null
+ sampleMimeType = video/avc
+ maxInputSize = -1
+ width = 1080
+ height = 720
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = -1
+ sampleRate = -1
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = null
+ drmInitData = -
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ total output bytes = 85933
+ sample count = 30
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ format:
+ bitrate = -1
+ id = 2
+ containerMimeType = null
+ sampleMimeType = audio/mp4a-latm
+ maxInputSize = -1
+ width = -1
+ height = -1
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = und
+ drmInitData = -
+ initializationData:
+ data = length 5, hash 2B7623A
+ total output bytes = 6804
+ sample count = 16
+ sample 0:
+ time = 696000
+ flags = 1
+ data = length 439, hash 4760D35B
+ sample 1:
+ time = 719000
+ flags = 1
+ data = length 463, hash 1601F88D
+ sample 2:
+ time = 743000
+ flags = 1
+ data = length 423, hash D4AE6773
+ sample 3:
+ time = 766000
+ flags = 1
+ data = length 497, hash A3C674D3
+ sample 4:
+ time = 789000
+ flags = 1
+ data = length 419, hash D3734A1F
+ sample 5:
+ time = 812000
+ flags = 1
+ data = length 474, hash DFB41F9
+ sample 6:
+ time = 835000
+ flags = 1
+ data = length 413, hash 53E7CB9F
+ sample 7:
+ time = 859000
+ flags = 1
+ data = length 445, hash D15B0E39
+ sample 8:
+ time = 882000
+ flags = 1
+ data = length 453, hash 77ED81E4
+ sample 9:
+ time = 905000
+ flags = 1
+ data = length 545, hash 3321AEB9
+ sample 10:
+ time = 928000
+ flags = 1
+ data = length 317, hash F557D0E
+ sample 11:
+ time = 952000
+ flags = 1
+ data = length 537, hash ED58CF7B
+ sample 12:
+ time = 975000
+ flags = 1
+ data = length 458, hash 51CDAA10
+ sample 13:
+ time = 998000
+ flags = 1
+ data = length 465, hash CBA1EFD7
+ sample 14:
+ time = 1021000
+ flags = 1
+ data = length 446, hash D6735B8A
+ sample 15:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump
new file mode 100644
index 0000000000..afd24e40ce
--- /dev/null
+++ b/library/core/src/test/assets/mp4/sample_fragmented_seekable.mp4.3.dump
@@ -0,0 +1,181 @@
+seekMap:
+ isSeekable = true
+ duration = 1067733
+ getPosition(0) = [[timeUs=66733, position=1325]]
+numberOfTracks = 2
+track 0:
+ format:
+ bitrate = -1
+ id = 1
+ containerMimeType = null
+ sampleMimeType = video/avc
+ maxInputSize = -1
+ width = 1080
+ height = 720
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = -1
+ sampleRate = -1
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = null
+ drmInitData = -
+ initializationData:
+ data = length 29, hash 4746B5D9
+ data = length 10, hash 7A0D0F2B
+ total output bytes = 85933
+ sample count = 30
+ sample 0:
+ time = 66000
+ flags = 1
+ data = length 38070, hash B58E1AEE
+ sample 1:
+ time = 199000
+ flags = 0
+ data = length 8340, hash 8AC449FF
+ sample 2:
+ time = 132000
+ flags = 0
+ data = length 1295, hash C0DA5090
+ sample 3:
+ time = 100000
+ flags = 0
+ data = length 469, hash D6E0A200
+ sample 4:
+ time = 166000
+ flags = 0
+ data = length 564, hash E5F56C5B
+ sample 5:
+ time = 332000
+ flags = 0
+ data = length 6075, hash 8756E49E
+ sample 6:
+ time = 266000
+ flags = 0
+ data = length 847, hash DCC2B618
+ sample 7:
+ time = 233000
+ flags = 0
+ data = length 455, hash B9CCE047
+ sample 8:
+ time = 299000
+ flags = 0
+ data = length 467, hash 69806D94
+ sample 9:
+ time = 466000
+ flags = 0
+ data = length 4549, hash 3944F501
+ sample 10:
+ time = 399000
+ flags = 0
+ data = length 1087, hash 491BF106
+ sample 11:
+ time = 367000
+ flags = 0
+ data = length 380, hash 5FED016A
+ sample 12:
+ time = 433000
+ flags = 0
+ data = length 455, hash 8A0610
+ sample 13:
+ time = 599000
+ flags = 0
+ data = length 5190, hash B9031D8
+ sample 14:
+ time = 533000
+ flags = 0
+ data = length 1071, hash 684E7DC8
+ sample 15:
+ time = 500000
+ flags = 0
+ data = length 653, hash 8494F326
+ sample 16:
+ time = 566000
+ flags = 0
+ data = length 485, hash 2CCC85F4
+ sample 17:
+ time = 733000
+ flags = 0
+ data = length 4884, hash D16B6A96
+ sample 18:
+ time = 666000
+ flags = 0
+ data = length 997, hash 164FF210
+ sample 19:
+ time = 633000
+ flags = 0
+ data = length 640, hash F664125B
+ sample 20:
+ time = 700000
+ flags = 0
+ data = length 491, hash B5930C7C
+ sample 21:
+ time = 866000
+ flags = 0
+ data = length 2989, hash 92CF4FCF
+ sample 22:
+ time = 800000
+ flags = 0
+ data = length 838, hash 294A3451
+ sample 23:
+ time = 767000
+ flags = 0
+ data = length 544, hash FCCE2DE6
+ sample 24:
+ time = 833000
+ flags = 0
+ data = length 329, hash A654FFA1
+ sample 25:
+ time = 1000000
+ flags = 0
+ data = length 1517, hash 5F7EBF8B
+ sample 26:
+ time = 933000
+ flags = 0
+ data = length 803, hash 7A5C4C1D
+ sample 27:
+ time = 900000
+ flags = 0
+ data = length 415, hash B31BBC3B
+ sample 28:
+ time = 967000
+ flags = 0
+ data = length 415, hash 850DFEA3
+ sample 29:
+ time = 1033000
+ flags = 0
+ data = length 619, hash AB5E56CA
+track 1:
+ format:
+ bitrate = -1
+ id = 2
+ containerMimeType = null
+ sampleMimeType = audio/mp4a-latm
+ maxInputSize = -1
+ width = -1
+ height = -1
+ frameRate = -1.0
+ rotationDegrees = 0
+ pixelWidthHeightRatio = 1.0
+ channelCount = 1
+ sampleRate = 44100
+ pcmEncoding = -1
+ encoderDelay = 0
+ encoderPadding = 0
+ subsampleOffsetUs = 9223372036854775807
+ selectionFlags = 0
+ language = und
+ drmInitData = -
+ initializationData:
+ data = length 5, hash 2B7623A
+ total output bytes = 10
+ sample count = 1
+ sample 0:
+ time = 1044000
+ flags = 1
+ data = length 10, hash A453EEBE
+tracksEnded = true
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java
index f5b0f48592..176211acb8 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4ExtractorTest.java
@@ -36,6 +36,12 @@ public final class FragmentedMp4ExtractorTest {
getExtractorFactory(Collections.emptyList()), "mp4/sample_fragmented.mp4");
}
+ @Test
+ public void testSampleSeekable() throws Exception {
+ ExtractorAsserts.assertBehavior(
+ getExtractorFactory(Collections.emptyList()), "mp4/sample_fragmented_seekable.mp4");
+ }
+
@Test
public void testSampleWithSeiPayloadParsing() throws Exception {
// Enabling the CEA-608 track enables SEI payload parsing.