/*
* Copyright (C) 2014 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.exoplayer.extractor;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.upstream.DataSource;
import java.io.EOFException;
import java.io.IOException;
import java.util.Arrays;
/**
* An {@link ExtractorInput} that wraps a {@link DataSource}.
*/
public final class DefaultExtractorInput implements ExtractorInput {
private static final byte[] SCRATCH_SPACE = new byte[4096];
private final DataSource dataSource;
private final long streamLength;
private long position;
private byte[] peekBuffer;
private int peekBufferPosition;
private int peekBufferLength;
/**
* @param dataSource The wrapped {@link DataSource}.
* @param position The initial position in the stream.
* @param length The length of the stream, or {@link C#LENGTH_UNBOUNDED} if it is unknown.
*/
public DefaultExtractorInput(DataSource dataSource, long position, long length) {
this.dataSource = dataSource;
this.position = position;
this.streamLength = length;
peekBuffer = new byte[8 * 1024];
}
@Override
public int read(byte[] target, int offset, int length) throws IOException, InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int peekBytes = Math.min(peekBufferLength, length);
System.arraycopy(peekBuffer, 0, target, offset, peekBytes);
offset += peekBytes;
length -= peekBytes;
int bytesRead = length != 0 ? dataSource.read(target, offset, length) : 0;
if (bytesRead == C.RESULT_END_OF_INPUT) {
return C.RESULT_END_OF_INPUT;
}
updatePeekBuffer(peekBytes);
bytesRead += peekBytes;
position += bytesRead;
return bytesRead;
}
@Override
public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
int peekBytes = Math.min(peekBufferLength, length);
System.arraycopy(peekBuffer, 0, target, offset, peekBytes);
offset += peekBytes;
int remaining = length - peekBytes;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(target, offset, remaining);
if (bytesRead == C.RESULT_END_OF_INPUT) {
if (allowEndOfInput && remaining == length) {
return false;
}
throw new EOFException();
}
offset += bytesRead;
remaining -= bytesRead;
}
updatePeekBuffer(peekBytes);
position += length;
return true;
}
@Override
public void readFully(byte[] target, int offset, int length)
throws IOException, InterruptedException {
readFully(target, offset, length, false);
}
@Override
public void skipFully(int length) throws IOException, InterruptedException {
int peekBytes = Math.min(peekBufferLength, length);
int remaining = length - peekBytes;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(SCRATCH_SPACE, 0, Math.min(SCRATCH_SPACE.length, remaining));
if (bytesRead == C.RESULT_END_OF_INPUT) {
throw new EOFException();
}
remaining -= bytesRead;
}
updatePeekBuffer(peekBytes);
position += length;
}
@Override
public void peekFully(byte[] target, int offset, int length)
throws IOException, InterruptedException {
ensureSpaceForPeek(length);
int peekBytes = Math.min(peekBufferLength - peekBufferPosition, length);
System.arraycopy(peekBuffer, peekBufferPosition, target, offset, peekBytes);
offset += peekBytes;
int fillBytes = length - peekBytes;
int remaining = fillBytes;
int writePosition = peekBufferLength;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(peekBuffer, writePosition, remaining);
if (bytesRead == C.RESULT_END_OF_INPUT) {
throw new EOFException();
}
System.arraycopy(peekBuffer, writePosition, target, offset, bytesRead);
remaining -= bytesRead;
writePosition += bytesRead;
offset += bytesRead;
}
peekBufferPosition += length;
peekBufferLength += fillBytes;
}
@Override
public void advancePeekPosition(int length) throws IOException, InterruptedException {
ensureSpaceForPeek(length);
int peekBytes = Math.min(peekBufferLength - peekBufferPosition, length);
int fillBytes = length - peekBytes;
int remaining = fillBytes;
int writePosition = peekBufferLength;
while (remaining > 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
int bytesRead = dataSource.read(peekBuffer, writePosition, remaining);
if (bytesRead == C.RESULT_END_OF_INPUT) {
throw new EOFException();
}
remaining -= bytesRead;
writePosition += bytesRead;
}
peekBufferPosition += length;
peekBufferLength += fillBytes;
}
@Override
public void resetPeekPosition() {
peekBufferPosition = 0;
}
@Override
public long getPosition() {
return position;
}
@Override
public long getLength() {
return streamLength;
}
/**
* Ensures {@code peekBuffer} is large enough to store at least {@code length} bytes from the
* current peek position.
*/
private void ensureSpaceForPeek(int length) {
int requiredLength = peekBufferPosition + length;
if (requiredLength > peekBuffer.length) {
peekBuffer = Arrays.copyOf(peekBuffer, Math.max(peekBuffer.length * 2, requiredLength));
}
}
/**
* Updates the peek buffer's length, position and contents after consuming data.
*
* @param bytesConsumed The number of bytes consumed from the peek buffer.
*/
private void updatePeekBuffer(int bytesConsumed) {
peekBufferLength -= bytesConsumed;
peekBufferPosition = 0;
System.arraycopy(peekBuffer, bytesConsumed, peekBuffer, 0, peekBufferLength);
}
}