From 37d12ff14a606d20a7a4df4591e5cabb26a13d1e Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Sat, 11 Apr 2015 00:55:38 +0100 Subject: [PATCH] Steps toward generalizing the Extractor interface for all extractors. --- ...rInput.java => DefaultExtractorInput.java} | 46 +++--- ...mpleQueue.java => DefaultTrackOutput.java} | 56 +++---- .../exoplayer/extractor/Extractor.java | 137 +++--------------- .../exoplayer/extractor/ExtractorInput.java | 109 ++++++++++++++ .../exoplayer/extractor/ExtractorOutput.java | 39 +++++ .../extractor/RollingSampleBuffer.java | 43 +++--- .../exoplayer/extractor/TrackOutput.java | 58 ++++++++ .../exoplayer/extractor/ts/AdtsExtractor.java | 14 +- .../exoplayer/extractor/ts/AdtsReader.java | 15 +- .../extractor/ts/ElementaryStreamReader.java | 2 +- .../exoplayer/extractor/ts/H264Reader.java | 36 +++-- .../exoplayer/extractor/ts/Id3Reader.java | 20 ++- .../exoplayer/extractor/ts/SeiReader.java | 9 +- .../exoplayer/extractor/ts/TsExtractor.java | 35 +++-- .../exoplayer/hls/HlsExtractorWrapper.java | 38 ++--- .../google/android/exoplayer/hls/TsChunk.java | 15 +- 16 files changed, 405 insertions(+), 267 deletions(-) rename library/src/main/java/com/google/android/exoplayer/extractor/{ts/DataSourceExtractorInput.java => DefaultExtractorInput.java} (67%) rename library/src/main/java/com/google/android/exoplayer/extractor/{SampleQueue.java => DefaultTrackOutput.java} (86%) create mode 100644 library/src/main/java/com/google/android/exoplayer/extractor/ExtractorInput.java create mode 100644 library/src/main/java/com/google/android/exoplayer/extractor/ExtractorOutput.java create mode 100644 library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/DataSourceExtractorInput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultExtractorInput.java similarity index 67% rename from library/src/main/java/com/google/android/exoplayer/extractor/ts/DataSourceExtractorInput.java rename to library/src/main/java/com/google/android/exoplayer/extractor/DefaultExtractorInput.java index f1bfd9f275..0fb9008afc 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/DataSourceExtractorInput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultExtractorInput.java @@ -13,32 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer.extractor.ts; +package com.google.android.exoplayer.extractor; -import com.google.android.exoplayer.extractor.Extractor.ExtractorInput; +import com.google.android.exoplayer.C; import com.google.android.exoplayer.upstream.DataSource; +import java.io.EOFException; import java.io.IOException; /** * An {@link ExtractorInput} that wraps a {@link DataSource}. */ -public final class DataSourceExtractorInput implements ExtractorInput { +public final class DefaultExtractorInput implements ExtractorInput { private static final byte[] SCRATCH_SPACE = new byte[4096]; private final DataSource dataSource; private long position; - private boolean isEnded; + private long length; /** * @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 DataSourceExtractorInput(DataSource dataSource, long position) { + public DefaultExtractorInput(DataSource dataSource, long position, long length) { this.dataSource = dataSource; this.position = position; + this.length = length; } @Override @@ -47,16 +50,15 @@ public final class DataSourceExtractorInput implements ExtractorInput { throw new InterruptedException(); } int bytesRead = dataSource.read(target, offset, length); - if (bytesRead == -1) { - isEnded = true; - return -1; + if (bytesRead == C.RESULT_END_OF_INPUT) { + return C.RESULT_END_OF_INPUT; } position += bytesRead; return bytesRead; } @Override - public boolean readFully(byte[] target, int offset, int length) + public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput) throws IOException, InterruptedException { int remaining = length; while (remaining > 0) { @@ -64,9 +66,11 @@ public final class DataSourceExtractorInput implements ExtractorInput { throw new InterruptedException(); } int bytesRead = dataSource.read(target, offset, remaining); - if (bytesRead == -1) { - isEnded = true; - return false; + if (bytesRead == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput && remaining == length) { + return false; + } + throw new EOFException(); } offset += bytesRead; remaining -= bytesRead; @@ -76,21 +80,25 @@ public final class DataSourceExtractorInput implements ExtractorInput { } @Override - public boolean skipFully(int length) throws IOException, InterruptedException { + 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 remaining = length; while (remaining > 0) { if (Thread.interrupted()) { throw new InterruptedException(); } int bytesRead = dataSource.read(SCRATCH_SPACE, 0, Math.min(SCRATCH_SPACE.length, remaining)); - if (bytesRead == -1) { - isEnded = true; - return false; + if (bytesRead == C.RESULT_END_OF_INPUT) { + throw new EOFException(); } remaining -= bytesRead; } position += length; - return true; } @Override @@ -99,8 +107,8 @@ public final class DataSourceExtractorInput implements ExtractorInput { } @Override - public boolean isEnded() { - return isEnded; + public long getLength() { + return length; } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/SampleQueue.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java similarity index 86% rename from library/src/main/java/com/google/android/exoplayer/extractor/SampleQueue.java rename to library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java index b207fc7adc..5455e9a398 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/SampleQueue.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer.extractor; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.extractor.Extractor.TrackOutput; import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.util.ParsableByteArray; @@ -29,7 +28,7 @@ import java.io.IOException; * the first sample returned from the queue is a keyframe, allowing splicing to another queue, and * so on. */ -public final class SampleQueue implements TrackOutput { +public final class DefaultTrackOutput implements TrackOutput { private final RollingSampleBuffer rollingBuffer; private final SampleHolder sampleInfoHolder; @@ -39,14 +38,11 @@ public final class SampleQueue implements TrackOutput { private long lastReadTimeUs; private long spliceOutTimeUs; - // Accessed only by the loading thread. - private boolean writingSample; - // Accessed by both the loading and consuming threads. private volatile long largestParsedTimestampUs; private volatile MediaFormat format; - public SampleQueue(BufferPool bufferPool) { + public DefaultTrackOutput(BufferPool bufferPool) { rollingBuffer = new RollingSampleBuffer(bufferPool); sampleInfoHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); needKeyframe = true; @@ -61,6 +57,13 @@ public final class SampleQueue implements TrackOutput { // Called by the consuming thread. + /** + * True if the output has received a format. False otherwise. + */ + public boolean hasFormat() { + return format != null; + } + public MediaFormat getFormat() { return format; } @@ -114,7 +117,7 @@ public final class SampleQueue implements TrackOutput { * @param nextQueue The queue being spliced to. * @return Whether the splice was configured successfully. */ - public boolean configureSpliceTo(SampleQueue nextQueue) { + public boolean configureSpliceTo(DefaultTrackOutput nextQueue) { if (spliceOutTimeUs != Long.MIN_VALUE) { // We've already configured the splice. return true; @@ -164,44 +167,29 @@ public final class SampleQueue implements TrackOutput { return true; } + // Called by the loading thread. + + public int sampleData(DataSource dataSource, int length) throws IOException { + return rollingBuffer.appendData(dataSource, length); + } + // TrackOutput implementation. Called by the loading thread. @Override - public boolean hasFormat() { - return format != null; - } - - @Override - public void setFormat(MediaFormat format) { + public void format(MediaFormat format) { this.format = format; } @Override - public int appendData(DataSource dataSource, int length) throws IOException { - return rollingBuffer.appendData(dataSource, length); - } - - @Override - public void appendData(ParsableByteArray buffer, int length) { + public void sampleData(ParsableByteArray buffer, int length) { rollingBuffer.appendData(buffer, length); } @Override - public void startSample(long sampleTimeUs, int offset) { - writingSample = true; - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sampleTimeUs); - rollingBuffer.startSample(sampleTimeUs, offset); - } - - @Override - public void commitSample(int flags, int offset, byte[] encryptionKey) { - rollingBuffer.commitSample(flags, offset, encryptionKey); - writingSample = false; - } - - @Override - public boolean isWritingSample() { - return writingSample; + public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { + largestParsedTimestampUs = Math.max(largestParsedTimestampUs, timeUs); + rollingBuffer.commitSample(timeUs, flags, rollingBuffer.getWritePosition() - size - offset, + size, encryptionKey); } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java index fc8e4a0ded..a2a11593a1 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java @@ -15,139 +15,44 @@ */ package com.google.android.exoplayer.extractor; -import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.upstream.DataSource; -import com.google.android.exoplayer.util.ParsableByteArray; +import com.google.android.exoplayer.C; import java.io.IOException; /** - * Facilitates extraction of media samples for HLS playbacks. + * Facilitates extraction of data from a container format. */ public interface Extractor { /** - * An object from which source data can be read. + * Returned by {@link #read(ExtractorInput)} if the {@link ExtractorInput} passed to the next + * {@link #read(ExtractorInput)} is required to provide data continuing from the position in the + * stream reached by the returning call. */ - public interface ExtractorInput { - - /** - * Reads up to {@code length} bytes from the input. - *

- * This method blocks until at least one byte of data can be read, the end of the input is - * detected, or an exception is thrown. - * - * @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 maximum number of bytes to read from the input. - * @return The number of bytes read, or -1 if the input has ended. - * @throws IOException If an error occurs reading from the input. - * @throws InterruptedException If the thread has been interrupted. - */ - int read(byte[] target, int offset, int length) throws IOException, InterruptedException; - - /** - * Like {@link #read(byte[], int, int)}, but guaranteed to read request {@code length} in full - * unless the end of the input is detected, or an exception is thrown. - * - * TODO: Firm up behavior of this method if (a) zero bytes are read before EOS, (b) the read - * is partially satisfied before EOS. - * - * @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 read from the input. - * @return True if the read was successful. False if the end of the input was reached. - * @throws IOException If an error occurs reading from the input. - * @throws InterruptedException If the thread has been interrupted. - */ - boolean readFully(byte[] target, int offset, int length) - throws IOException, InterruptedException; - - /** - * Like {@link #readFully(byte[], int, int)}, except the data is skipped instead of read. - * - * TODO: Firm up behavior of this method if (a) zero bytes are skipped before EOS, (b) the skip - * is partially satisfied before EOS. - * - * @param length The number of bytes to skip from the input. - * @return True if the read was successful. False if the end of the input was reached. - * @throws IOException If an error occurs reading from the input. - * @throws InterruptedException If the thread is interrupted. - */ - boolean skipFully(int length) throws IOException, InterruptedException; - - /** - * The current position in the stream. - * - * @return The position in the stream. - */ - long getPosition(); - - /** - * Whether or not the input has ended. - * - * @return True if the input has ended. False otherwise. - */ - boolean isEnded(); - - } + public static final int RESULT_CONTINUE = 0; + /** + * Returned by {@link #read(ExtractorInput)} if the end of the {@link ExtractorInput} was reached. + * Equal to {@link C#RESULT_END_OF_INPUT}. + */ + public static final int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; /** - * An object to which extracted data should be output. - */ - public interface TrackOutputBuilder { - - /** - * Invoked to build a {@link TrackOutput} to which data should be output for a given track. - * - * @param trackId A stable track id. - * @return The corresponding {@link TrackOutput}. - */ - TrackOutput buildOutput(int trackId); - - /** - * Invoked when all {@link TrackOutput}s have been built, meaning {@link #buildOutput(int)} - * will not be invoked again. - */ - void allOutputsBuilt(); - - } - - /** - * An object to which extracted data belonging to a given track should be output. - */ - public interface TrackOutput { - - boolean hasFormat(); - - void setFormat(MediaFormat format); - - boolean isWritingSample(); - - int appendData(DataSource dataSource, int length) throws IOException; - - void appendData(ParsableByteArray data, int length); - - void startSample(long timeUs, int offset); - - void commitSample(int flags, int offset, byte[] encryptionKey); - - } - - /** - * Initializes the extractor. + * Initializes the extractor with an {@link ExtractorOutput}. * - * @param output A {@link TrackOutputBuilder} to which extracted data should be output. + * @param output An {@link ExtractorOutput} to receive extracted data. */ - void init(TrackOutputBuilder output); + void init(ExtractorOutput output); /** - * Reads from the provided {@link ExtractorInput}. + * Extracts data read from a provided {@link ExtractorInput}. + *

+ * Each read will extract at most one sample from the stream before returning. * - * @param input The {@link ExtractorInput} from which to read. - * @throws IOException If an error occurred reading from the source. + * @param input The {@link ExtractorInput} from which data should be read. + * @return One of the {@code RESULT_} values defined in this interface. + * @throws IOException If an error occurred reading from the input. * @throws InterruptedException If the thread was interrupted. */ - void read(ExtractorInput input) throws IOException, InterruptedException; + int read(ExtractorInput input) throws IOException, InterruptedException; } 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 new file mode 100644 index 0000000000..63c09aab3d --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorInput.java @@ -0,0 +1,109 @@ +/* + * 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 java.io.EOFException; +import java.io.IOException; + +/** + * Provides data to be consumed by an {@link Extractor}. + */ +public interface ExtractorInput { + + /** + * Reads up to {@code length} bytes from the input. + *

+ * This method blocks until at least one byte of data can be read, the end of the input is + * detected, or an exception is thrown. + * + * @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 maximum number of bytes to read from the input. + * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the input has ended. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + int read(byte[] target, int offset, int length) throws IOException, InterruptedException; + + /** + * Like {@link #read(byte[], int, int)}, but reads the requested {@code length} in full. + *

+ * If the end of the input is found having read no data, then behavior is dependent on + * {@code allowEndOfInput}. If {@code allowEndOfInput == true} then {@code false} is returned. + * Otherwise an {@link EOFException} is thrown. + *

+ * Encountering the end of input having partially satisfied the read is always considered an + * error, and will result in an {@link EOFException} being thrown. + * + * @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 read from the input. + * @param allowEndOfInput True if encountering the end of the input having read 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 read was successful. False if the end of the input was encountered having + * read no data. + * @throws EOFException If the end of input was encountered having partially satisfied the read + * (i.e. having read at least one byte, but fewer than {@code length}), or if no bytes were + * read and {@code allowEndOfInput} is false. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + + /** + * Equivalent to {@code readFully(target, offset, length, false)}. + * + * @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 read from the input. + * @throws EOFException If the end of input was encountered. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + void readFully(byte[] target, int offset, int length) throws IOException, InterruptedException; + + /** + * Like {@link #readFully(byte[], int, int)}, except the data is skipped instead of read. + *

+ * Encountering the end of input is always considered an error, and will result in an + * {@link EOFException} being thrown. + * + * @param length The number of bytes to skip from the input. + * @throws EOFException If the end of input was encountered. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread is interrupted. + */ + void skipFully(int length) throws IOException, InterruptedException; + + /** + * The current position (byte offset) in the stream. + * + * @return The position (byte offset) in the stream. + */ + long getPosition(); + + /** + * Returns the length of the source stream, or {@link C#LENGTH_UNBOUNDED} if it is unknown. + * + * @return The length of the source stream, or {@link C#LENGTH_UNBOUNDED}. + */ + long getLength(); + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorOutput.java new file mode 100644 index 0000000000..09bc4704c0 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorOutput.java @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * Receives stream level data extracted by an {@link Extractor}. + */ +public interface ExtractorOutput { + + /** + * Invoked when the {@link Extractor} identifies the existence of a track in the stream. + *

+ * Returns a {@link TrackOutput} that will receive track level data belonging to the track. + * + * @param trackId A track identifier. + * @return The {@link TrackOutput} that should receive track level data belonging to the track. + */ + TrackOutput track(int trackId); + + /** + * Invoked when all tracks have been identified, meaning that {@link #track(int)} will not be + * invoked again. + */ + void endTracks(); + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java index e34474d9ba..e073c1b099 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java @@ -19,7 +19,6 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.upstream.DataSource; -import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.ParsableByteArray; import java.io.IOException; @@ -48,8 +47,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; private long totalBytesWritten; private byte[] lastFragment; private int lastFragmentOffset; - private long pendingSampleTimeUs; - private long pendingSampleOffset; public RollingSampleBuffer(BufferPool bufferPool) { this.fragmentPool = bufferPool; @@ -71,7 +68,8 @@ import java.util.concurrent.ConcurrentLinkedQueue; /** * Fills {@code holder} with information about the current sample, but does not write its data. *

- * The fields set are {SampleHolder#size}, {SampleHolder#timeUs} and {SampleHolder#flags}. + * The fields set are {@link SampleHolder#size}, {@link SampleHolder#timeUs} and + * {@link SampleHolder#flags}. * * @param holder The holder into which the current sample information should be written. * @return True if the holder was filled. False if there is no current sample. @@ -92,10 +90,15 @@ import java.util.concurrent.ConcurrentLinkedQueue; * Reads the current sample, advancing the read index to the next sample. * * @param sampleHolder The holder into which the current sample should be written. + * @return True if a sample was read. False if there is no current sample. */ - public void readSample(SampleHolder sampleHolder) { + public boolean readSample(SampleHolder sampleHolder) { // Write the sample information into the holder and extrasHolder. - infoQueue.peekSample(sampleHolder, extrasHolder); + boolean haveSample = infoQueue.peekSample(sampleHolder, extrasHolder); + if (!haveSample) { + return false; + } + // Read encryption data if the sample is encrypted. if (sampleHolder.isEncrypted()) { readEncryptionData(sampleHolder, extrasHolder); @@ -110,6 +113,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; // Advance the read head. long nextOffset = infoQueue.moveToNextSample(); dropFragmentsTo(nextOffset); + return true; } /** @@ -250,16 +254,12 @@ import java.util.concurrent.ConcurrentLinkedQueue; // Called by the loading thread. /** - * Indicates the start point for the next sample. + * Returns the current write position in the rolling buffer. * - * @param sampleTimeUs The sample timestamp. - * @param offset The offset of the sample's data, relative to the total number of bytes written - * to the buffer. Must be negative or zero. + * @return The current write position. */ - public void startSample(long sampleTimeUs, int offset) { - Assertions.checkState(offset <= 0); - pendingSampleTimeUs = sampleTimeUs; - pendingSampleOffset = totalBytesWritten + offset; + public long getWritePosition() { + return totalBytesWritten; } /** @@ -314,16 +314,15 @@ import java.util.concurrent.ConcurrentLinkedQueue; /** * Indicates the end point for the current sample, making it available for consumption. * + * @param sampleTimeUs The sample timestamp. * @param flags Flags that accompany the sample. See {@link SampleHolder#flags}. - * @param offset The offset of the first byte after the end of the sample's data, relative to - * the total number of bytes written to the buffer. Must be negative or zero. + * @param position The position of the sample data in the rolling buffer. + * @param size The size of the sample, in bytes. * @param encryptionKey The encryption key associated with the sample, or null. */ - public void commitSample(int flags, int offset, byte[] encryptionKey) { - Assertions.checkState(offset <= 0); - int sampleSize = (int) (totalBytesWritten + offset - pendingSampleOffset); - infoQueue.commitSample(pendingSampleTimeUs, pendingSampleOffset, sampleSize, flags, - encryptionKey); + public void commitSample(long sampleTimeUs, int flags, long position, int size, + byte[] encryptionKey) { + infoQueue.commitSample(sampleTimeUs, flags, position, size, encryptionKey); } /** @@ -398,7 +397,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; // Called by the loading thread. - public synchronized void commitSample(long timeUs, long offset, int size, int sampleFlags, + public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size, byte[] encryptionKey) { timesUs[writeIndex] = timeUs; offsets[writeIndex] = offset; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java new file mode 100644 index 0000000000..1ad55e50db --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java @@ -0,0 +1,58 @@ +/* + * 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.MediaFormat; +import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer.util.ParsableByteArray; + +/** + * Receives track level data extracted by an {@link Extractor}. + */ +public interface TrackOutput { + + /** + * Invoked when the {@link MediaFormat} of the track has been extracted from the stream. + * + * @param format The extracted {@link MediaFormat}. + */ + void format(MediaFormat format); + + /** + * Invoked to write sample data to the output. + * + * @param data A {@link ParsableByteArray} from which to read the sample data. + * @param length The number of bytes to read. + */ + void sampleData(ParsableByteArray data, int length); + + /** + * Invoked when metadata associated with a sample has been extracted from the stream. + *

+ * The corresponding sample data will have already been passed to the output via calls to + * {@link #sampleData(ParsableByteArray, int)}. + * + * @param timeUs The media timestamp associated with the sample, in microseconds. + * @param flags Flags associated with the sample. See {@link SampleHolder#flags}. + * @param size The size of the sample data, in bytes. + * @param offset The number of bytes that have been passed to + * {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample + * whose metadata is being passed. + * @param encryptionKey The encryption key associated with the sample. May be null. + */ + void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey); + +} diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java index 46bf20e744..d6c3ab73ab 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java @@ -16,6 +16,8 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.extractor.Extractor; +import com.google.android.exoplayer.extractor.ExtractorInput; +import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.util.ParsableByteArray; import java.io.IOException; @@ -42,16 +44,17 @@ public class AdtsExtractor implements Extractor { } @Override - public void init(TrackOutputBuilder output) { - adtsReader = new AdtsReader(output.buildOutput(0)); - output.allOutputsBuilt(); + public void init(ExtractorOutput output) { + adtsReader = new AdtsReader(output.track(0)); + output.endTracks(); } @Override - public void read(ExtractorInput input) throws IOException, InterruptedException { + public int read(ExtractorInput input) + throws IOException, InterruptedException { int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE); if (bytesRead == -1) { - return; + return RESULT_END_OF_INPUT; } // Feed whatever data we have to the reader, regardless of whether the read finished or not. @@ -62,6 +65,7 @@ public class AdtsExtractor implements Extractor { // unnecessary to copy the data through packetBuffer. adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket); firstPacket = false; + return RESULT_CONTINUE; } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java index 397485a85b..5165f089f8 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.extractor.Extractor.TrackOutput; +import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableBitArray; @@ -48,7 +48,8 @@ import java.util.Collections; private boolean lastByteWasFF; private boolean hasCrc; - // Parsed from the header. + // Used when parsing the header. + private boolean hasOutputFormat; private long frameDurationUs; private int sampleSize; @@ -78,17 +79,16 @@ import java.util.Collections; int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; if (continueRead(data, adtsScratch.getData(), targetLength)) { parseHeader(); - output.startSample(timeUs, 0); bytesRead = 0; state = STATE_READING_SAMPLE; } break; case STATE_READING_SAMPLE: int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); - output.appendData(data, bytesToRead); + output.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { - output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null); + output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); timeUs += frameDurationUs; bytesRead = 0; state = STATE_FINDING_SYNC; @@ -152,7 +152,7 @@ import java.util.Collections; private void parseHeader() { adtsScratch.setPosition(0); - if (!output.hasFormat()) { + if (!hasOutputFormat) { int audioObjectType = adtsScratch.readBits(2) + 1; int sampleRateIndex = adtsScratch.readBits(4); adtsScratch.skipBits(1); @@ -167,7 +167,8 @@ import java.util.Collections; MediaFormat.NO_VALUE, audioParams.second, audioParams.first, Collections.singletonList(audioSpecificConfig)); frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate; - output.setFormat(mediaFormat); + output.format(mediaFormat); + hasOutputFormat = true; } else { adtsScratch.skipBits(10); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/ElementaryStreamReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/ElementaryStreamReader.java index 16393a420b..d2df2aef14 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/ElementaryStreamReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/ElementaryStreamReader.java @@ -15,7 +15,7 @@ */ package com.google.android.exoplayer.extractor.ts; -import com.google.android.exoplayer.extractor.Extractor.TrackOutput; +import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.util.ParsableByteArray; /** diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java index 2ad5634b26..7f4336f085 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/H264Reader.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.extractor.Extractor.TrackOutput; +import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.H264Util; import com.google.android.exoplayer.util.MimeTypes; @@ -46,9 +46,15 @@ import java.util.List; private final NalUnitTargetBuffer sei; private final ParsableByteArray seiWrapper; + private boolean hasOutputFormat; private int scratchEscapeCount; private int[] scratchEscapePositions; + + private boolean writingSample; private boolean isKeyframe; + private long samplePosition; + private long sampleTimeUs; + private long totalBytesWritten; public H264Reader(TrackOutput output, SeiReader seiReader) { super(output); @@ -69,7 +75,8 @@ import java.util.List; byte[] dataArray = data.data; // Append the data to the buffer. - output.appendData(data, data.bytesLeft()); + totalBytesWritten += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); // Scan the appended data, processing NAL units as they are encountered while (offset < limit) { @@ -85,15 +92,20 @@ import java.util.List; } int nalUnitType = H264Util.getNalUnitType(dataArray, nextNalUnitOffset); - int nalUnitOffsetInData = nextNalUnitOffset - limit; + int bytesWrittenPastNalUnit = limit - nextNalUnitOffset; if (nalUnitType == NAL_UNIT_TYPE_AUD) { - if (output.isWritingSample()) { - if (isKeyframe && !output.hasFormat() && sps.isCompleted() && pps.isCompleted()) { + if (writingSample) { + if (isKeyframe && !hasOutputFormat && sps.isCompleted() && pps.isCompleted()) { parseMediaFormat(sps, pps); } - output.commitSample(isKeyframe ? C.SAMPLE_FLAG_SYNC : 0, nalUnitOffsetInData, null); + int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0; + int size = (int) (totalBytesWritten - samplePosition) - bytesWrittenPastNalUnit; + output.sampleMetadata(sampleTimeUs, flags, size, bytesWrittenPastNalUnit, null); + writingSample = false; } - output.startSample(pesTimeUs, nalUnitOffsetInData); + writingSample = true; + samplePosition = totalBytesWritten - bytesWrittenPastNalUnit; + sampleTimeUs = pesTimeUs; isKeyframe = false; } else if (nalUnitType == NAL_UNIT_TYPE_IDR) { isKeyframe = true; @@ -120,7 +132,7 @@ import java.util.List; } private void feedNalUnitTargetBuffersStart(int nalUnitType) { - if (!output.hasFormat()) { + if (!hasOutputFormat) { sps.startNalUnit(nalUnitType); pps.startNalUnit(nalUnitType); } @@ -128,7 +140,7 @@ import java.util.List; } private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) { - if (!output.hasFormat()) { + if (!hasOutputFormat) { sps.appendToNalUnit(dataArray, offset, limit); pps.appendToNalUnit(dataArray, offset, limit); } @@ -232,9 +244,9 @@ import java.util.List; frameHeight -= (frameCropTopOffset + frameCropBottomOffset) * cropUnitY; } - // Set the format. - output.setFormat(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE, - frameWidth, frameHeight, initializationData)); + output.format(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE, + C.UNKNOWN_TIME_US, frameWidth, frameHeight, initializationData)); + hasOutputFormat = true; } private void skipScalingList(ParsableBitArray bitArray, int size) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java index 4d93b2df5b..e5a9d355ae 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/Id3Reader.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.extractor.Extractor.TrackOutput; +import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.util.ParsableByteArray; /** @@ -25,24 +25,32 @@ import com.google.android.exoplayer.util.ParsableByteArray; */ /* package */ class Id3Reader extends ElementaryStreamReader { + private boolean writingSample; + private long sampleTimeUs; + private int sampleSize; + public Id3Reader(TrackOutput output) { super(output); - output.setFormat(MediaFormat.createId3Format()); + output.format(MediaFormat.createId3Format()); } @Override public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { if (startOfPacket) { - output.startSample(pesTimeUs, 0); + writingSample = true; + sampleTimeUs = pesTimeUs; + sampleSize = 0; } - if (output.isWritingSample()) { - output.appendData(data, data.bytesLeft()); + if (writingSample) { + sampleSize += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); } } @Override public void packetFinished() { - output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null); + output.sampleMetadata(sampleTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); + writingSample = false; } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java index a71b629ebf..66d6819832 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/SeiReader.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; -import com.google.android.exoplayer.extractor.Extractor.TrackOutput; +import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.text.eia608.Eia608Parser; import com.google.android.exoplayer.util.ParsableByteArray; @@ -31,7 +31,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; public SeiReader(TrackOutput output) { super(output); - output.setFormat(MediaFormat.createEia608Format()); + output.format(MediaFormat.createEia608Format()); } @Override @@ -55,9 +55,8 @@ import com.google.android.exoplayer.util.ParsableByteArray; } while (b == 0xFF); // Process the payload. We only support EIA-608 payloads currently. if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { - output.startSample(pesTimeUs, 0); - output.appendData(seiBuffer, payloadSize); - output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null); + output.sampleData(seiBuffer, payloadSize); + output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null); } else { seiBuffer.skip(payloadSize); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java index 6fa5aa95c7..f735831085 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java @@ -17,6 +17,8 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.extractor.Extractor; +import com.google.android.exoplayer.extractor.ExtractorInput; +import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.util.ParsableBitArray; import com.google.android.exoplayer.util.ParsableByteArray; @@ -50,7 +52,7 @@ public final class TsExtractor implements Extractor { private final ParsableBitArray tsScratch; // Accessed only by the loading thread. - private TrackOutputBuilder output; + private ExtractorOutput output; private long timestampOffsetUs; private long lastPts; @@ -65,21 +67,22 @@ public final class TsExtractor implements Extractor { } @Override - public void init(TrackOutputBuilder output) { + public void init(ExtractorOutput output) { this.output = output; } @Override - public void read(ExtractorInput input) throws IOException, InterruptedException { - if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE)) { - return; + public int read(ExtractorInput input) + throws IOException, InterruptedException { + if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE, true)) { + return RESULT_END_OF_INPUT; } tsPacketBuffer.setPosition(0); tsPacketBuffer.setLimit(TS_PACKET_SIZE); int syncByte = tsPacketBuffer.readUnsignedByte(); if (syncByte != TS_SYNC_BYTE) { - return; + return RESULT_CONTINUE; } tsPacketBuffer.readBytes(tsScratch, 3); @@ -105,6 +108,8 @@ public final class TsExtractor implements Extractor { payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output); } } + + return RESULT_CONTINUE; } /** @@ -140,7 +145,7 @@ public final class TsExtractor implements Extractor { private abstract static class TsPayloadReader { public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - TrackOutputBuilder output); + ExtractorOutput output); } @@ -157,7 +162,7 @@ public final class TsExtractor implements Extractor { @Override public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - TrackOutputBuilder output) { + ExtractorOutput output) { // Skip pointer. if (payloadUnitStartIndicator) { int pointerField = data.readUnsignedByte(); @@ -197,7 +202,7 @@ public final class TsExtractor implements Extractor { @Override public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - TrackOutputBuilder output) { + ExtractorOutput output) { // Skip pointer. if (payloadUnitStartIndicator) { int pointerField = data.readUnsignedByte(); @@ -241,16 +246,16 @@ public final class TsExtractor implements Extractor { ElementaryStreamReader pesPayloadReader = null; switch (streamType) { case TS_STREAM_TYPE_AAC: - pesPayloadReader = new AdtsReader(output.buildOutput(TS_STREAM_TYPE_AAC)); + pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC)); break; case TS_STREAM_TYPE_H264: - SeiReader seiReader = new SeiReader(output.buildOutput(TS_STREAM_TYPE_EIA608)); + SeiReader seiReader = new SeiReader(output.track(TS_STREAM_TYPE_EIA608)); streamReaders.put(TS_STREAM_TYPE_EIA608, seiReader); - pesPayloadReader = new H264Reader(output.buildOutput(TS_STREAM_TYPE_H264), + pesPayloadReader = new H264Reader(output.track(TS_STREAM_TYPE_H264), seiReader); break; case TS_STREAM_TYPE_ID3: - pesPayloadReader = new Id3Reader(output.buildOutput(TS_STREAM_TYPE_ID3)); + pesPayloadReader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3)); break; } @@ -260,7 +265,7 @@ public final class TsExtractor implements Extractor { } } - output.allOutputsBuilt(); + output.endTracks(); } } @@ -300,7 +305,7 @@ public final class TsExtractor implements Extractor { @Override public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - TrackOutputBuilder output) { + ExtractorOutput output) { if (payloadUnitStartIndicator) { switch (state) { case STATE_FINDING_HEADER: diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java index 628e216418..a340183ff0 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java @@ -17,10 +17,11 @@ package com.google.android.exoplayer.hls; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.extractor.Extractor; -import com.google.android.exoplayer.extractor.SampleQueue; -import com.google.android.exoplayer.extractor.Extractor.ExtractorInput; -import com.google.android.exoplayer.extractor.Extractor.TrackOutput; +import com.google.android.exoplayer.extractor.ExtractorInput; +import com.google.android.exoplayer.extractor.ExtractorOutput; +import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.util.Assertions; @@ -31,25 +32,24 @@ import java.io.IOException; /** * Wraps a {@link Extractor}, adding functionality to enable reading of the extracted samples. */ -public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder { +public final class HlsExtractorWrapper implements ExtractorOutput { private final BufferPool bufferPool; private final Extractor extractor; - private final SparseArray sampleQueues; + private final SparseArray sampleQueues; private final boolean shouldSpliceIn; - private volatile boolean outputsBuilt; + private volatile boolean tracksBuilt; // Accessed only by the consuming thread. private boolean prepared; private boolean spliceConfigured; - public HlsExtractorWrapper(BufferPool bufferPool, Extractor extractor, - boolean shouldSpliceIn) { + public HlsExtractorWrapper(BufferPool bufferPool, Extractor extractor, boolean shouldSpliceIn) { this.bufferPool = bufferPool; this.extractor = extractor; this.shouldSpliceIn = shouldSpliceIn; - sampleQueues = new SparseArray(); + sampleQueues = new SparseArray(); extractor.init(this); } @@ -76,8 +76,8 @@ public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder { boolean spliceConfigured = true; int trackCount = getTrackCount(); for (int i = 0; i < trackCount; i++) { - SampleQueue currentSampleQueue = sampleQueues.valueAt(i); - SampleQueue nextSampleQueue = nextExtractor.sampleQueues.valueAt(i); + DefaultTrackOutput currentSampleQueue = sampleQueues.valueAt(i); + DefaultTrackOutput nextSampleQueue = nextExtractor.sampleQueues.valueAt(i); spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue); } this.spliceConfigured = spliceConfigured; @@ -113,7 +113,7 @@ public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder { * @return True if the extractor is prepared. False otherwise. */ public boolean isPrepared() { - if (!prepared && outputsBuilt) { + if (!prepared && tracksBuilt) { for (int i = 0; i < sampleQueues.size(); i++) { if (!sampleQueues.valueAt(i).hasFormat()) { return false; @@ -188,25 +188,27 @@ public final class HlsExtractorWrapper implements Extractor.TrackOutputBuilder { * Reads from the provided {@link ExtractorInput}. * * @param input The {@link ExtractorInput} from which to read. + * @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}. * @throws IOException If an error occurred reading from the source. * @throws InterruptedException If the thread was interrupted. */ - public void read(ExtractorInput input) throws IOException, InterruptedException { - extractor.read(input); + public int read(ExtractorInput input) throws IOException, InterruptedException { + int result = extractor.read(input); + return result; } // ExtractorOutput implementation. @Override - public TrackOutput buildOutput(int id) { - SampleQueue sampleQueue = new SampleQueue(bufferPool); + public TrackOutput track(int id) { + DefaultTrackOutput sampleQueue = new DefaultTrackOutput(bufferPool); sampleQueues.put(id, sampleQueue); return sampleQueue; } @Override - public void allOutputsBuilt() { - this.outputsBuilt = true; + public void endTracks() { + this.tracksBuilt = true; } } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java index fb1d0914c5..5a639f9fad 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java @@ -15,8 +15,9 @@ */ package com.google.android.exoplayer.hls; -import com.google.android.exoplayer.extractor.Extractor.ExtractorInput; -import com.google.android.exoplayer.extractor.ts.DataSourceExtractorInput; +import com.google.android.exoplayer.extractor.DefaultExtractorInput; +import com.google.android.exoplayer.extractor.Extractor; +import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; @@ -101,9 +102,9 @@ public final class TsChunk extends HlsChunk { @Override public void load() throws IOException, InterruptedException { - ExtractorInput input = new DataSourceExtractorInput(dataSource, 0); + ExtractorInput input; try { - dataSource.open(dataSpec); + input = new DefaultExtractorInput(dataSource, 0, dataSource.open(dataSpec)); // If we previously fed part of this chunk to the extractor, skip it this time. // TODO: Ideally we'd construct a dataSpec that only loads the remainder of the data here, // rather than loading the whole chunk again and then skipping data we previously loaded. To @@ -111,12 +112,12 @@ public final class TsChunk extends HlsChunk { // encrypted with AES, for which we'll need to modify the way that decryption is performed. input.skipFully(loadPosition); try { - while (!input.isEnded() && !loadCanceled) { - extractor.read(input); + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractor.read(input); } } finally { loadPosition = (int) input.getPosition(); - loadFinished = !loadCanceled; } } finally { dataSource.close();