Commit 8744e8dc by olly Committed by Oliver Woodman

Refactor #6.HLS.4

Pull more logic up to HlsSampleSource. Somewhat regretfully,
this also backs out the optimization work done toward the
ref'd issue. I think that's one for another time perhaps...

Issue: #551
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123417413
parent 6d62962a
...@@ -28,9 +28,7 @@ import com.google.android.exoplayer.extractor.mp3.Mp3Extractor; ...@@ -28,9 +28,7 @@ import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.extractor.ts.AdtsExtractor; import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster; import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster;
import com.google.android.exoplayer.extractor.ts.TsExtractor; import com.google.android.exoplayer.extractor.ts.TsExtractor;
import com.google.android.exoplayer.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist; import com.google.android.exoplayer.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser; import com.google.android.exoplayer.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer.hls.playlist.Variant; import com.google.android.exoplayer.hls.playlist.Variant;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
...@@ -48,10 +46,8 @@ import android.util.Log; ...@@ -48,10 +46,8 @@ import android.util.Log;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.List;
import java.util.Locale; import java.util.Locale;
/** /**
...@@ -70,54 +66,53 @@ public class HlsChunkSource { ...@@ -70,54 +66,53 @@ public class HlsChunkSource {
private static final String VTT_FILE_EXTENSION = ".vtt"; private static final String VTT_FILE_EXTENSION = ".vtt";
private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
private final int type; private final String baseUri;
private final DataSource dataSource; private final DataSource dataSource;
private final FormatEvaluator adaptiveFormatEvaluator; private final FormatEvaluator adaptiveFormatEvaluator;
private final Evaluation evaluation; private final Evaluation evaluation;
private final HlsPlaylistParser playlistParser; private final HlsPlaylistParser playlistParser;
private final PtsTimestampAdjusterProvider timestampAdjusterProvider; private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
private final Variant[] variants;
private final HlsMediaPlaylist[] variantPlaylists;
private final long[] variantLastPlaylistLoadTimesMs;
private byte[] scratchSpace; private byte[] scratchSpace;
private boolean live; private boolean live;
private long durationUs; private long durationUs;
private IOException fatalError; private IOException fatalError;
private String baseUri;
private Format muxedAudioFormat;
private Format muxedCaptionFormat;
private Uri encryptionKeyUri; private Uri encryptionKeyUri;
private byte[] encryptionKey; private byte[] encryptionKey;
private String encryptionIvString; private String encryptionIvString;
private byte[] encryptionIv; private byte[] encryptionIv;
// Properties of exposed tracks.
private Variant[] variants;
private HlsMediaPlaylist[] variantPlaylists;
private long[] variantLastPlaylistLoadTimesMs;
// Properties of enabled variants. // Properties of enabled variants.
private Variant[] enabledVariants; private Variant[] enabledVariants;
private long[] enabledVariantBlacklistTimes; private long[] enabledVariantBlacklistTimes;
private boolean[] enabledVariantBlacklistFlags; private boolean[] enabledVariantBlacklistFlags;
/** /**
* @param type The type of chunk provided by the source. One of {@link C#TRACK_TYPE_DEFAULT}, * @param baseUri The playlist's base uri.
* {@link C#TRACK_TYPE_AUDIO} and {@link C#TRACK_TYPE_TEXT}. * @param variants The available variants.
* @param dataSource A {@link DataSource} suitable for loading the media data. * @param dataSource A {@link DataSource} suitable for loading the media data.
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
* same provider. * same provider.
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
*/ */
public HlsChunkSource(int type, DataSource dataSource, public HlsChunkSource(String baseUri, Variant[] variants, DataSource dataSource,
PtsTimestampAdjusterProvider timestampAdjusterProvider, PtsTimestampAdjusterProvider timestampAdjusterProvider,
FormatEvaluator adaptiveFormatEvaluator) { FormatEvaluator adaptiveFormatEvaluator) {
this.type = type; this.baseUri = baseUri;
this.variants = variants;
this.dataSource = dataSource; this.dataSource = dataSource;
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
this.timestampAdjusterProvider = timestampAdjusterProvider; this.timestampAdjusterProvider = timestampAdjusterProvider;
playlistParser = new HlsPlaylistParser(); playlistParser = new HlsPlaylistParser();
evaluation = new Evaluation(); evaluation = new Evaluation();
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
selectTracks(new int[] {0});
} }
/** /**
...@@ -142,19 +137,6 @@ public class HlsChunkSource { ...@@ -142,19 +137,6 @@ public class HlsChunkSource {
} }
/** /**
* Prepares the source.
*
* @param playlist A {@link HlsPlaylist}.
*/
public void prepare(HlsPlaylist playlist) {
processPlaylist(playlist);
if (variants.length > 0) {
// Select the first variant listed in the master playlist.
selectTracks(new int[] {0});
}
}
/**
* Returns whether this is a live playback. * Returns whether this is a live playback.
* <p> * <p>
* This method should only be called after the source has been prepared. * This method should only be called after the source has been prepared.
...@@ -200,28 +182,6 @@ public class HlsChunkSource { ...@@ -200,28 +182,6 @@ public class HlsChunkSource {
} }
/** /**
* Returns the format of the audio muxed into variants, or null if unknown.
* <p>
* This method should only be called after the source has been prepared.
*
* @return The format of the audio muxed into variants, or null if unknown.
*/
public Format getMuxedAudioFormat() {
return muxedAudioFormat;
}
/**
* Returns the format of the captions muxed into variants, or null if unknown.
* <p>
* This method should only be called after the source has been prepared.
*
* @return The format of the captions muxed into variants, or null if unknown.
*/
public Format getMuxedCaptionFormat() {
return muxedCaptionFormat;
}
/**
* Selects tracks for use. * Selects tracks for use.
* <p> * <p>
* This method should only be called after the source has been prepared. * This method should only be called after the source has been prepared.
...@@ -270,17 +230,6 @@ public class HlsChunkSource { ...@@ -270,17 +230,6 @@ public class HlsChunkSource {
} }
/** /**
* Notifies the source that a seek has occurred.
* <p>
* This method should only be called after the source has been prepared.
*/
public void seek() {
if (type == C.TRACK_TYPE_DEFAULT) {
timestampAdjusterProvider.reset();
}
}
/**
* Resets the source. * Resets the source.
* <p> * <p>
* This method should only be called after the source has been prepared. * This method should only be called after the source has been prepared.
...@@ -493,90 +442,6 @@ public class HlsChunkSource { ...@@ -493,90 +442,6 @@ public class HlsChunkSource {
// Private methods. // Private methods.
private void processPlaylist(HlsPlaylist playlist) {
baseUri = playlist.baseUri;
if (playlist instanceof HlsMediaPlaylist) {
if (type == C.TRACK_TYPE_TEXT || type == C.TRACK_TYPE_AUDIO) {
variants = new Variant[0];
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
return;
}
// type == C.TRACK_TYPE_DEFAULT
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null,
Format.NO_VALUE);
variants = new Variant[] {new Variant(baseUri, format, null)};
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
setMediaPlaylist(0, (HlsMediaPlaylist) playlist);
return;
}
HlsMasterPlaylist masterPlaylist = (HlsMasterPlaylist) playlist;
muxedAudioFormat = masterPlaylist.muxedAudioFormat;
muxedCaptionFormat = masterPlaylist.muxedCaptionFormat;
if (type == C.TRACK_TYPE_TEXT || type == C.TRACK_TYPE_AUDIO) {
List<Variant> variantList = type == C.TRACK_TYPE_AUDIO ? masterPlaylist.audios
: masterPlaylist.subtitles;
if (variantList != null && !variantList.isEmpty()) {
variants = new Variant[variantList.size()];
variantList.toArray(variants);
} else {
variants = new Variant[0];
}
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
return;
}
// type == C.TRACK_TYPE_DEFAULT
List<Variant> enabledVariantList = new ArrayList<>(masterPlaylist.variants);
ArrayList<Variant> definiteVideoVariants = new ArrayList<>();
ArrayList<Variant> definiteAudioOnlyVariants = new ArrayList<>();
for (int i = 0; i < enabledVariantList.size(); i++) {
Variant variant = enabledVariantList.get(i);
if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) {
definiteVideoVariants.add(variant);
} else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) {
definiteAudioOnlyVariants.add(variant);
}
}
if (!definiteVideoVariants.isEmpty()) {
// We've identified some variants as definitely containing video. Assume variants within the
// master playlist are marked consistently, and hence that we have the full set. Filter out
// any other variants, which are likely to be audio only.
enabledVariantList = definiteVideoVariants;
} else if (definiteAudioOnlyVariants.size() < enabledVariantList.size()) {
// We've identified some variants, but not all, as being audio only. Filter them out to leave
// the remaining variants, which are likely to contain video.
enabledVariantList.removeAll(definiteAudioOnlyVariants);
} else {
// Leave the enabled variants unchanged. They're likely either all video or all audio.
}
variants = new Variant[enabledVariantList.size()];
enabledVariantList.toArray(variants);
variantPlaylists = new HlsMediaPlaylist[variants.length];
variantLastPlaylistLoadTimesMs = new long[variants.length];
}
private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) {
String codecs = variant.codecs;
if (TextUtils.isEmpty(codecs)) {
return false;
}
String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)");
for (String codec : codecArray) {
if (codec.startsWith(prefix)) {
return true;
}
}
return false;
}
private int getNextVariantIndex(HlsMediaChunk previous, long playbackPositionUs) { private int getNextVariantIndex(HlsMediaChunk previous, long playbackPositionUs) {
clearStaleBlacklistedVariants(); clearStaleBlacklistedVariants();
if (enabledVariants.length > 1) { if (enabledVariants.length > 1) {
......
...@@ -62,12 +62,14 @@ import java.util.List; ...@@ -62,12 +62,14 @@ import java.util.List;
private final ChunkHolder nextChunkHolder; private final ChunkHolder nextChunkHolder;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final LoadControl loadControl; private final LoadControl loadControl;
private final Format muxedAudioFormat;
private final Format muxedCaptionFormat;
private volatile boolean sampleQueuesBuilt; private volatile boolean sampleQueuesBuilt;
private boolean prepared; private boolean prepared;
private boolean seenFirstTrackSelection; private boolean seenFirstTrackSelection;
private boolean notifyReset; private boolean readingEnabled;
private int enabledTrackCount; private int enabledTrackCount;
private Format downstreamFormat; private Format downstreamFormat;
...@@ -88,6 +90,10 @@ import java.util.List; ...@@ -88,6 +90,10 @@ import java.util.List;
* @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained. * @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained.
* @param loadControl Controls when the source is permitted to load data. * @param loadControl Controls when the source is permitted to load data.
* @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes.
* @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed audio,
* this is the audio {@link Format} as defined by the playlist.
* @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed
* captions, this is the audio {@link Format} as defined by the playlist.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required. * null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required. * @param eventListener A listener of events. May be null if delivery of events is not required.
...@@ -96,16 +102,20 @@ import java.util.List; ...@@ -96,16 +102,20 @@ import java.util.List;
* before propagating an error. * before propagating an error.
*/ */
public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl, public HlsTrackStreamWrapper(HlsChunkSource chunkSource, LoadControl loadControl,
int bufferSizeContribution, Handler eventHandler, int bufferSizeContribution, Format muxedAudioFormat, Format muxedCaptionFormat,
ChunkTrackStreamEventListener eventListener, int eventSourceId, int minLoadableRetryCount) { Handler eventHandler, ChunkTrackStreamEventListener eventListener, int eventSourceId,
int minLoadableRetryCount) {
this.chunkSource = chunkSource; this.chunkSource = chunkSource;
this.loadControl = loadControl; this.loadControl = loadControl;
this.bufferSizeContribution = bufferSizeContribution; this.bufferSizeContribution = bufferSizeContribution;
this.muxedAudioFormat = muxedAudioFormat;
this.muxedCaptionFormat = muxedCaptionFormat;
loader = new Loader("Loader:HlsTrackStreamWrapper", minLoadableRetryCount); loader = new Loader("Loader:HlsTrackStreamWrapper", minLoadableRetryCount);
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId); eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
nextChunkHolder = new ChunkHolder(); nextChunkHolder = new ChunkHolder();
sampleQueues = new SparseArray<>(); sampleQueues = new SparseArray<>();
mediaChunks = new LinkedList<>(); mediaChunks = new LinkedList<>();
readingEnabled = true;
pendingResetPositionUs = C.UNSET_TIME_US; pendingResetPositionUs = C.UNSET_TIME_US;
} }
...@@ -150,6 +160,10 @@ import java.util.List; ...@@ -150,6 +160,10 @@ import java.util.List;
return chunkSource.getDurationUs(); return chunkSource.getDurationUs();
} }
public boolean isLive() {
return chunkSource.isLive();
}
public TrackGroupArray getTrackGroups() { public TrackGroupArray getTrackGroups() {
return trackGroups; return trackGroups;
} }
...@@ -211,12 +225,8 @@ import java.util.List; ...@@ -211,12 +225,8 @@ import java.util.List;
} }
} }
public long readReset() { public void setReadingEnabled(boolean readingEnabled) {
if (notifyReset) { this.readingEnabled = readingEnabled;
notifyReset = false;
return lastSeekPositionUs;
}
return C.UNSET_TIME_US;
} }
public long getBufferedPositionUs() { public long getBufferedPositionUs() {
...@@ -265,7 +275,7 @@ import java.util.List; ...@@ -265,7 +275,7 @@ import java.util.List;
} }
/* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) { /* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) {
if (notifyReset || isPendingReset()) { if (!readingEnabled || isPendingReset()) {
return TrackStream.NOTHING_READ; return TrackStream.NOTHING_READ;
} }
...@@ -449,9 +459,9 @@ import java.util.List; ...@@ -449,9 +459,9 @@ import java.util.List;
Format trackFormat = null; Format trackFormat = null;
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) { if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) { if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
trackFormat = chunkSource.getMuxedAudioFormat(); trackFormat = muxedAudioFormat;
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) { } else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) {
trackFormat = chunkSource.getMuxedCaptionFormat(); trackFormat = muxedCaptionFormat;
} }
} }
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat)); trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat));
...@@ -494,33 +504,10 @@ import java.util.List; ...@@ -494,33 +504,10 @@ import java.util.List;
* @param positionUs The position to seek to. * @param positionUs The position to seek to.
*/ */
private void seekToInternal(long positionUs) { private void seekToInternal(long positionUs) {
// Treat all seeks into non-seekable media as being to t=0.
positionUs = chunkSource.isLive() ? 0 : positionUs;
lastSeekPositionUs = positionUs; lastSeekPositionUs = positionUs;
downstreamPositionUs = positionUs; downstreamPositionUs = positionUs;
notifyReset = true;
boolean seekInsideBuffer = !isPendingReset();
// TODO[REFACTOR]: This will nearly always fail to seek inside all buffers due to sparse tracks
// such as ID3 (probably EIA608 too). We need a way to not care if we can't seek to the keyframe
// before for such tracks. For ID3 we probably explicitly don't want the keyframe before, even
// if we do have it, since it might be quite a long way behind the seek position. We probably
// only want to output ID3 buffers whose timestamps are greater than or equal to positionUs.
int sampleQueueCount = sampleQueues.size();
for (int i = 0; seekInsideBuffer && i < sampleQueueCount; i++) {
if (groupEnabledStates[i]) {
seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs);
}
}
if (seekInsideBuffer) {
while (mediaChunks.size() > 1 && mediaChunks.get(1).startTimeUs <= positionUs) {
mediaChunks.removeFirst();
}
} else {
// If we failed to seek within the sample queues, we need to restart.
chunkSource.seek();
restartFrom(positionUs); restartFrom(positionUs);
} }
}
private void discardSamplesForDisabledTracks() { private void discardSamplesForDisabledTracks() {
if (!prepared) { if (!prepared) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment