diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 08837b4a60..6fd0e7d682 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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)). diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java index 8a403c3759..8be0371072 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java @@ -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 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 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; + } } diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java index eea64d4dc6..103d89188f 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsExtractorFactory.java @@ -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 createExtractor( + Result createExtractor( Extractor previousExtractor, Uri uri, Format format, diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index d307bbb04d..a7e8fcc292 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -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 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;