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:
parent
2e3ffe1e94
commit
c3c176d93c
@ -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(
|
||||
TimestampAdjuster 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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user