diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/DefaultExtractorInputTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/DefaultExtractorInputTest.java index fb2d91654d..19c6586bf5 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/DefaultExtractorInputTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/DefaultExtractorInputTest.java @@ -301,6 +301,76 @@ public class DefaultExtractorInputTest extends TestCase { } } + public void testResetPeekPosition() throws IOException, InterruptedException { + DefaultExtractorInput input = createDefaultExtractorInput(); + byte[] target = new byte[TEST_DATA.length]; + input.peekFully(target, 0, TEST_DATA.length); + + // Check that we read the whole of TEST_DATA. + assertTrue(Arrays.equals(TEST_DATA, target)); + assertEquals(0, input.getPosition()); + + // Check that we can peek again after resetting. + input.resetPeekPosition(); + byte[] target2 = new byte[TEST_DATA.length]; + input.peekFully(target2, 0, TEST_DATA.length); + assertTrue(Arrays.equals(TEST_DATA, target2)); + + // Check that we fail with EOFException if we peek past the end of the input. + try { + input.peekFully(target, 0, 1); + fail(); + } catch (EOFException e) { + // Expected. + } + } + + public void testPeekFullyAtEndOfStreamWithAllowEndOfInputSucceeds() + throws IOException, InterruptedException { + DefaultExtractorInput input = createDefaultExtractorInput(); + byte[] target = new byte[TEST_DATA.length]; + + // Check peeking up to the end of input succeeds. + assertTrue(input.peekFully(target, 0, TEST_DATA.length, true)); + + // Check peeking at the end of input with allowEndOfInput signals the end of input. + assertFalse(input.peekFully(target, 0, 1, true)); + } + + public void testPeekFullyAcrossEndOfInputWithAllowEndOfInputFails() + throws IOException, InterruptedException { + DefaultExtractorInput input = createDefaultExtractorInput(); + byte[] target = new byte[TEST_DATA.length]; + + // Check peeking before the end of input with allowEndOfInput succeeds. + assertTrue(input.peekFully(target, 0, TEST_DATA.length - 1, true)); + + // Check peeking across the end of input with allowEndOfInput throws. + try { + input.peekFully(target, 0, 2, true); + fail(); + } catch (EOFException e) { + // Expected. + } + } + + public void testResetAndPeekFullyPastEndOfStreamWithAllowEndOfInputFails() + throws IOException, InterruptedException { + DefaultExtractorInput input = createDefaultExtractorInput(); + byte[] target = new byte[TEST_DATA.length]; + + // Check peeking up to the end of input succeeds. + assertTrue(input.peekFully(target, 0, TEST_DATA.length, true)); + input.resetPeekPosition(); + try { + // Check peeking one more byte throws. + input.peekFully(target, 0, TEST_DATA.length + 1, true); + fail(); + } catch (EOFException e) { + // Expected. + } + } + private static FakeDataSource buildDataSource() throws IOException { FakeDataSource.Builder builder = new FakeDataSource.Builder(); builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultExtractorInput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultExtractorInput.java index 0e42a91d11..ca60e546b8 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultExtractorInput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultExtractorInput.java @@ -103,21 +103,42 @@ public final class DefaultExtractorInput implements ExtractorInput { skipFully(length, false); } + @Override + public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + if (!advancePeekPosition(length, allowEndOfInput)) { + return false; + } + System.arraycopy(peekBuffer, peekBufferPosition - length, target, offset, length); + return true; + } + @Override public void peekFully(byte[] target, int offset, int length) throws IOException, InterruptedException { - advancePeekPosition(length); - System.arraycopy(peekBuffer, peekBufferPosition - length, target, offset, length); + peekFully(target, offset, length, false); + } + + @Override + public boolean advancePeekPosition(int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + ensureSpaceForPeek(length); + int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); + peekBufferLength += length - bytesPeeked; + while (bytesPeeked < length) { + bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, + allowEndOfInput); + if (bytesPeeked == C.RESULT_END_OF_INPUT) { + return false; + } + } + peekBufferPosition += length; + return true; } @Override public void advancePeekPosition(int length) throws IOException, InterruptedException { - ensureSpaceForPeek(length); - peekBufferPosition += length; - while (peekBufferPosition > peekBufferLength) { - peekBufferLength = readFromDataSource(peekBuffer, 0, peekBufferPosition, peekBufferLength, - false); - } + advancePeekPosition(length, false); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorInput.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorInput.java index f99503d232..87c141c0b2 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorInput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorInput.java @@ -123,8 +123,38 @@ public interface ExtractorInput { * Peeks {@code length} bytes from the peek position, writing them into {@code target} at index * {@code offset}. The current read position is left unchanged. *
+ * If the end of the input is found having peeked no data, then behavior is dependent on + * {@code allowEndOfInput}. If {@code allowEndOfInput == true} then {@code false} is returned. + * Otherwise an {@link EOFException} is thrown. + *
* Calling {@link #resetPeekPosition()} resets the peek position to equal the current read - * position, so the caller can peek the same data again. Reading also resets the peek position. + * position, so the caller can peek the same data again. Reading and skipping also reset the peek + * position. + * + * @param target A target array into which data should be written. + * @param offset The offset into the target array at which to write. + * @param length The number of bytes to peek from the input. + * @param allowEndOfInput True if encountering the end of the input having peeked no data is + * allowed, and should result in {@code false} being returned. False if it should be + * considered an error, causing an {@link EOFException} to be thrown. + * @return True if the peek was successful. False if the end of the input was encountered having + * peeked no data. + * @throws EOFException If the end of input was encountered having partially satisfied the peek + * (i.e. having peeked at least one byte, but fewer than {@code length}), or if no bytes were + * peeked and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs peeking from the input. + * @throws InterruptedException If the thread is interrupted. + */ + boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + + /** + * Peeks {@code length} bytes from the peek position, writing them into {@code target} at index + * {@code offset}. The current read position is left unchanged. + *
+ * Calling {@link #resetPeekPosition()} resets the peek position to equal the current read + * position, so the caller can peek the same data again. Reading and skipping also reset the peek + * position. * * @param target A target array into which data should be written. * @param offset The offset into the target array at which to write. @@ -135,6 +165,28 @@ public interface ExtractorInput { */ void peekFully(byte[] target, int offset, int length) throws IOException, InterruptedException; + /** + * Advances the peek position by {@code length} bytes. + *
+ * If the end of the input is encountered before advancing the peek position, then behavior is + * dependent on {@code allowEndOfInput}. If {@code allowEndOfInput == true} then {@code false} is + * returned. Otherwise an {@link EOFException} is thrown. + * + * @param length The number of bytes by which to advance the peek position. + * @param allowEndOfInput True if encountering the end of the input before advancing is allowed, + * and should result in {@code false} being returned. False if it should be considered an + * error, causing an {@link EOFException} to be thrown. + * @return True if advancing the peek position was successful. False if the end of the input was + * encountered before the peek position could be advanced. + * @throws EOFException If the end of input was encountered having partially advanced (i.e. having + * advanced by at least one byte, but fewer than {@code length}), or if the end of input was + * encountered before advancing and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs advancing the peek position. + * @throws InterruptedException If the thread is interrupted. + */ + boolean advancePeekPosition(int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + /** * Advances the peek position by {@code length} bytes. *