diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java index 4aea8e642e..5bf369a5fc 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java @@ -15,12 +15,13 @@ */ package com.google.android.exoplayer.demo.full.player; -import com.google.android.exoplayer.FrameworkSampleSource; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder; import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback; +import com.google.android.exoplayer.source.DefaultSampleSource; +import com.google.android.exoplayer.source.FrameworkSampleExtractor; import android.content.Context; import android.media.MediaCodec; @@ -46,7 +47,8 @@ public class DefaultRendererBuilder implements RendererBuilder { @Override public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) { // Build the video and audio renderers. - FrameworkSampleSource sampleSource = new FrameworkSampleSource(context, uri, null, 2); + DefaultSampleSource sampleSource = + new DefaultSampleSource(new FrameworkSampleExtractor(context, uri, null), 2); MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, null, player.getMainHandler(), player, 50); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java index aef46d38d9..849e0ad986 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java @@ -15,11 +15,12 @@ */ package com.google.android.exoplayer.demo.simple; -import com.google.android.exoplayer.FrameworkSampleSource; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder; import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback; +import com.google.android.exoplayer.source.DefaultSampleSource; +import com.google.android.exoplayer.source.FrameworkSampleExtractor; import android.media.MediaCodec; import android.net.Uri; @@ -41,7 +42,8 @@ import android.net.Uri; @Override public void buildRenderers(RendererBuilderCallback callback) { // Build the video and audio renderers. - FrameworkSampleSource sampleSource = new FrameworkSampleSource(playerActivity, uri, null, 2); + DefaultSampleSource sampleSource = + new DefaultSampleSource(new FrameworkSampleExtractor(playerActivity, uri, null), 2); MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(), playerActivity, 50); diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java deleted file mode 100644 index fa73b288c8..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * 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; - -import com.google.android.exoplayer.util.Assertions; -import com.google.android.exoplayer.util.Util; - -import android.annotation.TargetApi; -import android.content.Context; -import android.media.MediaExtractor; -import android.net.Uri; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.util.Map; -import java.util.UUID; - -/** - * Extracts samples from a stream using Android's {@link MediaExtractor}. - */ -// TODO: This implementation needs to be fixed so that its methods are non-blocking (either -// through use of a background thread, or through changes to the framework's MediaExtractor API). -@TargetApi(16) -public final class FrameworkSampleSource implements SampleSource { - - private static final int TRACK_STATE_DISABLED = 0; - private static final int TRACK_STATE_ENABLED = 1; - private static final int TRACK_STATE_FORMAT_SENT = 2; - - // Parameters for a Uri data source. - private final Context context; - private final Uri uri; - private final Map headers; - - // Parameters for a FileDescriptor data source. - private final FileDescriptor fileDescriptor; - private final long fileDescriptorOffset; - private final long fileDescriptorLength; - - private MediaExtractor extractor; - private TrackInfo[] trackInfos; - private boolean prepared; - private int remainingReleaseCount; - private int[] trackStates; - private boolean[] pendingDiscontinuities; - - private long seekPositionUs; - - public FrameworkSampleSource(Context context, Uri uri, Map headers, - int downstreamRendererCount) { - Assertions.checkState(Util.SDK_INT >= 16); - this.remainingReleaseCount = downstreamRendererCount; - - this.context = context; - this.uri = uri; - this.headers = headers; - - this.fileDescriptor = null; - this.fileDescriptorOffset = 0; - this.fileDescriptorLength = 0; - } - - public FrameworkSampleSource(FileDescriptor fileDescriptor, long offset, long length, - int downstreamRendererCount) { - Assertions.checkState(Util.SDK_INT >= 16); - this.remainingReleaseCount = downstreamRendererCount; - - this.fileDescriptor = fileDescriptor; - this.fileDescriptorOffset = offset; - this.fileDescriptorLength = length; - - this.context = null; - this.uri = null; - this.headers = null; - } - - @Override - public boolean prepare() throws IOException { - if (!prepared) { - extractor = new MediaExtractor(); - if (context != null) { - extractor.setDataSource(context, uri, headers); - } else { - extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength); - } - - trackStates = new int[extractor.getTrackCount()]; - pendingDiscontinuities = new boolean[trackStates.length]; - trackInfos = new TrackInfo[trackStates.length]; - for (int i = 0; i < trackStates.length; i++) { - android.media.MediaFormat format = extractor.getTrackFormat(i); - long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION) ? - format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US; - String mime = format.getString(android.media.MediaFormat.KEY_MIME); - trackInfos[i] = new TrackInfo(mime, durationUs); - } - prepared = true; - } - return true; - } - - @Override - public int getTrackCount() { - Assertions.checkState(prepared); - return trackStates.length; - } - - @Override - public TrackInfo getTrackInfo(int track) { - Assertions.checkState(prepared); - return trackInfos[track]; - } - - @Override - public void enable(int track, long positionUs) { - Assertions.checkState(prepared); - Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED); - trackStates[track] = TRACK_STATE_ENABLED; - extractor.selectTrack(track); - seekToUs(positionUs); - } - - @Override - public boolean continueBuffering(long positionUs) { - // MediaExtractor takes care of buffering and blocks until it has samples, so we can always - // return true here. Although note that the blocking behavior is itself as bug, as per the - // TODO further up this file. This method will need to return something else as part of fixing - // the TODO. - return true; - } - - @Override - public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { - Assertions.checkState(prepared); - Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); - if (pendingDiscontinuities[track]) { - pendingDiscontinuities[track] = false; - return DISCONTINUITY_READ; - } - if (onlyReadDiscontinuity) { - return NOTHING_READ; - } - if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { - formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16( - extractor.getTrackFormat(track)); - formatHolder.drmInitData = Util.SDK_INT >= 18 ? getPsshInfoV18() : null; - trackStates[track] = TRACK_STATE_FORMAT_SENT; - return FORMAT_READ; - } - int extractorTrackIndex = extractor.getSampleTrackIndex(); - if (extractorTrackIndex == track) { - if (sampleHolder.data != null) { - int offset = sampleHolder.data.position(); - sampleHolder.size = extractor.readSampleData(sampleHolder.data, offset); - sampleHolder.data.position(offset + sampleHolder.size); - } else { - sampleHolder.size = 0; - } - sampleHolder.timeUs = extractor.getSampleTime(); - sampleHolder.flags = extractor.getSampleFlags(); - if ((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) { - sampleHolder.cryptoInfo.setFromExtractorV16(extractor); - } - seekPositionUs = -1; - extractor.advance(); - return SAMPLE_READ; - } else { - return extractorTrackIndex < 0 ? END_OF_STREAM : NOTHING_READ; - } - } - - @TargetApi(18) - private Map getPsshInfoV18() { - Map psshInfo = extractor.getPsshInfo(); - return (psshInfo == null || psshInfo.isEmpty()) ? null : psshInfo; - } - - @Override - public void disable(int track) { - Assertions.checkState(prepared); - Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); - extractor.unselectTrack(track); - pendingDiscontinuities[track] = false; - trackStates[track] = TRACK_STATE_DISABLED; - } - - @Override - public void seekToUs(long positionUs) { - Assertions.checkState(prepared); - if (seekPositionUs != positionUs) { - // Avoid duplicate calls to the underlying extractor's seek method in the case that there - // have been no interleaving calls to advance. - seekPositionUs = positionUs; - extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); - for (int i = 0; i < trackStates.length; ++i) { - if (trackStates[i] != TRACK_STATE_DISABLED) { - pendingDiscontinuities[i] = true; - } - } - } - } - - @Override - public long getBufferedPositionUs() { - Assertions.checkState(prepared); - long bufferedDurationUs = extractor.getCachedDuration(); - if (bufferedDurationUs == -1) { - return TrackRenderer.UNKNOWN_TIME_US; - } else { - long sampleTime = extractor.getSampleTime(); - return sampleTime == -1 ? TrackRenderer.END_OF_TRACK_US : sampleTime + bufferedDurationUs; - } - } - - @Override - public void release() { - Assertions.checkState(remainingReleaseCount > 0); - if (--remainingReleaseCount == 0) { - extractor.release(); - extractor = null; - } - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/source/DefaultSampleSource.java b/library/src/main/java/com/google/android/exoplayer/source/DefaultSampleSource.java new file mode 100644 index 0000000000..918ff1c57a --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/source/DefaultSampleSource.java @@ -0,0 +1,161 @@ +/* + * 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.source; + +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.MediaFormatHolder; +import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer.SampleSource; +import com.google.android.exoplayer.TrackInfo; +import com.google.android.exoplayer.util.Assertions; + +import java.io.IOException; + +/** {@link SampleSource} that extracts sample data using a {@link SampleExtractor} */ +public final class DefaultSampleSource implements SampleSource { + + private static final int TRACK_STATE_DISABLED = 0; + private static final int TRACK_STATE_ENABLED = 1; + private static final int TRACK_STATE_FORMAT_SENT = 2; + + private final SampleExtractor sampleExtractor; + + private TrackInfo[] trackInfos; + private boolean prepared; + private int remainingReleaseCount; + private int[] trackStates; + private boolean[] pendingDiscontinuities; + + private long seekPositionUs; + + /** + * Creates a new sample source that extracts samples using {@code sampleExtractor}. Specify the + * {@code downstreamRendererCount} to ensure that the sample source is released only when all + * downstream renderers have been released. + * + * @param sampleExtractor Sample extractor for accessing media samples. + * @param downstreamRendererCount Number of track renderers dependent on this sample source. + */ + public DefaultSampleSource(SampleExtractor sampleExtractor, int downstreamRendererCount) { + this.sampleExtractor = Assertions.checkNotNull(sampleExtractor); + this.remainingReleaseCount = downstreamRendererCount; + } + + @Override + public boolean prepare() throws IOException { + if (prepared) { + return true; + } + + if (sampleExtractor.prepare()) { + prepared = true; + trackInfos = sampleExtractor.getTrackInfos(); + trackStates = new int[trackInfos.length]; + pendingDiscontinuities = new boolean[trackInfos.length]; + } + + return prepared; + } + + @Override + public int getTrackCount() { + Assertions.checkState(prepared); + return trackInfos.length; + } + + @Override + public TrackInfo getTrackInfo(int track) { + Assertions.checkState(prepared); + return trackInfos[track]; + } + + @Override + public void enable(int track, long positionUs) { + Assertions.checkState(prepared); + Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED); + trackStates[track] = TRACK_STATE_ENABLED; + sampleExtractor.selectTrack(track); + seekToUs(positionUs); + } + + @Override + public void disable(int track) { + Assertions.checkState(prepared); + Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); + sampleExtractor.deselectTrack(track); + pendingDiscontinuities[track] = false; + trackStates[track] = TRACK_STATE_DISABLED; + } + + @Override + public boolean continueBuffering(long positionUs) throws IOException { + // Do nothing. + return true; + } + + @Override + public int readData(int track, long positionUs, MediaFormatHolder formatHolder, + SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException { + Assertions.checkState(prepared); + Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); + if (pendingDiscontinuities[track]) { + pendingDiscontinuities[track] = false; + return DISCONTINUITY_READ; + } + if (onlyReadDiscontinuity) { + return NOTHING_READ; + } + if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { + sampleExtractor.getTrackMediaFormat(track, formatHolder); + trackStates[track] = TRACK_STATE_FORMAT_SENT; + return FORMAT_READ; + } + + seekPositionUs = C.UNKNOWN_TIME_US; + return sampleExtractor.readSample(track, sampleHolder); + } + + @Override + public void seekToUs(long positionUs) { + Assertions.checkState(prepared); + if (seekPositionUs != positionUs) { + // Avoid duplicate calls to the underlying extractor's seek method in the case that there + // have been no interleaving calls to readSample. + seekPositionUs = positionUs; + sampleExtractor.seekTo(positionUs); + for (int i = 0; i < trackStates.length; ++i) { + if (trackStates[i] != TRACK_STATE_DISABLED) { + pendingDiscontinuities[i] = true; + } + } + } + } + + @Override + public long getBufferedPositionUs() { + Assertions.checkState(prepared); + return sampleExtractor.getBufferedPositionUs(); + } + + @Override + public void release() { + Assertions.checkState(remainingReleaseCount > 0); + if (--remainingReleaseCount == 0) { + sampleExtractor.release(); + } + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/source/FrameworkSampleExtractor.java b/library/src/main/java/com/google/android/exoplayer/source/FrameworkSampleExtractor.java new file mode 100644 index 0000000000..e4ab4805c6 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/source/FrameworkSampleExtractor.java @@ -0,0 +1,196 @@ +/* + * 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.source; + +import com.google.android.exoplayer.C; +import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.MediaFormatHolder; +import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer.SampleSource; +import com.google.android.exoplayer.TrackInfo; +import com.google.android.exoplayer.TrackRenderer; +import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.Util; + +import android.annotation.TargetApi; +import android.content.Context; +import android.media.MediaExtractor; +import android.net.Uri; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.Map; +import java.util.UUID; + +/** {@link SampleExtractor} that extracts samples from a stream using {@link MediaExtractor}. */ +// TODO: This implementation needs to be fixed so that its methods are non-blocking (either +// through use of a background thread, or through changes to the framework's MediaExtractor API). +@TargetApi(16) +public final class FrameworkSampleExtractor implements SampleExtractor { + + // Parameters for a Uri data source. + private final Context context; + private final Uri uri; + private final Map headers; + + // Parameters for a FileDescriptor data source. + private final FileDescriptor fileDescriptor; + private final long fileDescriptorOffset; + private final long fileDescriptorLength; + + private final MediaExtractor mediaExtractor; + + private TrackInfo[] trackInfos; + + /** + * Instantiates a new sample extractor reading from the specified {@code uri}. + * + * @param context Context for resolving {@code uri}. + * @param uri The content URI from which to extract data. + * @param headers Headers to send with requests for data. + */ + public FrameworkSampleExtractor(Context context, Uri uri, Map headers) { + Assertions.checkState(Util.SDK_INT >= 16); + + this.context = Assertions.checkNotNull(context); + this.uri = Assertions.checkNotNull(uri); + this.headers = headers; + + fileDescriptor = null; + fileDescriptorOffset = 0; + fileDescriptorLength = 0; + + mediaExtractor = new MediaExtractor(); + } + + /** + * Instantiates a new sample extractor reading from the specified seekable {@code fileDescriptor}. + * The caller is responsible for releasing the file descriptor. + * + * @param fileDescriptor File descriptor from which to read. + * @param offset The offset in bytes into the file where the data to be extracted starts. + * @param length The length in bytes of the data to be extracted. + */ + public FrameworkSampleExtractor(FileDescriptor fileDescriptor, long offset, long length) { + Assertions.checkState(Util.SDK_INT >= 16); + + context = null; + uri = null; + headers = null; + + this.fileDescriptor = Assertions.checkNotNull(fileDescriptor); + fileDescriptorOffset = offset; + fileDescriptorLength = length; + + mediaExtractor = new MediaExtractor(); + } + + @Override + public boolean prepare() throws IOException { + if (context != null) { + mediaExtractor.setDataSource(context, uri, headers); + } else { + mediaExtractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength); + } + + int trackCount = mediaExtractor.getTrackCount(); + trackInfos = new TrackInfo[trackCount]; + for (int i = 0; i < trackCount; i++) { + android.media.MediaFormat format = mediaExtractor.getTrackFormat(i); + long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION) + ? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US; + String mime = format.getString(android.media.MediaFormat.KEY_MIME); + trackInfos[i] = new TrackInfo(mime, durationUs); + } + + return true; + } + + @Override + public TrackInfo[] getTrackInfos() { + return trackInfos; + } + + @Override + public void selectTrack(int index) { + mediaExtractor.selectTrack(index); + } + + @Override + public void deselectTrack(int index) { + mediaExtractor.unselectTrack(index); + } + + @Override + public long getBufferedPositionUs() { + long bufferedDurationUs = mediaExtractor.getCachedDuration(); + if (bufferedDurationUs == -1) { + return TrackRenderer.UNKNOWN_TIME_US; + } else { + long sampleTime = mediaExtractor.getSampleTime(); + return sampleTime == -1 ? TrackRenderer.END_OF_TRACK_US : sampleTime + bufferedDurationUs; + } + } + + @Override + public void seekTo(long positionUs) { + mediaExtractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + + @Override + public void getTrackMediaFormat(int track, MediaFormatHolder mediaFormatHolder) { + mediaFormatHolder.format = + MediaFormat.createFromFrameworkMediaFormatV16(mediaExtractor.getTrackFormat(track)); + mediaFormatHolder.drmInitData = Util.SDK_INT >= 18 ? getPsshInfoV18() : null; + } + + @Override + public int readSample(int track, SampleHolder sampleHolder) { + int sampleTrack = mediaExtractor.getSampleTrackIndex(); + if (sampleTrack != track) { + return sampleTrack < 0 ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ; + } + + if (sampleHolder.data != null) { + int offset = sampleHolder.data.position(); + sampleHolder.size = mediaExtractor.readSampleData(sampleHolder.data, offset); + sampleHolder.data.position(offset + sampleHolder.size); + } else { + sampleHolder.size = 0; + } + sampleHolder.timeUs = mediaExtractor.getSampleTime(); + sampleHolder.flags = mediaExtractor.getSampleFlags(); + if ((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) { + sampleHolder.cryptoInfo.setFromExtractorV16(mediaExtractor); + } + + mediaExtractor.advance(); + + return SampleSource.SAMPLE_READ; + } + + @Override + public void release() { + mediaExtractor.release(); + } + + @TargetApi(18) + private Map getPsshInfoV18() { + Map psshInfo = mediaExtractor.getPsshInfo(); + return (psshInfo == null || psshInfo.isEmpty()) ? null : psshInfo; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/source/SampleExtractor.java b/library/src/main/java/com/google/android/exoplayer/source/SampleExtractor.java new file mode 100644 index 0000000000..bce39cc9fc --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/source/SampleExtractor.java @@ -0,0 +1,100 @@ +/* + * 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.source; + +import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.MediaFormatHolder; +import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer.SampleSource; +import com.google.android.exoplayer.TrackInfo; +import com.google.android.exoplayer.TrackRenderer; + +import java.io.IOException; + +/** + * Extractor for reading track metadata and samples stored in tracks. + * + *

Call {@link #prepare} until it returns {@code true}, then access track metadata via + * {@link #getTrackInfos} and {@link #getTrackMediaFormat}. + * + *

Pass indices of tracks to read from to {@link #selectTrack}. A track can later be deselected + * by calling {@link #deselectTrack}. It is safe to select/deselect tracks after reading sample + * data or seeking. Initially, all tracks are deselected. + * + *

Call {@link #release()} when the extractor is no longer needed to free resources. + */ +public interface SampleExtractor { + + /** + * Prepares the extractor for reading track metadata and samples. + * + * @return Whether the source is ready; if {@code false}, {@link #prepare()} must be called again. + * @throws IOException Thrown if the source can't be read. + */ + boolean prepare() throws IOException; + + /** Returns track information about all tracks that can be selected. */ + TrackInfo[] getTrackInfos(); + + /** Selects the track at {@code index} for reading sample data. */ + void selectTrack(int index); + + /** Deselects the track at {@code index}, so no more samples will be read from that track. */ + void deselectTrack(int index); + + /** + * Returns an estimate of the position up to which data is buffered. + * + *

This method should not be called until after the extractor has been successfully prepared. + * + * @return An estimate of the absolute position in microseconds up to which data is buffered, + * or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, or + * {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available. + */ + long getBufferedPositionUs(); + + /** + * Seeks to the specified time in microseconds. + * + *

This method should not be called until after the extractor has been successfully prepared. + * + * @param positionUs The seek position in microseconds. + */ + void seekTo(long positionUs); + + /** Stores the {@link MediaFormat} of {@code track}. */ + void getTrackMediaFormat(int track, MediaFormatHolder mediaFormatHolder); + + /** + * Reads the next sample in the track at index {@code track} into {@code sampleHolder}, returning + * {@link SampleSource#SAMPLE_READ} if it is available. + * + *

Advances to the next sample if a sample was read. + * + * @param track The index of the track from which to read a sample. + * @param sampleHolder The holder for read sample data, if {@link SampleSource#SAMPLE_READ} is + * returned. + * @return {@link SampleSource#SAMPLE_READ} if a sample was read into {@code sampleHolder}, or + * {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or + * {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not + * loaded. + */ + int readSample(int track, SampleHolder sampleHolder); + + /** Releases resources associated with this extractor. */ + void release(); + +}