mirror of
https://github.com/samsonjs/media.git
synced 2026-03-28 09:55:48 +00:00
Allow extractor injection for HLS
Issue:#2748 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=172726367
This commit is contained in:
parent
49aca6e9ce
commit
2cfc478c3e
12 changed files with 287 additions and 165 deletions
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
@ -15,19 +15,13 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.source.hls;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
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.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.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
|
||||
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
|
||||
|
|
@ -35,12 +29,10 @@ import com.google.android.exoplayer2.source.chunk.MediaChunk;
|
|||
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
|
@ -49,19 +41,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
*/
|
||||
/* package */ final class HlsMediaChunk extends MediaChunk {
|
||||
|
||||
private static final AtomicInteger UID_SOURCE = new AtomicInteger();
|
||||
|
||||
private static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
||||
"com.apple.streaming.transportStreamTimestamp";
|
||||
|
||||
private static final String AAC_FILE_EXTENSION = ".aac";
|
||||
private static final String AC3_FILE_EXTENSION = ".ac3";
|
||||
private static final String EC3_FILE_EXTENSION = ".ec3";
|
||||
private static final String MP3_FILE_EXTENSION = ".mp3";
|
||||
private static final String MP4_FILE_EXTENSION = ".mp4";
|
||||
private static final String M4_FILE_EXTENSION_PREFIX = ".m4";
|
||||
private static final String VTT_FILE_EXTENSION = ".vtt";
|
||||
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
|
||||
private static final AtomicInteger uidSource = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* A unique identifier for the chunk.
|
||||
|
|
@ -83,26 +67,24 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
private final boolean isEncrypted;
|
||||
private final boolean isMasterTimestampSource;
|
||||
private final TimestampAdjuster timestampAdjuster;
|
||||
private final String lastPathSegment;
|
||||
private final Extractor previousExtractor;
|
||||
private final boolean shouldSpliceIn;
|
||||
private final boolean needNewExtractor;
|
||||
private final List<Format> muxedCaptionFormats;
|
||||
private final DrmInitData drmInitData;
|
||||
|
||||
private final boolean isPackedAudio;
|
||||
private final Extractor extractor;
|
||||
private final boolean isPackedAudioExtractor;
|
||||
private final boolean reusingExtractor;
|
||||
private final Id3Decoder id3Decoder;
|
||||
private final ParsableByteArray id3Data;
|
||||
|
||||
private Extractor extractor;
|
||||
private HlsSampleStreamWrapper output;
|
||||
private int initSegmentBytesLoaded;
|
||||
private int bytesLoaded;
|
||||
private boolean id3TimestampPeeked;
|
||||
private boolean initLoadCompleted;
|
||||
private HlsSampleStreamWrapper extractorOutput;
|
||||
private volatile boolean loadCanceled;
|
||||
private volatile boolean loadCompleted;
|
||||
|
||||
/**
|
||||
* @param extractorFactory A {@link HlsExtractorFactory} from which the HLS media chunk
|
||||
* extractor is obtained.
|
||||
* @param dataSource The source from which the data should be loaded.
|
||||
* @param dataSpec Defines the data to be loaded.
|
||||
* @param initDataSpec Defines the initialization data to be fed to new extractors. May be null.
|
||||
|
|
@ -124,10 +106,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* @param encryptionIv The AES initialization vector, or null if the segment is not fully
|
||||
* encrypted.
|
||||
*/
|
||||
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
|
||||
HlsUrl hlsUrl, List<Format> muxedCaptionFormats, int trackSelectionReason,
|
||||
Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex,
|
||||
int discontinuitySequenceNumber, boolean isMasterTimestampSource,
|
||||
public HlsMediaChunk(HlsExtractorFactory extractorFactory, DataSource dataSource,
|
||||
DataSpec dataSpec, DataSpec initDataSpec, HlsUrl hlsUrl, List<Format> muxedCaptionFormats,
|
||||
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
|
||||
int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource,
|
||||
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, DrmInitData drmInitData,
|
||||
byte[] fullSegmentEncryptionKey, byte[] encryptionIv) {
|
||||
super(buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv), dataSpec,
|
||||
|
|
@ -136,33 +118,34 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
|
||||
this.initDataSpec = initDataSpec;
|
||||
this.hlsUrl = hlsUrl;
|
||||
this.muxedCaptionFormats = muxedCaptionFormats;
|
||||
this.isMasterTimestampSource = isMasterTimestampSource;
|
||||
this.timestampAdjuster = timestampAdjuster;
|
||||
// Note: this.dataSource and dataSource may be different.
|
||||
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
|
||||
this.drmInitData = drmInitData;
|
||||
lastPathSegment = dataSpec.uri.getLastPathSegment();
|
||||
isPackedAudio = lastPathSegment.endsWith(AAC_FILE_EXTENSION)
|
||||
|| lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|
||||
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)
|
||||
|| lastPathSegment.endsWith(MP3_FILE_EXTENSION);
|
||||
Extractor previousExtractor = null;
|
||||
if (previousChunk != null) {
|
||||
id3Decoder = previousChunk.id3Decoder;
|
||||
id3Data = previousChunk.id3Data;
|
||||
previousExtractor = previousChunk.extractor;
|
||||
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl;
|
||||
needNewExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|
||||
|| shouldSpliceIn;
|
||||
previousExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|
||||
|| shouldSpliceIn ? null : previousChunk.extractor;
|
||||
} else {
|
||||
id3Decoder = isPackedAudio ? new Id3Decoder() : null;
|
||||
id3Data = isPackedAudio ? new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH) : null;
|
||||
previousExtractor = null;
|
||||
shouldSpliceIn = false;
|
||||
needNewExtractor = true;
|
||||
}
|
||||
Pair<Extractor, Boolean> extractorData = extractorFactory.createExtractor(previousExtractor,
|
||||
dataSpec.uri, trackFormat, muxedCaptionFormats, drmInitData, timestampAdjuster);
|
||||
extractor = extractorData.first;
|
||||
isPackedAudioExtractor = extractorData.second;
|
||||
reusingExtractor = extractor == previousExtractor;
|
||||
initLoadCompleted = reusingExtractor && initDataSpec != null;
|
||||
if (isPackedAudioExtractor) {
|
||||
id3Decoder = previousChunk != null ? previousChunk.id3Decoder : new Id3Decoder();
|
||||
id3Data = previousChunk != null ? previousChunk.id3Data
|
||||
: new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH);
|
||||
} else {
|
||||
id3Decoder = null;
|
||||
id3Data = null;
|
||||
}
|
||||
initDataSource = dataSource;
|
||||
uid = UID_SOURCE.getAndIncrement();
|
||||
uid = uidSource.getAndIncrement();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -172,8 +155,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* @param output The output that will receive the loaded samples.
|
||||
*/
|
||||
public void init(HlsSampleStreamWrapper output) {
|
||||
extractorOutput = output;
|
||||
this.output = output;
|
||||
output.init(uid, shouldSpliceIn);
|
||||
if (!reusingExtractor) {
|
||||
extractor.init(output);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -200,10 +186,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
|
||||
@Override
|
||||
public void load() throws IOException, InterruptedException {
|
||||
if (extractor == null && !isPackedAudio) {
|
||||
// See HLS spec, version 20, Section 3.4 for more information on packed audio extraction.
|
||||
extractor = createExtractor();
|
||||
}
|
||||
maybeLoadInitData();
|
||||
if (!loadCanceled) {
|
||||
loadMedia();
|
||||
|
|
@ -213,8 +195,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
// Internal loading methods.
|
||||
|
||||
private void maybeLoadInitData() throws IOException, InterruptedException {
|
||||
if (previousExtractor == extractor || initLoadCompleted || initDataSpec == null) {
|
||||
// According to spec, for packed audio, initDataSpec is expected to be null.
|
||||
if (initLoadCompleted || initDataSpec == null) {
|
||||
// Note: The HLS spec forbids initialization segments for packed audio.
|
||||
return;
|
||||
}
|
||||
DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded);
|
||||
|
|
@ -258,10 +240,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
try {
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource,
|
||||
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
|
||||
if (extractor == null) {
|
||||
// Media segment format is packed audio.
|
||||
if (isPackedAudioExtractor && !id3TimestampPeeked) {
|
||||
long id3Timestamp = peekId3PrivTimestamp(input);
|
||||
extractor = buildPackedAudioExtractor(id3Timestamp != C.TIME_UNSET
|
||||
id3TimestampPeeked = true;
|
||||
output.setSampleOffsetUs(id3Timestamp != C.TIME_UNSET
|
||||
? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
|
||||
}
|
||||
if (skipLoadedBytes) {
|
||||
|
|
@ -345,68 +327,4 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
return dataSource;
|
||||
}
|
||||
|
||||
private Extractor createExtractor() {
|
||||
// Select the extractor that will read the chunk.
|
||||
Extractor extractor;
|
||||
boolean usingNewExtractor = true;
|
||||
if (MimeTypes.TEXT_VTT.equals(hlsUrl.format.sampleMimeType)
|
||||
|| lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|
||||
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
|
||||
extractor = new WebvttExtractor(trackFormat.language, timestampAdjuster);
|
||||
} else if (!needNewExtractor) {
|
||||
// Only reuse TS and fMP4 extractors.
|
||||
usingNewExtractor = false;
|
||||
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 {
|
||||
// MPEG-2 TS segments, but we need a new extractor.
|
||||
// This flag ensures the change of pid between streams does not affect the sample queues.
|
||||
@DefaultTsPayloadReaderFactory.Flags
|
||||
int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM;
|
||||
List<Format> closedCaptionFormats = muxedCaptionFormats;
|
||||
if (closedCaptionFormats != null) {
|
||||
// The playlist declares closed caption renditions, we should ignore descriptors.
|
||||
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS;
|
||||
} else {
|
||||
closedCaptionFormats = Collections.emptyList();
|
||||
}
|
||||
String codecs = trackFormat.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, closedCaptionFormats));
|
||||
}
|
||||
if (usingNewExtractor) {
|
||||
extractor.init(extractorOutput);
|
||||
}
|
||||
return extractor;
|
||||
}
|
||||
|
||||
private Extractor buildPackedAudioExtractor(long startTimeUs) {
|
||||
Extractor extractor;
|
||||
if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) {
|
||||
extractor = new AdtsExtractor(startTimeUs);
|
||||
} else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION)
|
||||
|| lastPathSegment.endsWith(EC3_FILE_EXTENSION)) {
|
||||
extractor = new Ac3Extractor(startTimeUs);
|
||||
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
|
||||
extractor = new Mp3Extractor(0, startTimeUs);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown extension for audio file: " + lastPathSegment);
|
||||
}
|
||||
extractor.init(extractorOutput);
|
||||
return extractor;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
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,
|
||||
AdaptiveMediaSourceEventListener eventListener) {
|
||||
this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener,
|
||||
new HlsPlaylistParser());
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue