/*
* 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.hls;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.extractor.DefaultTrackOutput;
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.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
import android.util.SparseArray;
import java.io.IOException;
/**
* Wraps a {@link Extractor}, adding functionality to enable reading of the extracted samples.
*/
public final class HlsExtractorWrapper implements ExtractorOutput {
public final int trigger;
public final Format format;
public final long startTimeUs;
private final Extractor extractor;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final boolean shouldSpliceIn;
private final int adaptiveMaxWidth;
private final int adaptiveMaxHeight;
private MediaFormat[] sampleQueueFormats;
private Allocator allocator;
private volatile boolean tracksBuilt;
// Accessed only by the consuming thread.
private boolean prepared;
private boolean spliceConfigured;
public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, Extractor extractor,
boolean shouldSpliceIn, int adaptiveMaxWidth, int adaptiveMaxHeight) {
this.trigger = trigger;
this.format = format;
this.startTimeUs = startTimeUs;
this.extractor = extractor;
this.shouldSpliceIn = shouldSpliceIn;
this.adaptiveMaxWidth = adaptiveMaxWidth;
this.adaptiveMaxHeight = adaptiveMaxHeight;
sampleQueues = new SparseArray<>();
}
/**
* Initializes the wrapper for use.
*
* @param allocator An allocator for obtaining allocations into which extracted data is written.
*/
public void init(Allocator allocator) {
this.allocator = allocator;
extractor.init(this);
}
/**
* Whether the extractor is prepared.
*
* @return True if the extractor is prepared. False otherwise.
*/
public boolean isPrepared() {
if (!prepared && tracksBuilt) {
for (int i = 0; i < sampleQueues.size(); i++) {
if (!sampleQueues.valueAt(i).hasFormat()) {
return false;
}
}
prepared = true;
sampleQueueFormats = new MediaFormat[sampleQueues.size()];
for (int i = 0; i < sampleQueueFormats.length; i++) {
MediaFormat format = sampleQueues.valueAt(i).getFormat();
if (MimeTypes.isVideo(format.mimeType) && (adaptiveMaxWidth != MediaFormat.NO_VALUE
|| adaptiveMaxHeight != MediaFormat.NO_VALUE)) {
format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight);
}
sampleQueueFormats[i] = format;
}
}
return prepared;
}
/**
* Clears queues for all tracks, returning all allocations to the allocator.
*/
public void clear() {
for (int i = 0; i < sampleQueues.size(); i++) {
sampleQueues.valueAt(i).clear();
}
}
/**
* Gets the largest timestamp of any sample parsed by the extractor.
*
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed.
*/
public long getLargestParsedTimestampUs() {
long largestParsedTimestampUs = Long.MIN_VALUE;
for (int i = 0; i < sampleQueues.size(); i++) {
largestParsedTimestampUs = Math.max(largestParsedTimestampUs,
sampleQueues.valueAt(i).getLargestParsedTimestampUs());
}
return largestParsedTimestampUs;
}
/**
* Attempts to configure a splice from this extractor to the next.
* <p>
* The splice is performed such that for each track the samples read from the next extractor
* start with a keyframe, and continue from where the samples read from this extractor finish.
* A successful splice may discard samples from either or both extractors.
* <p>
* Splice configuration may fail if the next extractor is not yet in a state that allows the
* splice to be performed. Calling this method is a noop if the splice has already been
* configured. Hence this method should be called repeatedly during the window within which a
* splice can be performed.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param nextExtractor The extractor being spliced to.
*/
public final void configureSpliceTo(HlsExtractorWrapper nextExtractor) {
Assertions.checkState(isPrepared());
if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) {
// The splice is already configured, or the next extractor doesn't want to be spliced in, or
// the next extractor isn't ready to be spliced in.
return;
}
boolean spliceConfigured = true;
int trackCount = getTrackCount();
for (int i = 0; i < trackCount; i++) {
DefaultTrackOutput currentSampleQueue = sampleQueues.valueAt(i);
DefaultTrackOutput nextSampleQueue = nextExtractor.sampleQueues.valueAt(i);
spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue);
}
this.spliceConfigured = spliceConfigured;
return;
}
/**
* Gets the number of available tracks.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @return The number of available tracks.
*/
public int getTrackCount() {
Assertions.checkState(isPrepared());
return sampleQueues.size();
}
/**
* Gets the {@link MediaFormat} of the specified track.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param track The track index.
* @return The corresponding format.
*/
public MediaFormat getMediaFormat(int track) {
Assertions.checkState(isPrepared());
return sampleQueueFormats[track];
}
/**
* Gets the next sample for the specified track.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param track The track from which to read.
* @param holder A {@link SampleHolder} into which the sample should be read.
* @return True if a sample was read. False otherwise.
*/
public boolean getSample(int track, SampleHolder holder) {
Assertions.checkState(isPrepared());
return sampleQueues.valueAt(track).getSample(holder);
}
/**
* Discards samples for the specified track up to the specified time.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param track The track from which samples should be discarded.
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public void discardUntil(int track, long timeUs) {
Assertions.checkState(isPrepared());
sampleQueues.valueAt(track).discardUntil(timeUs);
}
/**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the
* specified track.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
* for the specified track. False otherwise.
*/
public boolean hasSamples(int track) {
Assertions.checkState(isPrepared());
return !sampleQueues.valueAt(track).isEmpty();
}
/**
* 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 int read(ExtractorInput input) throws IOException, InterruptedException {
int result = extractor.read(input, null);
Assertions.checkState(result != Extractor.RESULT_SEEK);
return result;
}
// ExtractorOutput implementation.
@Override
public TrackOutput track(int id) {
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator);
sampleQueues.put(id, sampleQueue);
return sampleQueue;
}
@Override
public void endTracks() {
this.tracksBuilt = true;
}
@Override
public void seekMap(SeekMap seekMap) {
// Do nothing.
}
@Override
public void drmInitData(DrmInitData drmInit) {
// Do nothing.
}
}