HLS Bugfix: Avoid loading initialization segment when reusing extractor
This bug affects any playlist that uses initialization segments. In practice, almost exclusively fragmented mp4 segmented playlists are affected. The bottom line is that extractors are chosen for reuse after the initialization segment connection is open. However, reused extractors do not need re-parsing the init segment, so loading the initialization is wasteful. PiperOrigin-RevId: 234479467
This commit is contained in:
parent
e97910a694
commit
0ceff589b3
@ -6,6 +6,8 @@
|
||||
* Form an adaptive track group out of audio renditions with matching name.
|
||||
* Support encrypted initialization segments
|
||||
([#5441](https://github.com/google/ExoPlayer/issues/5441)).
|
||||
* Prevent unnecessary reloads of the initialization segment for reused
|
||||
extractors.
|
||||
* DASH:
|
||||
* Fix issue handling large `EventStream` presentation timestamps
|
||||
([#5490](https://github.com/google/ExoPlayer/issues/5490)).
|
||||
|
@ -17,7 +17,6 @@ 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;
|
||||
@ -71,7 +70,7 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Extractor, Boolean> createExtractor(
|
||||
public Result createExtractor(
|
||||
Extractor previousExtractor,
|
||||
Uri uri,
|
||||
Format format,
|
||||
@ -84,21 +83,15 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
|
||||
if (previousExtractor != null) {
|
||||
// A extractor has already been successfully used. Return one of the same type.
|
||||
if (previousExtractor instanceof TsExtractor
|
||||
|| previousExtractor instanceof FragmentedMp4Extractor) {
|
||||
// TS and fMP4 extractors can be reused.
|
||||
if (isReusable(previousExtractor)) {
|
||||
return buildResult(previousExtractor);
|
||||
} else if (previousExtractor instanceof WebvttExtractor) {
|
||||
return buildResult(new WebvttExtractor(format.language, timestampAdjuster));
|
||||
} else if (previousExtractor instanceof AdtsExtractor) {
|
||||
return buildResult(new AdtsExtractor());
|
||||
} else if (previousExtractor instanceof Ac3Extractor) {
|
||||
return buildResult(new Ac3Extractor());
|
||||
} else if (previousExtractor instanceof Mp3Extractor) {
|
||||
return buildResult(new Mp3Extractor());
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected previousExtractor type: " + previousExtractor.getClass().getSimpleName());
|
||||
Result result =
|
||||
buildResultForSameExtractorType(previousExtractor, format, timestampAdjuster);
|
||||
if (result == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected previousExtractor type: " + previousExtractor.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,12 +242,28 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
new DefaultTsPayloadReaderFactory(payloadReaderFactoryFlags, muxedCaptionFormats));
|
||||
}
|
||||
|
||||
private static Pair<Extractor, Boolean> buildResult(Extractor extractor) {
|
||||
return new Pair<>(
|
||||
private static Result buildResultForSameExtractorType(
|
||||
Extractor previousExtractor, Format format, TimestampAdjuster timestampAdjuster) {
|
||||
if (previousExtractor instanceof WebvttExtractor) {
|
||||
return buildResult(new WebvttExtractor(format.language, timestampAdjuster));
|
||||
} else if (previousExtractor instanceof AdtsExtractor) {
|
||||
return buildResult(new AdtsExtractor());
|
||||
} else if (previousExtractor instanceof Ac3Extractor) {
|
||||
return buildResult(new Ac3Extractor());
|
||||
} else if (previousExtractor instanceof Mp3Extractor) {
|
||||
return buildResult(new Mp3Extractor());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Result buildResult(Extractor extractor) {
|
||||
return new Result(
|
||||
extractor,
|
||||
extractor instanceof AdtsExtractor
|
||||
|| extractor instanceof Ac3Extractor
|
||||
|| extractor instanceof Mp3Extractor);
|
||||
|| extractor instanceof Mp3Extractor,
|
||||
isReusable(extractor));
|
||||
}
|
||||
|
||||
private static boolean sniffQuietly(Extractor extractor, ExtractorInput input)
|
||||
@ -270,4 +279,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isReusable(Extractor previousExtractor) {
|
||||
return previousExtractor instanceof TsExtractor
|
||||
|| previousExtractor instanceof FragmentedMp4Extractor;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
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;
|
||||
@ -32,6 +31,33 @@ import java.util.Map;
|
||||
*/
|
||||
public interface HlsExtractorFactory {
|
||||
|
||||
/** Holds an {@link Extractor} and associated parameters. */
|
||||
final class Result {
|
||||
|
||||
/** The created extractor; */
|
||||
public final Extractor extractor;
|
||||
/** Whether the segments for which {@link #extractor} is created are packed audio segments. */
|
||||
public final boolean isPackedAudioExtractor;
|
||||
/**
|
||||
* Whether {@link #extractor} may be reused for following continuous (no immediately preceding
|
||||
* discontinuities) segments of the same variant.
|
||||
*/
|
||||
public final boolean isReusable;
|
||||
|
||||
/**
|
||||
* Creates a result.
|
||||
*
|
||||
* @param extractor See {@link #extractor}.
|
||||
* @param isPackedAudioExtractor See {@link #isPackedAudioExtractor}.
|
||||
* @param isReusable See {@link #isReusable}.
|
||||
*/
|
||||
public Result(Extractor extractor, boolean isPackedAudioExtractor, boolean isReusable) {
|
||||
this.extractor = extractor;
|
||||
this.isPackedAudioExtractor = isPackedAudioExtractor;
|
||||
this.isReusable = isReusable;
|
||||
}
|
||||
}
|
||||
|
||||
HlsExtractorFactory DEFAULT = new DefaultHlsExtractorFactory();
|
||||
|
||||
/**
|
||||
@ -51,13 +77,11 @@ public interface HlsExtractorFactory {
|
||||
* @param sniffingExtractorInput The first extractor input that will be passed to the returned
|
||||
* extractor's {@link Extractor#read(ExtractorInput, PositionHolder)}. Must only be used to
|
||||
* call {@link Extractor#sniff(ExtractorInput)}.
|
||||
* @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.
|
||||
* @return A {@link Result}.
|
||||
* @throws InterruptedException If the thread is interrupted while sniffing.
|
||||
* @throws IOException If an I/O error is encountered while sniffing.
|
||||
*/
|
||||
Pair<Extractor, Boolean> createExtractor(
|
||||
Result createExtractor(
|
||||
Extractor previousExtractor,
|
||||
Uri uri,
|
||||
Format format,
|
||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source.hls;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
@ -133,9 +132,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
scratchId3Data = previousChunk.scratchId3Data;
|
||||
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl || !previousChunk.loadCompleted;
|
||||
previousExtractor =
|
||||
previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber || shouldSpliceIn
|
||||
? null
|
||||
: previousChunk.extractor;
|
||||
previousChunk.isExtractorReusable
|
||||
&& previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
|
||||
&& !shouldSpliceIn
|
||||
? previousChunk.extractor
|
||||
: null;
|
||||
} else {
|
||||
id3Decoder = new Id3Decoder();
|
||||
scratchId3Data = new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH);
|
||||
@ -204,6 +205,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
private final boolean initSegmentEncrypted;
|
||||
|
||||
private Extractor extractor;
|
||||
private boolean isExtractorReusable;
|
||||
private HlsSampleStreamWrapper output;
|
||||
private int initSegmentBytesLoaded;
|
||||
private int nextLoadPosition;
|
||||
@ -287,6 +289,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Override
|
||||
public void load() throws IOException, InterruptedException {
|
||||
if (extractor == null && previousExtractor != null) {
|
||||
extractor = previousExtractor;
|
||||
isExtractorReusable = true;
|
||||
initLoadCompleted = initDataSpec != null;
|
||||
output.init(uid, shouldSpliceIn, /* reusingExtractor= */ true);
|
||||
}
|
||||
maybeLoadInitData();
|
||||
if (!loadCanceled) {
|
||||
if (!hasGapTag) {
|
||||
@ -380,7 +388,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
long id3Timestamp = peekId3PrivTimestamp(extractorInput);
|
||||
extractorInput.resetPeekPosition();
|
||||
|
||||
Pair<Extractor, Boolean> extractorData =
|
||||
HlsExtractorFactory.Result result =
|
||||
extractorFactory.createExtractor(
|
||||
previousExtractor,
|
||||
dataSpec.uri,
|
||||
@ -390,21 +398,16 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
timestampAdjuster,
|
||||
dataSource.getResponseHeaders(),
|
||||
extractorInput);
|
||||
extractor = extractorData.first;
|
||||
boolean reusingExtractor = extractor == previousExtractor;
|
||||
boolean isPackedAudioExtractor = extractorData.second;
|
||||
if (isPackedAudioExtractor) {
|
||||
extractor = result.extractor;
|
||||
isExtractorReusable = result.isReusable;
|
||||
if (result.isPackedAudioExtractor) {
|
||||
output.setSampleOffsetUs(
|
||||
id3Timestamp != C.TIME_UNSET
|
||||
? timestampAdjuster.adjustTsTimestamp(id3Timestamp)
|
||||
: startTimeUs);
|
||||
}
|
||||
initLoadCompleted = reusingExtractor && initDataSpec != null;
|
||||
|
||||
output.init(uid, shouldSpliceIn, reusingExtractor);
|
||||
if (!reusingExtractor) {
|
||||
extractor.init(output);
|
||||
}
|
||||
output.init(uid, shouldSpliceIn, /* reusingExtractor= */ false);
|
||||
extractor.init(output);
|
||||
}
|
||||
|
||||
return extractorInput;
|
||||
|
Loading…
x
Reference in New Issue
Block a user