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 {
private long samplesRead;
private int sampleBytesRemaining;
/**
* Constructs a new {@link Mp3Extractor}.
*/
public Mp3Extractor() {
this(0);
}
/**
* Constructs a new {@link Mp3Extractor}.
*
* @param flags Flags that control the extractor's behavior.
*/
public Mp3Extractor(@Flags int flags) {
......@@ -129,8 +124,6 @@ public final class Mp3Extractor implements Extractor {
}
/**
* Constructs a new {@link Mp3Extractor}.
*
* @param flags Flags that control the extractor's behavior.
* @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or
* {@link C#TIME_UNSET} if forcing is not required.
......@@ -144,6 +137,8 @@ public final class Mp3Extractor implements Extractor {
basisTimeUs = C.TIME_UNSET;
}
// Extractor implementation.
@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
return synchronize(input, true);
......@@ -195,6 +190,8 @@ public final class Mp3Extractor implements Extractor {
return readSample(input);
}
// Internal methods.
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) {
extractorInput.resetPeekPosition();
......
......@@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* Facilitates the extraction of AC-3 samples from elementary audio files formatted as AC-3
* bitstreams.
* Extracts samples from (E-)AC-3 bitstreams.
*/
public final class Ac3Extractor implements Extractor {
......@@ -71,6 +70,8 @@ public final class Ac3Extractor implements Extractor {
sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE);
}
// Extractor implementation.
@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers.
......
......@@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS
* headers.
* Extracts samples from AAC bit streams with ADTS framing.
*/
public final class AdtsExtractor implements Extractor {
......@@ -70,6 +69,8 @@ public final class AdtsExtractor implements Extractor {
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
}
// Extractor implementation.
@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers.
......
......@@ -28,7 +28,7 @@ import java.util.Collections;
import java.util.List;
/**
* Default implementation for {@link TsPayloadReader.Factory}.
* Default {@link TsPayloadReader.Factory} implementation.
*/
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory {
......
/*
* 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;
}
private final HlsExtractorFactory extractorFactory;
private final DataSource mediaDataSource;
private final DataSource encryptionDataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider;
......@@ -106,6 +107,8 @@ import java.util.List;
private long liveEdgeTimeUs;
/**
* @param extractorFactory An {@link HlsExtractorFactory} from which to obtain the extractors for
* media chunks.
* @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists.
* @param variants The available variants.
* @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the
......@@ -116,9 +119,10 @@ import java.util.List;
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist.
*/
public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants,
HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider,
List<Format> muxedCaptionFormats) {
public HlsChunkSource(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
HlsUrl[] variants, HlsDataSourceFactory dataSourceFactory,
TimestampAdjusterProvider timestampAdjusterProvider, List<Format> muxedCaptionFormats) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker;
this.variants = variants;
this.timestampAdjusterProvider = timestampAdjusterProvider;
......@@ -321,11 +325,11 @@ import java.util.List;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null);
out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl,
muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence,
isTimestampMaster, timestampAdjuster, previous, mediaPlaylist.drmInitData, encryptionKey,
encryptionIv);
out.chunk = new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, initDataSpec,
selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(),
trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs,
chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous,
mediaPlaylist.drmInitData, encryptionKey, encryptionIv);
}
/**
......
/*
* 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);
}
......@@ -44,6 +44,7 @@ import java.util.List;
public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback,
HlsPlaylistTracker.PlaylistEventListener {
private final HlsExtractorFactory extractorFactory;
private final HlsPlaylistTracker playlistTracker;
private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount;
......@@ -60,8 +61,10 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
private CompositeSequenceableLoader sequenceableLoader;
public HlsMediaPeriod(HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator) {
public HlsMediaPeriod(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
HlsDataSourceFactory dataSourceFactory, int minLoadableRetryCount,
EventDispatcher eventDispatcher, Allocator allocator) {
this.extractorFactory = extractorFactory;
this.playlistTracker = playlistTracker;
this.dataSourceFactory = dataSourceFactory;
this.minLoadableRetryCount = minLoadableRetryCount;
......@@ -344,8 +347,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) {
HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants,
dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats);
HlsChunkSource defaultChunkSource = new HlsChunkSource(extractorFactory, playlistTracker,
variants, dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats);
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs,
muxedAudioFormat, minLoadableRetryCount, eventDispatcher);
}
......
......@@ -20,6 +20,7 @@ import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener;
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.source.MediaPeriod;
......@@ -51,6 +52,7 @@ public final class HlsMediaSource implements MediaSource,
*/
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
private final HlsExtractorFactory extractorFactory;
private final Uri manifestUri;
private final HlsDataSourceFactory dataSourceFactory;
private final int minLoadableRetryCount;
......@@ -60,32 +62,57 @@ public final class HlsMediaSource implements MediaSource,
private HlsPlaylistTracker playlistTracker;
private Listener sourceListener;
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
* segments and keys.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener An {@link AdaptiveMediaSourceEventListener}. May be null if delivery of
* events is not required.
*/
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
eventListener);
}
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
* segments and keys.
* @param minLoadableRetryCount The minimum number of times loads must be retried before
* errors are propagated.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener An {@link AdaptiveMediaSourceEventListener}. May be null if delivery of
* events is not required.
*/
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory), minLoadableRetryCount,
eventHandler, eventListener);
}
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener) {
this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener,
new HlsPlaylistParser());
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory),
HlsExtractorFactory.DEFAULT, minLoadableRetryCount, eventHandler, eventListener,
new HlsPlaylistParser());
}
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
* segments and keys.
* @param extractorFactory An {@link HlsExtractorFactory} for {@link Extractor}s for the segments.
* @param minLoadableRetryCount The minimum number of times loads must be retried before
* errors are propagated.
* @param eventHandler A handler for events. May be null if delivery of events is not required.
* @param eventListener An {@link AdaptiveMediaSourceEventListener}. May be null if delivery of
* events is not required.
* @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists.
*/
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
int minLoadableRetryCount, Handler eventHandler,
HlsExtractorFactory extractorFactory, int minLoadableRetryCount, Handler eventHandler,
AdaptiveMediaSourceEventListener eventListener,
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory;
this.extractorFactory = extractorFactory;
this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistParser = playlistParser;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
......@@ -108,8 +135,8 @@ public final class HlsMediaSource implements MediaSource,
@Override
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
Assertions.checkArgument(id.periodIndex == 0);
return new HlsMediaPeriod(playlistTracker, dataSourceFactory, minLoadableRetryCount,
eventDispatcher, allocator);
return new HlsMediaPeriod(extractorFactory, playlistTracker, dataSourceFactory,
minLoadableRetryCount, eventDispatcher, allocator);
}
@Override
......
......@@ -103,6 +103,7 @@ import java.util.LinkedList;
private boolean[] trackGroupEnabledStates;
private boolean[] trackGroupIsAudioVideoFlags;
private long sampleOffsetUs;
private long lastSeekPositionUs;
private long pendingResetPositionUs;
private boolean pendingResetUpstreamFormats;
......@@ -369,16 +370,16 @@ import java.util.LinkedList;
// SampleStream implementation.
/* package */ boolean isReady(int trackGroupIndex) {
public boolean isReady(int trackGroupIndex) {
return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample());
}
/* package */ void maybeThrowError() throws IOException {
public void maybeThrowError() throws IOException {
loader.maybeThrowError();
chunkSource.maybeThrowError();
}
/* package */ int readData(int trackGroupIndex, FormatHolder formatHolder,
public int readData(int trackGroupIndex, FormatHolder formatHolder,
DecoderInputBuffer buffer, boolean requireFormat) {
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
......@@ -402,7 +403,7 @@ import java.util.LinkedList;
lastSeekPositionUs);
}
/* package */ int skipData(int trackGroupIndex, long positionUs) {
public int skipData(int trackGroupIndex, long positionUs) {
SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
return sampleQueue.advanceToEnd();
......@@ -573,6 +574,7 @@ import java.util.LinkedList;
}
}
SampleQueue trackOutput = new SampleQueue(allocator);
trackOutput.setSampleOffsetUs(sampleOffsetUs);
trackOutput.setUpstreamFormatChangeListener(this);
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id;
......@@ -599,6 +601,15 @@ import java.util.LinkedList;
handler.post(maybeFinishPrepareRunnable);
}
// Called by the loading thread.
public void setSampleOffsetUs(long sampleOffsetUs) {
this.sampleOffsetUs = sampleOffsetUs;
for (SampleQueue sampleQueue : sampleQueues) {
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
}
}
// Internal methods.
private void maybeFinishPrepare() {
......
......@@ -134,8 +134,9 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
* playlist or a master playlist.
* @param dataSourceFactory A factory for {@link DataSource} instances.
* @param eventDispatcher A dispatcher to notify of events.
* @param minRetryCount The minimum number of times the load must be retried before blacklisting a
* playlist.
* @param minRetryCount The minimum number of times loads must be retried before
* {@link #maybeThrowPlaylistRefreshError(HlsUrl)} and
* {@link #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
* @param primaryPlaylistListener A callback for the primary playlist change events.
*/
public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory,
......
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