Skip to content

Commit

Permalink
Allow extractor injection for HLS
Browse files Browse the repository at this point in the history
Issue:google#2748

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=172726367
  • Loading branch information
AquilesCanta authored and ojw28 committed Oct 19, 2017
1 parent 49aca6e commit 2cfc478
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 165 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,25 +112,18 @@ public Extractor[] createExtractors() {
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) {
this(flags, C.TIME_UNSET);
}

/**
* 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.
Expand All @@ -144,6 +137,8 @@ public Mp3Extractor(@Flags int flags, long forcedFirstSampleTimestampUs) {
basisTimeUs = C.TIME_UNSET;
}

// Extractor implementation.

@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
return synchronize(input, true);
Expand Down Expand Up @@ -195,6 +190,8 @@ public int read(ExtractorInput input, PositionHolder seekPosition)
return readSample(input);
}

// Internal methods.

private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
if (sampleBytesRemaining == 0) {
extractorInput.resetPeekPosition();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
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 {

Expand Down Expand Up @@ -71,6 +70,8 @@ public Ac3Extractor(long firstSampleTimestampUs) {
sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE);
}

// Extractor implementation.

@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@
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 {

Expand Down Expand Up @@ -70,6 +69,8 @@ public AdtsExtractor(long firstSampleTimestampUs) {
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
}

// Extractor implementation.

@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
// Skip any ID3 headers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import java.util.List;

/**
* Default implementation for {@link TsPayloadReader.Factory}.
* Default {@link TsPayloadReader.Factory} implementation.
*/
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public void clear() {

}

private final HlsExtractorFactory extractorFactory;
private final DataSource mediaDataSource;
private final DataSource encryptionDataSource;
private final TimestampAdjusterProvider timestampAdjusterProvider;
Expand All @@ -106,6 +107,8 @@ public void clear() {
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
Expand All @@ -116,9 +119,10 @@ public void clear() {
* @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;
Expand Down Expand Up @@ -321,11 +325,11 @@ public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChu
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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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);

}
Loading

0 comments on commit 2cfc478

Please sign in to comment.