Commit 2cfc478c by aquilescanta Committed by Oliver Woodman

Allow extractor injection for HLS

Issue:#2748

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=172726367
parent 49aca6e9
...@@ -112,16 +112,11 @@ public final class Mp3Extractor implements Extractor { ...@@ -112,16 +112,11 @@ public final class Mp3Extractor implements Extractor {
private long samplesRead; private long samplesRead;
private int sampleBytesRemaining; private int sampleBytesRemaining;
/**
* Constructs a new {@link Mp3Extractor}.
*/
public Mp3Extractor() { public Mp3Extractor() {
this(0); this(0);
} }
/** /**
* Constructs a new {@link Mp3Extractor}.
*
* @param flags Flags that control the extractor's behavior. * @param flags Flags that control the extractor's behavior.
*/ */
public Mp3Extractor(@Flags int flags) { public Mp3Extractor(@Flags int flags) {
...@@ -129,8 +124,6 @@ public final class Mp3Extractor implements Extractor { ...@@ -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 flags Flags that control the extractor's behavior.
* @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or * @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or
* {@link C#TIME_UNSET} if forcing is not required. * {@link C#TIME_UNSET} if forcing is not required.
...@@ -144,6 +137,8 @@ public final class Mp3Extractor implements Extractor { ...@@ -144,6 +137,8 @@ public final class Mp3Extractor implements Extractor {
basisTimeUs = C.TIME_UNSET; basisTimeUs = C.TIME_UNSET;
} }
// Extractor implementation.
@Override @Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
return synchronize(input, true); return synchronize(input, true);
...@@ -195,6 +190,8 @@ public final class Mp3Extractor implements Extractor { ...@@ -195,6 +190,8 @@ public final class Mp3Extractor implements Extractor {
return readSample(input); return readSample(input);
} }
// Internal methods.
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) { if (sampleBytesRemaining == 0) {
extractorInput.resetPeekPosition(); extractorInput.resetPeekPosition();
......
...@@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
/** /**
* Facilitates the extraction of AC-3 samples from elementary audio files formatted as AC-3 * Extracts samples from (E-)AC-3 bitstreams.
* bitstreams.
*/ */
public final class Ac3Extractor implements Extractor { public final class Ac3Extractor implements Extractor {
...@@ -71,6 +70,8 @@ public final class Ac3Extractor implements Extractor { ...@@ -71,6 +70,8 @@ public final class Ac3Extractor implements Extractor {
sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE); sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE);
} }
// Extractor implementation.
@Override @Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers. // Skip any ID3 headers.
......
...@@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util; ...@@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
/** /**
* Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS * Extracts samples from AAC bit streams with ADTS framing.
* headers.
*/ */
public final class AdtsExtractor implements Extractor { public final class AdtsExtractor implements Extractor {
...@@ -70,6 +69,8 @@ public final class AdtsExtractor implements Extractor { ...@@ -70,6 +69,8 @@ public final class AdtsExtractor implements Extractor {
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
} }
// Extractor implementation.
@Override @Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers. // Skip any ID3 headers.
......
...@@ -28,7 +28,7 @@ import java.util.Collections; ...@@ -28,7 +28,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* Default implementation for {@link TsPayloadReader.Factory}. * Default {@link TsPayloadReader.Factory} implementation.
*/ */
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory {
......
/*
* 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);
}
}
...@@ -80,6 +80,7 @@ import java.util.List; ...@@ -80,6 +80,7 @@ import java.util.List;
} }
private final HlsExtractorFactory extractorFactory;
private final DataSource mediaDataSource; private final DataSource mediaDataSource;
private final DataSource encryptionDataSource; private final DataSource encryptionDataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider; private final TimestampAdjusterProvider timestampAdjusterProvider;
...@@ -106,6 +107,8 @@ import java.util.List; ...@@ -106,6 +107,8 @@ import java.util.List;
private long liveEdgeTimeUs; 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 playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists.
* @param variants The available variants. * @param variants The available variants.
* @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the * @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the
...@@ -116,9 +119,10 @@ import java.util.List; ...@@ -116,9 +119,10 @@ import java.util.List;
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption * @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist. * information is available in the master playlist.
*/ */
public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, public HlsChunkSource(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider, HlsUrl[] variants, HlsDataSourceFactory dataSourceFactory,
List<Format> muxedCaptionFormats) { TimestampAdjusterProvider timestampAdjusterProvider, List<Format> muxedCaptionFormats) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker; this.playlistTracker = playlistTracker;
this.variants = variants; this.variants = variants;
this.timestampAdjusterProvider = timestampAdjusterProvider; this.timestampAdjusterProvider = timestampAdjusterProvider;
...@@ -321,11 +325,11 @@ import java.util.List; ...@@ -321,11 +325,11 @@ import java.util.List;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null); null);
out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl, out.chunk = new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, initDataSpec,
muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(),
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs,
isTimestampMaster, timestampAdjuster, previous, mediaPlaylist.drmInitData, encryptionKey, chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous,
encryptionIv); mediaPlaylist.drmInitData, encryptionKey, encryptionIv);
} }
/** /**
......
/*
* 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);
}
...@@ -15,19 +15,13 @@ ...@@ -15,19 +15,13 @@
*/ */
package com.google.android.exoplayer2.source.hls; 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.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; 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.Metadata;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder; import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
import com.google.android.exoplayer2.metadata.id3.PrivFrame; import com.google.android.exoplayer2.metadata.id3.PrivFrame;
...@@ -35,12 +29,10 @@ import com.google.android.exoplayer2.source.chunk.MediaChunk; ...@@ -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.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; 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.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster; import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
...@@ -49,19 +41,11 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -49,19 +41,11 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
/* package */ final class HlsMediaChunk extends MediaChunk { /* package */ final class HlsMediaChunk extends MediaChunk {
private static final AtomicInteger UID_SOURCE = new AtomicInteger();
private static final String PRIV_TIMESTAMP_FRAME_OWNER = private static final String PRIV_TIMESTAMP_FRAME_OWNER =
"com.apple.streaming.transportStreamTimestamp"; "com.apple.streaming.transportStreamTimestamp";
private static final String AAC_FILE_EXTENSION = ".aac"; private static final AtomicInteger uidSource = new AtomicInteger();
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";
/** /**
* A unique identifier for the chunk. * A unique identifier for the chunk.
...@@ -83,26 +67,24 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -83,26 +67,24 @@ import java.util.concurrent.atomic.AtomicInteger;
private final boolean isEncrypted; private final boolean isEncrypted;
private final boolean isMasterTimestampSource; private final boolean isMasterTimestampSource;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
private final String lastPathSegment;
private final Extractor previousExtractor;
private final boolean shouldSpliceIn; private final boolean shouldSpliceIn;
private final boolean needNewExtractor; private final Extractor extractor;
private final List<Format> muxedCaptionFormats; private final boolean isPackedAudioExtractor;
private final DrmInitData drmInitData; private final boolean reusingExtractor;
private final boolean isPackedAudio;
private final Id3Decoder id3Decoder; private final Id3Decoder id3Decoder;
private final ParsableByteArray id3Data; private final ParsableByteArray id3Data;
private Extractor extractor; private HlsSampleStreamWrapper output;
private int initSegmentBytesLoaded; private int initSegmentBytesLoaded;
private int bytesLoaded; private int bytesLoaded;
private boolean id3TimestampPeeked;
private boolean initLoadCompleted; private boolean initLoadCompleted;
private HlsSampleStreamWrapper extractorOutput;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
private volatile boolean loadCompleted; 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 dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to 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. * @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; ...@@ -124,10 +106,10 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param encryptionIv The AES initialization vector, or null if the segment is not fully * @param encryptionIv The AES initialization vector, or null if the segment is not fully
* encrypted. * encrypted.
*/ */
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec, public HlsMediaChunk(HlsExtractorFactory extractorFactory, DataSource dataSource,
HlsUrl hlsUrl, List<Format> muxedCaptionFormats, int trackSelectionReason, DataSpec dataSpec, DataSpec initDataSpec, HlsUrl hlsUrl, List<Format> muxedCaptionFormats,
Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex, int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
int discontinuitySequenceNumber, boolean isMasterTimestampSource, int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource,
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, DrmInitData drmInitData, TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, DrmInitData drmInitData,
byte[] fullSegmentEncryptionKey, byte[] encryptionIv) { byte[] fullSegmentEncryptionKey, byte[] encryptionIv) {
super(buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv), dataSpec, super(buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv), dataSpec,
...@@ -136,33 +118,34 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -136,33 +118,34 @@ import java.util.concurrent.atomic.AtomicInteger;
this.discontinuitySequenceNumber = discontinuitySequenceNumber; this.discontinuitySequenceNumber = discontinuitySequenceNumber;
this.initDataSpec = initDataSpec; this.initDataSpec = initDataSpec;
this.hlsUrl = hlsUrl; this.hlsUrl = hlsUrl;
this.muxedCaptionFormats = muxedCaptionFormats;
this.isMasterTimestampSource = isMasterTimestampSource; this.isMasterTimestampSource = isMasterTimestampSource;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
// Note: this.dataSource and dataSource may be different. // Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource; this.isEncrypted = this.dataSource instanceof Aes128DataSource;
this.drmInitData = drmInitData; Extractor previousExtractor = null;
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);
if (previousChunk != null) { if (previousChunk != null) {
id3Decoder = previousChunk.id3Decoder;
id3Data = previousChunk.id3Data;
previousExtractor = previousChunk.extractor;
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl; shouldSpliceIn = previousChunk.hlsUrl != hlsUrl;
needNewExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber previousExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|| shouldSpliceIn; || shouldSpliceIn ? null : previousChunk.extractor;
} else { } else {
id3Decoder = isPackedAudio ? new Id3Decoder() : null;
id3Data = isPackedAudio ? new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH) : null;
previousExtractor = null;
shouldSpliceIn = false; 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; initDataSource = dataSource;
uid = UID_SOURCE.getAndIncrement(); uid = uidSource.getAndIncrement();
} }
/** /**
...@@ -172,8 +155,11 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -172,8 +155,11 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param output The output that will receive the loaded samples. * @param output The output that will receive the loaded samples.
*/ */
public void init(HlsSampleStreamWrapper output) { public void init(HlsSampleStreamWrapper output) {
extractorOutput = output; this.output = output;
output.init(uid, shouldSpliceIn); output.init(uid, shouldSpliceIn);
if (!reusingExtractor) {
extractor.init(output);
}
} }
@Override @Override
...@@ -200,10 +186,6 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -200,10 +186,6 @@ import java.util.concurrent.atomic.AtomicInteger;
@Override @Override
public void load() throws IOException, InterruptedException { 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(); maybeLoadInitData();
if (!loadCanceled) { if (!loadCanceled) {
loadMedia(); loadMedia();
...@@ -213,8 +195,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -213,8 +195,8 @@ import java.util.concurrent.atomic.AtomicInteger;
// Internal loading methods. // Internal loading methods.
private void maybeLoadInitData() throws IOException, InterruptedException { private void maybeLoadInitData() throws IOException, InterruptedException {
if (previousExtractor == extractor || initLoadCompleted || initDataSpec == null) { if (initLoadCompleted || initDataSpec == null) {
// According to spec, for packed audio, initDataSpec is expected to be null. // Note: The HLS spec forbids initialization segments for packed audio.
return; return;
} }
DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded); DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded);
...@@ -258,10 +240,10 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -258,10 +240,10 @@ import java.util.concurrent.atomic.AtomicInteger;
try { try {
ExtractorInput input = new DefaultExtractorInput(dataSource, ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
if (extractor == null) { if (isPackedAudioExtractor && !id3TimestampPeeked) {
// Media segment format is packed audio.
long id3Timestamp = peekId3PrivTimestamp(input); long id3Timestamp = peekId3PrivTimestamp(input);
extractor = buildPackedAudioExtractor(id3Timestamp != C.TIME_UNSET id3TimestampPeeked = true;
output.setSampleOffsetUs(id3Timestamp != C.TIME_UNSET
? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs); ? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
} }
if (skipLoadedBytes) { if (skipLoadedBytes) {
...@@ -345,68 +327,4 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -345,68 +327,4 @@ import java.util.concurrent.atomic.AtomicInteger;
return dataSource; 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;
}
} }
...@@ -44,6 +44,7 @@ import java.util.List; ...@@ -44,6 +44,7 @@ import java.util.List;
public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback, public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback,
HlsPlaylistTracker.PlaylistEventListener { HlsPlaylistTracker.PlaylistEventListener {
private final HlsExtractorFactory extractorFactory;
private final HlsPlaylistTracker playlistTracker; private final HlsPlaylistTracker playlistTracker;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
...@@ -60,8 +61,10 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -60,8 +61,10 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader; private CompositeSequenceableLoader sequenceableLoader;
public HlsMediaPeriod(HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory, public HlsMediaPeriod(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator) { HlsDataSourceFactory dataSourceFactory, int minLoadableRetryCount,
EventDispatcher eventDispatcher, Allocator allocator) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker; this.playlistTracker = playlistTracker;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
...@@ -344,8 +347,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper ...@@ -344,8 +347,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants, private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) { Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) {
HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants, HlsChunkSource defaultChunkSource = new HlsChunkSource(extractorFactory, playlistTracker,
dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats); variants, dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats);
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs, return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs,
muxedAudioFormat, minLoadableRetryCount, eventDispatcher); muxedAudioFormat, minLoadableRetryCount, eventDispatcher);
} }
......
...@@ -20,6 +20,7 @@ import android.os.Handler; ...@@ -20,6 +20,7 @@ import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo; 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;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
...@@ -51,6 +52,7 @@ public final class HlsMediaSource implements MediaSource, ...@@ -51,6 +52,7 @@ public final class HlsMediaSource implements MediaSource,
*/ */
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
private final HlsExtractorFactory extractorFactory;
private final Uri manifestUri; private final Uri manifestUri;
private final HlsDataSourceFactory dataSourceFactory; private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
...@@ -60,32 +62,57 @@ public final class HlsMediaSource implements MediaSource, ...@@ -60,32 +62,57 @@ public final class HlsMediaSource implements MediaSource,
private HlsPlaylistTracker playlistTracker; private HlsPlaylistTracker playlistTracker;
private Listener sourceListener; 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, public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler, this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
eventListener); 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, public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler, int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) { AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory), minLoadableRetryCount, this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory),
eventHandler, eventListener); HlsExtractorFactory.DEFAULT, minLoadableRetryCount, eventHandler, eventListener,
}
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener,
new HlsPlaylistParser()); 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, public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler, HlsExtractorFactory extractorFactory, int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener, AdaptiveMediaSourceEventListener eventListener,
ParsingLoadable.Parser<HlsPlaylist> playlistParser) { ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.extractorFactory = extractorFactory;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistParser = playlistParser; this.playlistParser = playlistParser;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
...@@ -108,8 +135,8 @@ public final class HlsMediaSource implements MediaSource, ...@@ -108,8 +135,8 @@ public final class HlsMediaSource implements MediaSource,
@Override @Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) { public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
Assertions.checkArgument(id.periodIndex == 0); Assertions.checkArgument(id.periodIndex == 0);
return new HlsMediaPeriod(playlistTracker, dataSourceFactory, minLoadableRetryCount, return new HlsMediaPeriod(extractorFactory, playlistTracker, dataSourceFactory,
eventDispatcher, allocator); minLoadableRetryCount, eventDispatcher, allocator);
} }
@Override @Override
......
...@@ -103,6 +103,7 @@ import java.util.LinkedList; ...@@ -103,6 +103,7 @@ import java.util.LinkedList;
private boolean[] trackGroupEnabledStates; private boolean[] trackGroupEnabledStates;
private boolean[] trackGroupIsAudioVideoFlags; private boolean[] trackGroupIsAudioVideoFlags;
private long sampleOffsetUs;
private long lastSeekPositionUs; private long lastSeekPositionUs;
private long pendingResetPositionUs; private long pendingResetPositionUs;
private boolean pendingResetUpstreamFormats; private boolean pendingResetUpstreamFormats;
...@@ -369,16 +370,16 @@ import java.util.LinkedList; ...@@ -369,16 +370,16 @@ import java.util.LinkedList;
// SampleStream implementation. // SampleStream implementation.
/* package */ boolean isReady(int trackGroupIndex) { public boolean isReady(int trackGroupIndex) {
return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample()); return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample());
} }
/* package */ void maybeThrowError() throws IOException { public void maybeThrowError() throws IOException {
loader.maybeThrowError(); loader.maybeThrowError();
chunkSource.maybeThrowError(); chunkSource.maybeThrowError();
} }
/* package */ int readData(int trackGroupIndex, FormatHolder formatHolder, public int readData(int trackGroupIndex, FormatHolder formatHolder,
DecoderInputBuffer buffer, boolean requireFormat) { DecoderInputBuffer buffer, boolean requireFormat) {
if (isPendingReset()) { if (isPendingReset()) {
return C.RESULT_NOTHING_READ; return C.RESULT_NOTHING_READ;
...@@ -402,7 +403,7 @@ import java.util.LinkedList; ...@@ -402,7 +403,7 @@ import java.util.LinkedList;
lastSeekPositionUs); lastSeekPositionUs);
} }
/* package */ int skipData(int trackGroupIndex, long positionUs) { public int skipData(int trackGroupIndex, long positionUs) {
SampleQueue sampleQueue = sampleQueues[trackGroupIndex]; SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) { if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
return sampleQueue.advanceToEnd(); return sampleQueue.advanceToEnd();
...@@ -573,6 +574,7 @@ import java.util.LinkedList; ...@@ -573,6 +574,7 @@ import java.util.LinkedList;
} }
} }
SampleQueue trackOutput = new SampleQueue(allocator); SampleQueue trackOutput = new SampleQueue(allocator);
trackOutput.setSampleOffsetUs(sampleOffsetUs);
trackOutput.setUpstreamFormatChangeListener(this); trackOutput.setUpstreamFormatChangeListener(this);
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1); sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id; sampleQueueTrackIds[trackCount] = id;
...@@ -599,6 +601,15 @@ import java.util.LinkedList; ...@@ -599,6 +601,15 @@ import java.util.LinkedList;
handler.post(maybeFinishPrepareRunnable); 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. // Internal methods.
private void maybeFinishPrepare() { private void maybeFinishPrepare() {
......
...@@ -134,8 +134,9 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -134,8 +134,9 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
* playlist or a master playlist. * playlist or a master playlist.
* @param dataSourceFactory A factory for {@link DataSource} instances. * @param dataSourceFactory A factory for {@link DataSource} instances.
* @param eventDispatcher A dispatcher to notify of events. * @param eventDispatcher A dispatcher to notify of events.
* @param minRetryCount The minimum number of times the load must be retried before blacklisting a * @param minRetryCount The minimum number of times loads must be retried before
* playlist. * {@link #maybeThrowPlaylistRefreshError(HlsUrl)} and
* {@link #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
* @param primaryPlaylistListener A callback for the primary playlist change events. * @param primaryPlaylistListener A callback for the primary playlist change events.
*/ */
public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory, public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory,
......
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