Allow extractor injection for HLS

Issue:#2748

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=172726367
This commit is contained in:
aquilescanta 2017-10-19 03:57:51 -07:00 committed by Oliver Woodman
parent 49aca6e9ce
commit 2cfc478c3e
12 changed files with 287 additions and 165 deletions

View file

@ -112,16 +112,11 @@ public final class Mp3Extractor implements Extractor {
private long samplesRead;
private int sampleBytesRemaining;
/**
* Constructs a new {@link Mp3Extractor}.
*/
public Mp3Extractor() {
this(0);
}
/**
* Constructs a new {@link Mp3Extractor}.
*
* @param flags Flags that control the extractor's behavior.
*/
public Mp3Extractor(@Flags int flags) {
@ -129,8 +124,6 @@ public final class Mp3Extractor implements Extractor {
}
/**
* Constructs a new {@link Mp3Extractor}.
*
* @param flags Flags that control the extractor's behavior.
* @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or
* {@link C#TIME_UNSET} if forcing is not required.
@ -144,6 +137,8 @@ public final class Mp3Extractor implements Extractor {
basisTimeUs = C.TIME_UNSET;
}
// Extractor implementation.
@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
return synchronize(input, true);
@ -195,6 +190,8 @@ public final class Mp3Extractor implements Extractor {
return readSample(input);
}
// Internal methods.
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) {
extractorInput.resetPeekPosition();

View file

@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* Facilitates the extraction of AC-3 samples from elementary audio files formatted as AC-3
* bitstreams.
* Extracts samples from (E-)AC-3 bitstreams.
*/
public final class Ac3Extractor implements Extractor {
@ -71,6 +70,8 @@ public final class Ac3Extractor implements Extractor {
sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE);
}
// Extractor implementation.
@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers.

View file

@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS
* headers.
* Extracts samples from AAC bit streams with ADTS framing.
*/
public final class AdtsExtractor implements Extractor {
@ -70,6 +69,8 @@ public final class AdtsExtractor implements Extractor {
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
}
// Extractor implementation.
@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers.

View file

@ -28,7 +28,7 @@ import java.util.Collections;
import java.util.List;
/**
* Default implementation for {@link TsPayloadReader.Factory}.
* Default {@link TsPayloadReader.Factory} implementation.
*/
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory {

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2017 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.exoplayer2.source.hls;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Pair;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.Extractor;
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.util.MimeTypes;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.Collections;
import java.util.List;
/**
* Default {@link HlsExtractorFactory} implementation.
*
* <p>This class can be extended to override {@link TsExtractor} instantiation.</p>
*/
public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
public static final String AAC_FILE_EXTENSION = ".aac";
public static final String AC3_FILE_EXTENSION = ".ac3";
public static final String EC3_FILE_EXTENSION = ".ec3";
public static final String MP3_FILE_EXTENSION = ".mp3";
public static final String MP4_FILE_EXTENSION = ".mp4";
public static final String M4_FILE_EXTENSION_PREFIX = ".m4";
public static final String VTT_FILE_EXTENSION = ".vtt";
public static final String WEBVTT_FILE_EXTENSION = ".webvtt";
@Override
public Pair<Extractor, Boolean> createExtractor(Extractor previousExtractor, Uri uri,
Format format, List<Format> muxedCaptionFormats, DrmInitData drmInitData,
TimestampAdjuster timestampAdjuster) {
String lastPathSegment = uri.getLastPathSegment();
boolean isPackedAudioExtractor = false;
Extractor extractor;
if (MimeTypes.TEXT_VTT.equals(format.sampleMimeType)
|| lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
extractor = new WebvttExtractor(format.language, timestampAdjuster);
} else if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
isPackedAudioExtractor = true;
extractor = new AdtsExtractor();
} else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)) {
isPackedAudioExtractor = true;
extractor = new Ac3Extractor();
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
isPackedAudioExtractor = true;
extractor = new Mp3Extractor(0, 0);
} else if (previousExtractor != null) {
// Only reuse TS and fMP4 extractors.
extractor = previousExtractor;
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)
|| lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)) {
extractor = new FragmentedMp4Extractor(0, timestampAdjuster, null, drmInitData);
} else {
// For any other file extension, we assume TS format.
@DefaultTsPayloadReaderFactory.Flags
int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM;
if (muxedCaptionFormats != null) {
// The playlist declares closed caption renditions, we should ignore descriptors.
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS;
} else {
muxedCaptionFormats = Collections.emptyList();
}
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(TsExtractor.MODE_HLS, timestampAdjuster,
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats));
}
return Pair.create(extractor, isPackedAudioExtractor);
}
}

View file

@ -80,6 +80,7 @@ import java.util.List;
}
private final HlsExtractorFactory extractorFactory;
private final DataSource mediaDataSource;
private final DataSource encryptionDataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider;
@ -106,6 +107,8 @@ import java.util.List;
private long liveEdgeTimeUs;
/**
* @param extractorFactory An {@link HlsExtractorFactory} from which to obtain the extractors for
* media chunks.
* @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists.
* @param variants The available variants.
* @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the
@ -116,9 +119,10 @@ import java.util.List;
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist.
*/
public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants,
HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider,
List<Format> muxedCaptionFormats) {
public HlsChunkSource(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
HlsUrl[] variants, HlsDataSourceFactory dataSourceFactory,
TimestampAdjusterProvider timestampAdjusterProvider, List<Format> muxedCaptionFormats) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker;
this.variants = variants;
this.timestampAdjusterProvider = timestampAdjusterProvider;
@ -321,11 +325,11 @@ import java.util.List;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null);
out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl,
muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence,
isTimestampMaster, timestampAdjuster, previous, mediaPlaylist.drmInitData, encryptionKey,
encryptionIv);
out.chunk = new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, initDataSpec,
selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(),
trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs,
chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous,
mediaPlaylist.drmInitData, encryptionKey, encryptionIv);
}
/**

View file

@ -0,0 +1,53 @@
/*
* Copyright (C) 2017 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.exoplayer2.source.hls;
import android.net.Uri;
import android.util.Pair;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.util.List;
/**
* Factory for HLS media chunk extractors.
*/
public interface HlsExtractorFactory {
HlsExtractorFactory DEFAULT = new DefaultHlsExtractorFactory();
/**
* Creates an {@link Extractor} for extracting HLS media chunks.
*
* @param previousExtractor A previously used {@link Extractor} which can be reused if the current
* chunk is a continuation of the previously extracted chunk, or null otherwise. It is the
* responsibility of implementers to only reuse extractors that are suited for reusage.
* @param uri The URI of the media chunk.
* @param format A {@link Format} associated with the chunk to extract.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist.
* @param drmInitData {@link DrmInitData} associated with the chunk.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @return A pair containing the {@link Extractor} and a boolean that indicates whether it is a
* packed audio extractor. The first element may be {@code previousExtractor} if the factory
* has determined it can be re-used.
*/
Pair<Extractor, Boolean> createExtractor(Extractor previousExtractor, Uri uri, Format format,
List<Format> muxedCaptionFormats, DrmInitData drmInitData,
TimestampAdjuster timestampAdjuster);
}

View file

@ -15,19 +15,13 @@
*/
package com.google.android.exoplayer2.source.hls;
import android.text.TextUtils;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData;
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.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.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
@ -35,12 +29,10 @@ import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
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.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@ -49,19 +41,11 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
/* package */ final class HlsMediaChunk extends MediaChunk {
private static final AtomicInteger UID_SOURCE = new AtomicInteger();
private static final String PRIV_TIMESTAMP_FRAME_OWNER =
"com.apple.streaming.transportStreamTimestamp";
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 M4_FILE_EXTENSION_PREFIX = ".m4";
private static final String VTT_FILE_EXTENSION = ".vtt";
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
private static final AtomicInteger uidSource = new AtomicInteger();
/**
* A unique identifier for the chunk.
@ -83,26 +67,24 @@ import java.util.concurrent.atomic.AtomicInteger;
private final boolean isEncrypted;
private final boolean isMasterTimestampSource;
private final TimestampAdjuster timestampAdjuster;
private final String lastPathSegment;
private final Extractor previousExtractor;
private final boolean shouldSpliceIn;
private final boolean needNewExtractor;
private final List<Format> muxedCaptionFormats;
private final DrmInitData drmInitData;
private final boolean isPackedAudio;
private final Extractor extractor;
private final boolean isPackedAudioExtractor;
private final boolean reusingExtractor;
private final Id3Decoder id3Decoder;
private final ParsableByteArray id3Data;
private Extractor extractor;
private HlsSampleStreamWrapper output;
private int initSegmentBytesLoaded;
private int bytesLoaded;
private boolean id3TimestampPeeked;
private boolean initLoadCompleted;
private HlsSampleStreamWrapper extractorOutput;
private volatile boolean loadCanceled;
private volatile boolean loadCompleted;
/**
* @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk
* extractor is obtained.
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded.
* @param initDataSpec Defines the initialization data to be fed to new extractors. May be null.
@ -124,10 +106,10 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param encryptionIv The AES initialization vector, or null if the segment is not fully
* encrypted.
*/
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
HlsUrl hlsUrl, List<Format> muxedCaptionFormats, int trackSelectionReason,
Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex,
int discontinuitySequenceNumber, boolean isMasterTimestampSource,
public HlsMediaChunk(HlsExtractorFactory extractorFactory, DataSource dataSource,
DataSpec dataSpec, DataSpec initDataSpec, HlsUrl hlsUrl, List<Format> muxedCaptionFormats,
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource,
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, DrmInitData drmInitData,
byte[] fullSegmentEncryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv), dataSpec,
@ -136,33 +118,34 @@ import java.util.concurrent.atomic.AtomicInteger;
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec;
this.hlsUrl = hlsUrl;
this.muxedCaptionFormats = muxedCaptionFormats;
this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster;
// Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
this.drmInitData = drmInitData;
lastPathSegment = dataSpec.uri.getLastPathSegment();
isPackedAudio = lastPathSegment.endsWith(AAC_FILE_EXTENSION)
|| lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)
|| lastPathSegment.endsWith(MP3_FILE_EXTENSION);
Extractor previousExtractor = null;
if (previousChunk != null) {
id3Decoder = previousChunk.id3Decoder;
id3Data = previousChunk.id3Data;
previousExtractor = previousChunk.extractor;
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl;
needNewExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|| shouldSpliceIn;
previousExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|| shouldSpliceIn ? null : previousChunk.extractor;
} else {
id3Decoder = isPackedAudio ? new Id3Decoder() : null;
id3Data = isPackedAudio ? new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH) : null;
previousExtractor = null;
shouldSpliceIn = false;
needNewExtractor = true;
}
Pair<Extractor, Boolean> extractorData = extractorFactory.createExtractor(previousExtractor,
dataSpec.uri, trackFormat, muxedCaptionFormats, drmInitData, timestampAdjuster);
extractor = extractorData.first;
isPackedAudioExtractor = extractorData.second;
reusingExtractor = extractor == previousExtractor;
initLoadCompleted = reusingExtractor && initDataSpec != null;
if (isPackedAudioExtractor) {
id3Decoder = previousChunk != null ? previousChunk.id3Decoder : new Id3Decoder();
id3Data = previousChunk != null ? previousChunk.id3Data
: new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH);
} else {
id3Decoder = null;
id3Data = null;
}
initDataSource = dataSource;
uid = UID_SOURCE.getAndIncrement();
uid = uidSource.getAndIncrement();
}
/**
@ -172,8 +155,11 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param output The output that will receive the loaded samples.
*/
public void init(HlsSampleStreamWrapper output) {
extractorOutput = output;
this.output = output;
output.init(uid, shouldSpliceIn);
if (!reusingExtractor) {
extractor.init(output);
}
}
@Override
@ -200,10 +186,6 @@ import java.util.concurrent.atomic.AtomicInteger;
@Override
public void load() throws IOException, InterruptedException {
if (extractor == null && !isPackedAudio) {
// See HLS spec, version 20, Section 3.4 for more information on packed audio extraction.
extractor = createExtractor();
}
maybeLoadInitData();
if (!loadCanceled) {
loadMedia();
@ -213,8 +195,8 @@ import java.util.concurrent.atomic.AtomicInteger;
// Internal loading methods.
private void maybeLoadInitData() throws IOException, InterruptedException {
if (previousExtractor == extractor || initLoadCompleted || initDataSpec == null) {
// According to spec, for packed audio, initDataSpec is expected to be null.
if (initLoadCompleted || initDataSpec == null) {
// Note: The HLS spec forbids initialization segments for packed audio.
return;
}
DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded);
@ -258,10 +240,10 @@ import java.util.concurrent.atomic.AtomicInteger;
try {
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (extractor == null) {
// Media segment format is packed audio.
if (isPackedAudioExtractor && !id3TimestampPeeked) {
long id3Timestamp = peekId3PrivTimestamp(input);
extractor = buildPackedAudioExtractor(id3Timestamp != C.TIME_UNSET
id3TimestampPeeked = true;
output.setSampleOffsetUs(id3Timestamp != C.TIME_UNSET
? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
}
if (skipLoadedBytes) {
@ -345,68 +327,4 @@ import java.util.concurrent.atomic.AtomicInteger;
return dataSource;
}
private Extractor createExtractor() {
// Select the extractor that will read the chunk.
Extractor extractor;
boolean usingNewExtractor = true;
if (MimeTypes.TEXT_VTT.equals(hlsUrl.format.sampleMimeType)
|| 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 = previousExtractor;
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)
|| lastPathSegment.startsWith(M4_FILE_EXTENSION_PREFIX, lastPathSegment.length() - 4)) {
extractor = new FragmentedMp4Extractor(0, timestampAdjuster, null, drmInitData);
} 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 = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM;
List<Format> closedCaptionFormats = muxedCaptionFormats;
if (closedCaptionFormats != null) {
// The playlist declares closed caption renditions, we should ignore descriptors.
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS;
} else {
closedCaptionFormats = Collections.emptyList();
}
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(TsExtractor.MODE_HLS, timestampAdjuster,
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, closedCaptionFormats));
}
if (usingNewExtractor) {
extractor.init(extractorOutput);
}
return extractor;
}
private Extractor buildPackedAudioExtractor(long startTimeUs) {
Extractor extractor;
if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
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(0, startTimeUs);
} else {
throw new IllegalArgumentException("Unknown extension for audio file: " + lastPathSegment);
}
extractor.init(extractorOutput);
return extractor;
}
}

View file

@ -44,6 +44,7 @@ import java.util.List;
public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback,
HlsPlaylistTracker.PlaylistEventListener {
private final HlsExtractorFactory extractorFactory;
private final HlsPlaylistTracker playlistTracker;
private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount;
@ -60,8 +61,10 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader;
public HlsMediaPeriod(HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator) {
public HlsMediaPeriod(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
HlsDataSourceFactory dataSourceFactory, int minLoadableRetryCount,
EventDispatcher eventDispatcher, Allocator allocator) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker;
this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount;
@ -344,8 +347,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) {
HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants,
dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats);
HlsChunkSource defaultChunkSource = new HlsChunkSource(extractorFactory, playlistTracker,
variants, dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats);
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs,
muxedAudioFormat, minLoadableRetryCount, eventDispatcher);
}

View file

@ -20,6 +20,7 @@ import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod;
@ -51,6 +52,7 @@ public final class HlsMediaSource implements MediaSource,
*/
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
private final HlsExtractorFactory extractorFactory;
private final Uri manifestUri;
private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount;
@ -60,32 +62,57 @@ public final class HlsMediaSource implements MediaSource,
private HlsPlaylistTracker playlistTracker;
private Listener sourceListener;
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
* segments and keys.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener An {@link AdaptiveMediaSourceEventListener}. May be null if delivery of
* events is not required.
*/
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
eventListener);
}
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
* segments and keys.
* @param minLoadableRetryCount The minimum number of times loads must be retried before
* errors are propagated.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener An {@link AdaptiveMediaSourceEventListener}. May be null if delivery of
* events is not required.
*/
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory), minLoadableRetryCount,
eventHandler, eventListener);
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory),
HlsExtractorFactory.DEFAULT, minLoadableRetryCount, eventHandler, eventListener,
new HlsPlaylistParser());
}
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
* segments and keys.
* @param extractorFactory An {@link HlsExtractorFactory} for {@link Extractor}s for the segments.
* @param minLoadableRetryCount The minimum number of times loads must be retried before
* errors are propagated.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener An {@link AdaptiveMediaSourceEventListener}. May be null if delivery of
* events is not required.
* @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists.
*/
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener,
new HlsPlaylistParser());
}
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
HlsExtractorFactory extractorFactory, int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener,
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory;
this.extractorFactory = extractorFactory;
this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistParser = playlistParser;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
@ -108,8 +135,8 @@ public final class HlsMediaSource implements MediaSource,
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
Assertions.checkArgument(id.periodIndex == 0);
return new HlsMediaPeriod(playlistTracker, dataSourceFactory, minLoadableRetryCount,
eventDispatcher, allocator);
return new HlsMediaPeriod(extractorFactory, playlistTracker, dataSourceFactory,
minLoadableRetryCount, eventDispatcher, allocator);
}
@Override

View file

@ -103,6 +103,7 @@ import java.util.LinkedList;
private boolean[] trackGroupEnabledStates;
private boolean[] trackGroupIsAudioVideoFlags;
private long sampleOffsetUs;
private long lastSeekPositionUs;
private long pendingResetPositionUs;
private boolean pendingResetUpstreamFormats;
@ -369,16 +370,16 @@ import java.util.LinkedList;
// SampleStream implementation.
/* package */ boolean isReady(int trackGroupIndex) {
public boolean isReady(int trackGroupIndex) {
return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample());
}
/* package */ void maybeThrowError() throws IOException {
public void maybeThrowError() throws IOException {
loader.maybeThrowError();
chunkSource.maybeThrowError();
}
/* package */ int readData(int trackGroupIndex, FormatHolder formatHolder,
public int readData(int trackGroupIndex, FormatHolder formatHolder,
DecoderInputBuffer buffer, boolean requireFormat) {
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
@ -402,7 +403,7 @@ import java.util.LinkedList;
lastSeekPositionUs);
}
/* package */ int skipData(int trackGroupIndex, long positionUs) {
public int skipData(int trackGroupIndex, long positionUs) {
SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
return sampleQueue.advanceToEnd();
@ -573,6 +574,7 @@ import java.util.LinkedList;
}
}
SampleQueue trackOutput = new SampleQueue(allocator);
trackOutput.setSampleOffsetUs(sampleOffsetUs);
trackOutput.setUpstreamFormatChangeListener(this);
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id;
@ -599,6 +601,15 @@ import java.util.LinkedList;
handler.post(maybeFinishPrepareRunnable);
}
// Called by the loading thread.
public void setSampleOffsetUs(long sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs;
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
}
}
// Internal methods.
private void maybeFinishPrepare() {

View file

@ -134,8 +134,9 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
* playlist or a master playlist.
* @param dataSourceFactory A factory for {@link DataSource} instances.
* @param eventDispatcher A dispatcher to notify of events.
* @param minRetryCount The minimum number of times the load must be retried before blacklisting a
* playlist.
* @param minRetryCount The minimum number of times loads must be retried before
* {@link #maybeThrowPlaylistRefreshError(HlsUrl)} and
* {@link #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
* @param primaryPlaylistListener A callback for the primary playlist change events.
*/
public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory,