mirror of
https://github.com/androidx/media.git
synced 2025-05-17 12:39:52 +08:00
Create HlsMediaChunkExtractor
To be the abstraction to use for integrating with MediaParser. PiperOrigin-RevId: 316710421
This commit is contained in:
parent
aed8cad8c3
commit
2273b00a53
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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 androidx.annotation.VisibleForTesting;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
|
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.Ac4Extractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
|
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link HlsMediaChunkExtractor} implementation that uses ExoPlayer app-bundled {@link Extractor
|
||||||
|
* Extractors}.
|
||||||
|
*/
|
||||||
|
public final class BundledHlsMediaChunkExtractor implements HlsMediaChunkExtractor {
|
||||||
|
|
||||||
|
private static final PositionHolder DUMMY_POSITION_HOLDER = new PositionHolder();
|
||||||
|
|
||||||
|
@VisibleForTesting /* package */ final Extractor extractor;
|
||||||
|
private final Format masterPlaylistFormat;
|
||||||
|
private final TimestampAdjuster timestampAdjuster;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param extractor The underlying {@link Extractor}.
|
||||||
|
* @param masterPlaylistFormat The {@link Format} obtained from the master playlist.
|
||||||
|
* @param timestampAdjuster A {@link TimestampAdjuster} to adjust sample timestamps.
|
||||||
|
*/
|
||||||
|
public BundledHlsMediaChunkExtractor(
|
||||||
|
Extractor extractor, Format masterPlaylistFormat, TimestampAdjuster timestampAdjuster) {
|
||||||
|
this.extractor = extractor;
|
||||||
|
this.masterPlaylistFormat = masterPlaylistFormat;
|
||||||
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ExtractorOutput extractorOutput) {
|
||||||
|
extractor.init(extractorOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean read(ExtractorInput extractorInput) throws IOException {
|
||||||
|
return extractor.read(extractorInput, DUMMY_POSITION_HOLDER) == Extractor.RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPackedAudioExtractor() {
|
||||||
|
return extractor instanceof AdtsExtractor
|
||||||
|
|| extractor instanceof Ac3Extractor
|
||||||
|
|| extractor instanceof Ac4Extractor
|
||||||
|
|| extractor instanceof Mp3Extractor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HlsMediaChunkExtractor reuseOrRecreate() {
|
||||||
|
if (extractor instanceof TsExtractor || extractor instanceof FragmentedMp4Extractor) {
|
||||||
|
// We can reuse this instance.
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
Extractor newExtractorInstance;
|
||||||
|
if (extractor instanceof WebvttExtractor) {
|
||||||
|
newExtractorInstance = new WebvttExtractor(masterPlaylistFormat.language, timestampAdjuster);
|
||||||
|
} else if (extractor instanceof AdtsExtractor) {
|
||||||
|
newExtractorInstance = new AdtsExtractor();
|
||||||
|
} else if (extractor instanceof Ac3Extractor) {
|
||||||
|
newExtractorInstance = new Ac3Extractor();
|
||||||
|
} else if (extractor instanceof Ac4Extractor) {
|
||||||
|
newExtractorInstance = new Ac4Extractor();
|
||||||
|
} else if (extractor instanceof Mp3Extractor) {
|
||||||
|
newExtractorInstance = new Mp3Extractor();
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Unexpected previousExtractor type: " + extractor.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
return new BundledHlsMediaChunkExtractor(
|
||||||
|
newExtractorInstance, masterPlaylistFormat, timestampAdjuster);
|
||||||
|
}
|
||||||
|
}
|
@ -88,8 +88,8 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result createExtractor(
|
public BundledHlsMediaChunkExtractor createExtractor(
|
||||||
@Nullable Extractor previousExtractor,
|
@Nullable HlsMediaChunkExtractor previousExtractor,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
Format format,
|
Format format,
|
||||||
@Nullable List<Format> muxedCaptionFormats,
|
@Nullable List<Format> muxedCaptionFormats,
|
||||||
@ -97,22 +97,6 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
Map<String, List<String>> responseHeaders,
|
Map<String, List<String>> responseHeaders,
|
||||||
ExtractorInput extractorInput)
|
ExtractorInput extractorInput)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
if (previousExtractor != null) {
|
|
||||||
// An extractor has already been successfully used. Return one of the same type.
|
|
||||||
if (isReusable(previousExtractor)) {
|
|
||||||
return buildResult(previousExtractor);
|
|
||||||
} else {
|
|
||||||
@Nullable
|
|
||||||
Result result =
|
|
||||||
buildResultForSameExtractorType(previousExtractor, format, timestampAdjuster);
|
|
||||||
if (result == null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Unexpected previousExtractor type: " + previousExtractor.getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FileTypes.Type
|
@FileTypes.Type
|
||||||
int formatInferredFileType = FileTypes.inferFileTypeFromMimeType(format.sampleMimeType);
|
int formatInferredFileType = FileTypes.inferFileTypeFromMimeType(format.sampleMimeType);
|
||||||
@FileTypes.Type
|
@FileTypes.Type
|
||||||
@ -139,14 +123,15 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
checkNotNull(
|
checkNotNull(
|
||||||
createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster));
|
createExtractorByFileType(fileType, format, muxedCaptionFormats, timestampAdjuster));
|
||||||
if (sniffQuietly(extractor, extractorInput)) {
|
if (sniffQuietly(extractor, extractorInput)) {
|
||||||
return buildResult(extractor);
|
return new BundledHlsMediaChunkExtractor(extractor, format, timestampAdjuster);
|
||||||
}
|
}
|
||||||
if (fileType == FileTypes.TS) {
|
if (fileType == FileTypes.TS) {
|
||||||
fallBackExtractor = extractor;
|
fallBackExtractor = extractor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildResult(checkNotNull(fallBackExtractor));
|
return new BundledHlsMediaChunkExtractor(
|
||||||
|
checkNotNull(fallBackExtractor), format, timestampAdjuster);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addFileTypeIfNotPresent(
|
private static void addFileTypeIfNotPresent(
|
||||||
@ -257,34 +242,6 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
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 Ac4Extractor) {
|
|
||||||
return buildResult(new Ac4Extractor());
|
|
||||||
} 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 Ac4Extractor
|
|
||||||
|| extractor instanceof Mp3Extractor,
|
|
||||||
isReusable(extractor));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean sniffQuietly(Extractor extractor, ExtractorInput input)
|
private static boolean sniffQuietly(Extractor extractor, ExtractorInput input)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
@ -297,9 +254,4 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isReusable(Extractor previousExtractor) {
|
|
||||||
return previousExtractor instanceof TsExtractor
|
|
||||||
|| previousExtractor instanceof FragmentedMp4Extractor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -31,33 +31,6 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public interface HlsExtractorFactory {
|
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();
|
HlsExtractorFactory DEFAULT = new DefaultHlsExtractorFactory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,11 +49,11 @@ public interface HlsExtractorFactory {
|
|||||||
* @param sniffingExtractorInput The first extractor input that will be passed to the returned
|
* @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
|
* extractor's {@link Extractor#read(ExtractorInput, PositionHolder)}. Must only be used to
|
||||||
* call {@link Extractor#sniff(ExtractorInput)}.
|
* call {@link Extractor#sniff(ExtractorInput)}.
|
||||||
* @return A {@link Result}.
|
* @return An {@link HlsMediaChunkExtractor}.
|
||||||
* @throws IOException If an I/O error is encountered while sniffing.
|
* @throws IOException If an I/O error is encountered while sniffing.
|
||||||
*/
|
*/
|
||||||
Result createExtractor(
|
HlsMediaChunkExtractor createExtractor(
|
||||||
@Nullable Extractor previousExtractor,
|
@Nullable HlsMediaChunkExtractor previousExtractor,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
Format format,
|
Format format,
|
||||||
@Nullable List<Format> muxedCaptionFormats,
|
@Nullable List<Format> muxedCaptionFormats,
|
||||||
|
@ -21,9 +21,7 @@ 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.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
|
||||||
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;
|
||||||
@ -56,8 +54,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
/**
|
/**
|
||||||
* Creates a new instance.
|
* Creates a new instance.
|
||||||
*
|
*
|
||||||
* @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk extractor
|
* @param extractorFactory A {@link HlsExtractorFactory} from which the {@link
|
||||||
* is obtained.
|
* HlsMediaChunkExtractor} 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 format The chunk format.
|
* @param format The chunk format.
|
||||||
* @param startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds.
|
* @param startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds.
|
||||||
@ -130,7 +128,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
int discontinuitySequenceNumber =
|
int discontinuitySequenceNumber =
|
||||||
mediaPlaylist.discontinuitySequence + mediaSegment.relativeDiscontinuitySequence;
|
mediaPlaylist.discontinuitySequence + mediaSegment.relativeDiscontinuitySequence;
|
||||||
|
|
||||||
@Nullable Extractor previousExtractor = null;
|
@Nullable HlsMediaChunkExtractor previousExtractor = null;
|
||||||
Id3Decoder id3Decoder;
|
Id3Decoder id3Decoder;
|
||||||
ParsableByteArray scratchId3Data;
|
ParsableByteArray scratchId3Data;
|
||||||
boolean shouldSpliceIn;
|
boolean shouldSpliceIn;
|
||||||
@ -147,8 +145,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
sampleQueueDiscardFromIndices = previousChunk.sampleQueueDiscardFromIndices;
|
sampleQueueDiscardFromIndices = previousChunk.sampleQueueDiscardFromIndices;
|
||||||
}
|
}
|
||||||
previousExtractor =
|
previousExtractor =
|
||||||
previousChunk.isExtractorReusable
|
previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
|
||||||
&& previousChunk.discontinuitySequenceNumber == discontinuitySequenceNumber
|
|
||||||
&& !shouldSpliceIn
|
&& !shouldSpliceIn
|
||||||
? previousChunk.extractor
|
? previousChunk.extractor
|
||||||
: null;
|
: null;
|
||||||
@ -188,7 +185,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
public static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
public static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
||||||
"com.apple.streaming.transportStreamTimestamp";
|
"com.apple.streaming.transportStreamTimestamp";
|
||||||
private static final PositionHolder DUMMY_POSITION_HOLDER = new PositionHolder();
|
|
||||||
|
|
||||||
private static final AtomicInteger uidSource = new AtomicInteger();
|
private static final AtomicInteger uidSource = new AtomicInteger();
|
||||||
|
|
||||||
@ -207,7 +203,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
|
|
||||||
@Nullable private final DataSource initDataSource;
|
@Nullable private final DataSource initDataSource;
|
||||||
@Nullable private final DataSpec initDataSpec;
|
@Nullable private final DataSpec initDataSpec;
|
||||||
@Nullable private final Extractor previousExtractor;
|
@Nullable private final HlsMediaChunkExtractor previousExtractor;
|
||||||
|
|
||||||
private final boolean isMasterTimestampSource;
|
private final boolean isMasterTimestampSource;
|
||||||
private final boolean hasGapTag;
|
private final boolean hasGapTag;
|
||||||
@ -221,8 +217,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
private final boolean initSegmentEncrypted;
|
private final boolean initSegmentEncrypted;
|
||||||
private final boolean shouldSpliceIn;
|
private final boolean shouldSpliceIn;
|
||||||
|
|
||||||
private @MonotonicNonNull Extractor extractor;
|
private @MonotonicNonNull HlsMediaChunkExtractor extractor;
|
||||||
private boolean isExtractorReusable;
|
|
||||||
private @MonotonicNonNull HlsSampleStreamWrapper output;
|
private @MonotonicNonNull HlsSampleStreamWrapper output;
|
||||||
// nextLoadPosition refers to the init segment if initDataLoadRequired is true.
|
// nextLoadPosition refers to the init segment if initDataLoadRequired is true.
|
||||||
// Otherwise, nextLoadPosition refers to the media segment.
|
// Otherwise, nextLoadPosition refers to the media segment.
|
||||||
@ -253,7 +248,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
boolean isMasterTimestampSource,
|
boolean isMasterTimestampSource,
|
||||||
TimestampAdjuster timestampAdjuster,
|
TimestampAdjuster timestampAdjuster,
|
||||||
@Nullable DrmInitData drmInitData,
|
@Nullable DrmInitData drmInitData,
|
||||||
@Nullable Extractor previousExtractor,
|
@Nullable HlsMediaChunkExtractor previousExtractor,
|
||||||
Id3Decoder id3Decoder,
|
Id3Decoder id3Decoder,
|
||||||
ParsableByteArray scratchId3Data,
|
ParsableByteArray scratchId3Data,
|
||||||
boolean shouldSpliceIn,
|
boolean shouldSpliceIn,
|
||||||
@ -340,9 +335,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
// output == null means init() hasn't been called.
|
// output == null means init() hasn't been called.
|
||||||
Assertions.checkNotNull(output);
|
Assertions.checkNotNull(output);
|
||||||
if (extractor == null && previousExtractor != null) {
|
if (extractor == null && previousExtractor != null) {
|
||||||
extractor = previousExtractor;
|
extractor = previousExtractor.reuseOrRecreate();
|
||||||
isExtractorReusable = true;
|
initDataLoadRequired = extractor != previousExtractor;
|
||||||
initDataLoadRequired = false;
|
|
||||||
}
|
}
|
||||||
maybeLoadInitData();
|
maybeLoadInitData();
|
||||||
if (!loadCanceled) {
|
if (!loadCanceled) {
|
||||||
@ -410,10 +404,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
input.skipFully(nextLoadPosition);
|
input.skipFully(nextLoadPosition);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
int result = Extractor.RESULT_CONTINUE;
|
while (!loadCanceled && extractor.read(input)) {}
|
||||||
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
|
|
||||||
result = extractor.read(input, DUMMY_POSITION_HOLDER);
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
nextLoadPosition = (int) (input.getPosition() - dataSpec.position);
|
nextLoadPosition = (int) (input.getPosition() - dataSpec.position);
|
||||||
}
|
}
|
||||||
@ -434,7 +425,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
long id3Timestamp = peekId3PrivTimestamp(extractorInput);
|
long id3Timestamp = peekId3PrivTimestamp(extractorInput);
|
||||||
extractorInput.resetPeekPosition();
|
extractorInput.resetPeekPosition();
|
||||||
|
|
||||||
HlsExtractorFactory.Result result =
|
extractor =
|
||||||
extractorFactory.createExtractor(
|
extractorFactory.createExtractor(
|
||||||
previousExtractor,
|
previousExtractor,
|
||||||
dataSpec.uri,
|
dataSpec.uri,
|
||||||
@ -443,9 +434,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||||||
timestampAdjuster,
|
timestampAdjuster,
|
||||||
dataSource.getResponseHeaders(),
|
dataSource.getResponseHeaders(),
|
||||||
extractorInput);
|
extractorInput);
|
||||||
extractor = result.extractor;
|
if (extractor.isPackedAudioExtractor()) {
|
||||||
isExtractorReusable = result.isReusable;
|
|
||||||
if (result.isPackedAudioExtractor) {
|
|
||||||
output.setSampleOffsetUs(
|
output.setSampleOffsetUs(
|
||||||
id3Timestamp != C.TIME_UNSET
|
id3Timestamp != C.TIME_UNSET
|
||||||
? timestampAdjuster.adjustTsTimestamp(id3Timestamp)
|
? timestampAdjuster.adjustTsTimestamp(id3Timestamp)
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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 com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/** Extracts samples and track {@link Format Formats} from {@link HlsMediaChunk HlsMediaChunks}. */
|
||||||
|
public interface HlsMediaChunkExtractor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the extractor with an {@link ExtractorOutput}. Called at most once.
|
||||||
|
*
|
||||||
|
* @param extractorOutput An {@link ExtractorOutput} to receive extracted data.
|
||||||
|
*/
|
||||||
|
void init(ExtractorOutput extractorOutput);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts data read from a provided {@link ExtractorInput}. Must not be called before {@link
|
||||||
|
* #init(ExtractorOutput)}.
|
||||||
|
*
|
||||||
|
* <p>A single call to this method will block until some progress has been made, but will not
|
||||||
|
* block for longer than this. Hence each call will consume only a small amount of input data.
|
||||||
|
*
|
||||||
|
* <p>When this method throws an {@link IOException}, extraction may continue by providing an
|
||||||
|
* {@link ExtractorInput} with an unchanged {@link ExtractorInput#getPosition() read position} to
|
||||||
|
* a subsequent call to this method.
|
||||||
|
*
|
||||||
|
* @param extractorInput The input to read from.
|
||||||
|
* @return Whether there is any data left to extract. Returns false if the end of input has been
|
||||||
|
* reached.
|
||||||
|
* @throws IOException If an error occurred reading from or parsing the input.
|
||||||
|
*/
|
||||||
|
boolean read(ExtractorInput extractorInput) throws IOException;
|
||||||
|
|
||||||
|
/** Returns whether this is a packed audio extractor, as defined in RFC 8216, Section 3.4. */
|
||||||
|
boolean isPackedAudioExtractor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this instance can be used for extracting multiple continuous segments, returns itself.
|
||||||
|
* Otherwise, returns a new instance for extracting the same type of media.
|
||||||
|
*/
|
||||||
|
HlsMediaChunkExtractor reuseOrRecreate();
|
||||||
|
}
|
@ -22,10 +22,8 @@ import androidx.test.core.app.ApplicationProvider;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
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.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.Ac3Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
|
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
|
||||||
@ -44,7 +42,6 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class DefaultHlsExtractorFactoryTest {
|
public class DefaultHlsExtractorFactoryTest {
|
||||||
|
|
||||||
private Extractor fMp4Extractor;
|
|
||||||
private Uri tsUri;
|
private Uri tsUri;
|
||||||
private Format webVttFormat;
|
private Format webVttFormat;
|
||||||
private TimestampAdjuster timestampAdjuster;
|
private TimestampAdjuster timestampAdjuster;
|
||||||
@ -52,7 +49,6 @@ public class DefaultHlsExtractorFactoryTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
fMp4Extractor = new FragmentedMp4Extractor();
|
|
||||||
tsUri = Uri.parse("http://path/filename.ts");
|
tsUri = Uri.parse("http://path/filename.ts");
|
||||||
webVttFormat = new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build();
|
webVttFormat = new Format.Builder().setSampleMimeType(MimeTypes.TEXT_VTT).build();
|
||||||
timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
|
timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
|
||||||
@ -60,24 +56,6 @@ public class DefaultHlsExtractorFactoryTest {
|
|||||||
ac3ResponseHeaders.put("Content-Type", Collections.singletonList(MimeTypes.AUDIO_AC3));
|
ac3ResponseHeaders.put("Content-Type", Collections.singletonList(MimeTypes.AUDIO_AC3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void createExtractor_withPreviousExtractor_returnsSameExtractorType() throws Exception {
|
|
||||||
ExtractorInput extractorInput = new FakeExtractorInput.Builder().build();
|
|
||||||
|
|
||||||
HlsExtractorFactory.Result result =
|
|
||||||
new DefaultHlsExtractorFactory()
|
|
||||||
.createExtractor(
|
|
||||||
/* previousExtractor= */ fMp4Extractor,
|
|
||||||
tsUri,
|
|
||||||
webVttFormat,
|
|
||||||
/* muxedCaptionFormats= */ null,
|
|
||||||
timestampAdjuster,
|
|
||||||
ac3ResponseHeaders,
|
|
||||||
extractorInput);
|
|
||||||
|
|
||||||
assertThat(result.extractor.getClass()).isEqualTo(FragmentedMp4Extractor.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createExtractor_withFileTypeInFormat_returnsExtractorMatchingFormat()
|
public void createExtractor_withFileTypeInFormat_returnsExtractorMatchingFormat()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
@ -88,7 +66,7 @@ public class DefaultHlsExtractorFactoryTest {
|
|||||||
ApplicationProvider.getApplicationContext(), "webvtt/typical"))
|
ApplicationProvider.getApplicationContext(), "webvtt/typical"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HlsExtractorFactory.Result result =
|
BundledHlsMediaChunkExtractor result =
|
||||||
new DefaultHlsExtractorFactory()
|
new DefaultHlsExtractorFactory()
|
||||||
.createExtractor(
|
.createExtractor(
|
||||||
/* previousExtractor= */ null,
|
/* previousExtractor= */ null,
|
||||||
@ -112,7 +90,7 @@ public class DefaultHlsExtractorFactoryTest {
|
|||||||
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "ts/sample.ac3"))
|
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "ts/sample.ac3"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HlsExtractorFactory.Result result =
|
BundledHlsMediaChunkExtractor result =
|
||||||
new DefaultHlsExtractorFactory()
|
new DefaultHlsExtractorFactory()
|
||||||
.createExtractor(
|
.createExtractor(
|
||||||
/* previousExtractor= */ null,
|
/* previousExtractor= */ null,
|
||||||
@ -135,7 +113,7 @@ public class DefaultHlsExtractorFactoryTest {
|
|||||||
ApplicationProvider.getApplicationContext(), "ts/sample_ac3.ts"))
|
ApplicationProvider.getApplicationContext(), "ts/sample_ac3.ts"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HlsExtractorFactory.Result result =
|
BundledHlsMediaChunkExtractor result =
|
||||||
new DefaultHlsExtractorFactory()
|
new DefaultHlsExtractorFactory()
|
||||||
.createExtractor(
|
.createExtractor(
|
||||||
/* previousExtractor= */ null,
|
/* previousExtractor= */ null,
|
||||||
@ -159,7 +137,7 @@ public class DefaultHlsExtractorFactoryTest {
|
|||||||
ApplicationProvider.getApplicationContext(), "mp3/bear-id3.mp3"))
|
ApplicationProvider.getApplicationContext(), "mp3/bear-id3.mp3"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HlsExtractorFactory.Result result =
|
BundledHlsMediaChunkExtractor result =
|
||||||
new DefaultHlsExtractorFactory()
|
new DefaultHlsExtractorFactory()
|
||||||
.createExtractor(
|
.createExtractor(
|
||||||
/* previousExtractor= */ null,
|
/* previousExtractor= */ null,
|
||||||
@ -177,7 +155,7 @@ public class DefaultHlsExtractorFactoryTest {
|
|||||||
public void createExtractor_withNoMatchingExtractor_fallsBackOnTsExtractor() throws Exception {
|
public void createExtractor_withNoMatchingExtractor_fallsBackOnTsExtractor() throws Exception {
|
||||||
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
|
ExtractorInput emptyExtractorInput = new FakeExtractorInput.Builder().build();
|
||||||
|
|
||||||
HlsExtractorFactory.Result result =
|
BundledHlsMediaChunkExtractor result =
|
||||||
new DefaultHlsExtractorFactory()
|
new DefaultHlsExtractorFactory()
|
||||||
.createExtractor(
|
.createExtractor(
|
||||||
/* previousExtractor= */ null,
|
/* previousExtractor= */ null,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user