Move HLS extractor construction to HlsMediaChunk

This allows ID3 PRIV timestamp extraction and Extractor Sniffing.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=140209568
This commit is contained in:
aquilescanta 2016-11-25 10:26:05 -08:00 committed by Oliver Woodman
parent 2e3ffe1e94
commit c3c176d93c
2 changed files with 85 additions and 102 deletions

View File

@ -17,17 +17,9 @@ package com.google.android.exoplayer2.source.hls;
import android.net.Uri;
import android.os.SystemClock;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.chunk.Chunk;
@ -41,7 +33,6 @@ import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.UriUtil;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
@ -89,14 +80,6 @@ import java.util.Locale;
}
private static final String AAC_FILE_EXTENSION = ".aac";
private static final String AC3_FILE_EXTENSION = ".ac3";
private static final String EC3_FILE_EXTENSION = ".ec3";
private static final String MP3_FILE_EXTENSION = ".mp3";
private static final String MP4_FILE_EXTENSION = ".mp4";
private static final String VTT_FILE_EXTENSION = ".vtt";
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
private final DataSource dataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider;
private final HlsUrl[] variants;
@ -281,68 +264,10 @@ import java.util.Locale;
if (previous != null && !switchingVariant) {
startTimeUs = previous.getAdjustedEndTimeUs();
}
Format format = variants[newVariantIndex].format;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Set the extractor that will read the chunk.
Extractor extractor;
boolean needNewExtractor = previous == null
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| format != previous.trackFormat;
boolean extractorNeedsInit = true;
TimestampAdjuster timestampAdjuster = null;
String lastPathSegment = chunkUri.getLastPathSegment();
if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
// TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3
// case below.
extractor = new AdtsExtractor(startTimeUs);
} else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)) {
extractor = new Ac3Extractor(startTimeUs);
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
extractor = new Mp3Extractor(startTimeUs);
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
startTimeUs);
extractor = new WebvttExtractor(format.language, timestampAdjuster);
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) {
if (needNewExtractor) {
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
extractor = new FragmentedMp4Extractor(0, timestampAdjuster);
} else {
extractorNeedsInit = false;
extractor = previous.extractor;
}
} else if (needNewExtractor) {
// MPEG-2 TS segments, but we need a new extractor.
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
// This flag ensures the change of pid between streams does not affect the sample queues.
@DefaultTsPayloadReaderFactory.Flags
int esReaderFactoryFlags = 0;
String codecs = format.codecs;
if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can
// explicitly ignore them even if they're declared.
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM;
}
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM;
}
}
extractor = new TsExtractor(timestampAdjuster,
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true);
} else {
// MPEG-2 TS segments, and we need to continue using the same extractor.
extractor = previous.extractor;
extractorNeedsInit = false;
}
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
DataSpec initDataSpec = null;
Segment initSegment = mediaPlaylist.initializationSegment;
@ -356,9 +281,9 @@ import java.util.Locale;
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null);
out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex],
trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
segment, chunkMediaSequence, isTimestampMaster, timestampAdjuster, extractor,
extractorNeedsInit, switchingVariant, encryptionKey, encryptionIv);
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segment,
chunkMediaSequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey,
encryptionIv);
}
/**

View File

@ -15,15 +15,23 @@
*/
package com.google.android.exoplayer2.source.hls;
import android.text.TextUtils;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
@ -35,6 +43,14 @@ import java.util.concurrent.atomic.AtomicInteger;
private static final AtomicInteger UID_SOURCE = new AtomicInteger();
private static final String AAC_FILE_EXTENSION = ".aac";
private static final String AC3_FILE_EXTENSION = ".ac3";
private static final String EC3_FILE_EXTENSION = ".ec3";
private static final String MP3_FILE_EXTENSION = ".mp3";
private static final String MP4_FILE_EXTENSION = ".mp4";
private static final String VTT_FILE_EXTENSION = ".vtt";
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
/**
* A unique identifier for the chunk.
*/
@ -45,11 +61,6 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public final int discontinuitySequenceNumber;
/**
* The extractor into which this chunk is being consumed.
*/
public final Extractor extractor;
/**
* The url of the playlist from which this chunk was obtained.
*/
@ -58,11 +69,11 @@ import java.util.concurrent.atomic.AtomicInteger;
private final DataSource initDataSource;
private final DataSpec initDataSpec;
private final boolean isEncrypted;
private final boolean extractorNeedsInit;
private final boolean shouldSpliceIn;
private final boolean isMasterTimestampSource;
private final TimestampAdjuster timestampAdjuster;
private final HlsMediaChunk previousChunk;
private Extractor extractor;
private int initSegmentBytesLoaded;
private int bytesLoaded;
private boolean initLoadCompleted;
@ -82,19 +93,14 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param chunkIndex The media sequence number of the chunk.
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param extractor The extractor to decode samples from the data.
* @param extractorNeedsInit Whether the extractor needs initializing with the target
* {@link HlsSampleStreamWrapper}.
* @param shouldSpliceIn Whether the samples parsed from this chunk should be spliced into any
* samples already queued to the {@link HlsSampleStreamWrapper}.
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
* @param encryptionKey For AES encryption chunks, the encryption key.
* @param encryptionIv For AES encryption chunks, the encryption initialization vector.
*/
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment,
int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster,
Extractor extractor, boolean extractorNeedsInit, boolean shouldSpliceIn, byte[] encryptionKey,
byte[] encryptionIv) {
HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format,
trackSelectionReason, trackSelectionData, segment.startTimeUs,
segment.startTimeUs + segment.durationUs, chunkIndex);
@ -102,9 +108,7 @@ import java.util.concurrent.atomic.AtomicInteger;
this.hlsUrl = hlsUrl;
this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster;
this.extractor = extractor;
this.extractorNeedsInit = extractorNeedsInit;
this.shouldSpliceIn = shouldSpliceIn;
this.previousChunk = previousChunk;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
initDataSource = dataSource;
@ -121,10 +125,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
public void init(HlsSampleStreamWrapper output) {
extractorOutput = output;
output.init(uid, shouldSpliceIn);
if (extractorNeedsInit) {
extractor.init(output);
}
output.init(uid, previousChunk != null && previousChunk.hlsUrl != hlsUrl);
}
/**
@ -165,6 +166,9 @@ import java.util.concurrent.atomic.AtomicInteger;
@Override
public void load() throws IOException, InterruptedException {
if (extractor == null) {
extractor = buildExtractor();
}
maybeLoadInitData();
if (!loadCanceled) {
loadMedia();
@ -173,8 +177,62 @@ import java.util.concurrent.atomic.AtomicInteger;
// Private methods.
private Extractor buildExtractor() {
// Set the extractor that will read the chunk.
Extractor extractor;
boolean needNewExtractor = previousChunk == null
|| previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|| trackFormat != previousChunk.trackFormat;
boolean usingNewExtractor = true;
String lastPathSegment = dataSpec.uri.getLastPathSegment();
if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
// TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner
// identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3
// case below.
extractor = new AdtsExtractor(startTimeUs);
} else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)) {
extractor = new Ac3Extractor(startTimeUs);
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
extractor = new Mp3Extractor(startTimeUs);
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
extractor = new WebvttExtractor(trackFormat.language, timestampAdjuster);
} else if (!needNewExtractor) {
// Only reuse TS and fMP4 extractors.
usingNewExtractor = false;
extractor = previousChunk.extractor;
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) {
extractor = new FragmentedMp4Extractor(0, timestampAdjuster);
} else {
// MPEG-2 TS segments, but we need a new extractor.
// This flag ensures the change of pid between streams does not affect the sample queues.
@DefaultTsPayloadReaderFactory.Flags
int esReaderFactoryFlags = 0;
String codecs = trackFormat.codecs;
if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can
// explicitly ignore them even if they're declared.
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM;
}
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM;
}
}
extractor = new TsExtractor(timestampAdjuster,
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true);
}
if (usingNewExtractor) {
extractor.init(extractorOutput);
}
return extractor;
}
private void maybeLoadInitData() throws IOException, InterruptedException {
if (!extractorNeedsInit || initLoadCompleted || initDataSpec == null) {
if (previousChunk == null || previousChunk.extractor != extractor || initLoadCompleted
|| initDataSpec == null) {
return;
}
DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded);