mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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 long samplesRead;
|
||||||
private int sampleBytesRemaining;
|
private int sampleBytesRemaining;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@link Mp3Extractor}.
|
|
||||||
*/
|
|
||||||
public Mp3Extractor() {
|
public Mp3Extractor() {
|
||||||
this(0);
|
this(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link Mp3Extractor}.
|
|
||||||
*
|
|
||||||
* @param flags Flags that control the extractor's behavior.
|
* @param flags Flags that control the extractor's behavior.
|
||||||
*/
|
*/
|
||||||
public Mp3Extractor(@Flags int flags) {
|
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 flags Flags that control the extractor's behavior.
|
||||||
* @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or
|
* @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or
|
||||||
* {@link C#TIME_UNSET} if forcing is not required.
|
* {@link C#TIME_UNSET} if forcing is not required.
|
||||||
|
|
@ -144,6 +137,8 @@ public final class Mp3Extractor implements Extractor {
|
||||||
basisTimeUs = C.TIME_UNSET;
|
basisTimeUs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extractor implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
return synchronize(input, true);
|
return synchronize(input, true);
|
||||||
|
|
@ -195,6 +190,8 @@ public final class Mp3Extractor implements Extractor {
|
||||||
return readSample(input);
|
return readSample(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
|
private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException {
|
||||||
if (sampleBytesRemaining == 0) {
|
if (sampleBytesRemaining == 0) {
|
||||||
extractorInput.resetPeekPosition();
|
extractorInput.resetPeekPosition();
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction of AC-3 samples from elementary audio files formatted as AC-3
|
* Extracts samples from (E-)AC-3 bitstreams.
|
||||||
* bitstreams.
|
|
||||||
*/
|
*/
|
||||||
public final class Ac3Extractor implements Extractor {
|
public final class Ac3Extractor implements Extractor {
|
||||||
|
|
||||||
|
|
@ -71,6 +70,8 @@ public final class Ac3Extractor implements Extractor {
|
||||||
sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE);
|
sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extractor implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
// Skip any ID3 headers.
|
// Skip any ID3 headers.
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS
|
* Extracts samples from AAC bit streams with ADTS framing.
|
||||||
* headers.
|
|
||||||
*/
|
*/
|
||||||
public final class AdtsExtractor implements Extractor {
|
public final class AdtsExtractor implements Extractor {
|
||||||
|
|
||||||
|
|
@ -70,6 +69,8 @@ public final class AdtsExtractor implements Extractor {
|
||||||
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
|
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extractor implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
// Skip any ID3 headers.
|
// Skip any ID3 headers.
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation for {@link TsPayloadReader.Factory}.
|
* Default {@link TsPayloadReader.Factory} implementation.
|
||||||
*/
|
*/
|
||||||
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory {
|
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 mediaDataSource;
|
||||||
private final DataSource encryptionDataSource;
|
private final DataSource encryptionDataSource;
|
||||||
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
private final TimestampAdjusterProvider timestampAdjusterProvider;
|
||||||
|
|
@ -106,6 +107,8 @@ import java.util.List;
|
||||||
private long liveEdgeTimeUs;
|
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 playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists.
|
||||||
* @param variants The available variants.
|
* @param variants The available variants.
|
||||||
* @param dataSourceFactory An {@link HlsDataSourceFactory} to create {@link DataSource}s for the
|
* @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
|
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
|
||||||
* information is available in the master playlist.
|
* information is available in the master playlist.
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants,
|
public HlsChunkSource(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
|
||||||
HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider,
|
HlsUrl[] variants, HlsDataSourceFactory dataSourceFactory,
|
||||||
List<Format> muxedCaptionFormats) {
|
TimestampAdjusterProvider timestampAdjusterProvider, List<Format> muxedCaptionFormats) {
|
||||||
|
this.extractorFactory = extractorFactory;
|
||||||
this.playlistTracker = playlistTracker;
|
this.playlistTracker = playlistTracker;
|
||||||
this.variants = variants;
|
this.variants = variants;
|
||||||
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
||||||
|
|
@ -321,11 +325,11 @@ import java.util.List;
|
||||||
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
|
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
|
||||||
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
|
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
|
||||||
null);
|
null);
|
||||||
out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl,
|
out.chunk = new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, initDataSpec,
|
||||||
muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
|
selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(),
|
||||||
startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence,
|
trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs,
|
||||||
isTimestampMaster, timestampAdjuster, previous, mediaPlaylist.drmInitData, encryptionKey,
|
chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous,
|
||||||
encryptionIv);
|
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;
|
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.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.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.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.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;
|
||||||
|
|
@ -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.source.hls.playlist.HlsMasterPlaylist.HlsUrl;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
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.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
|
@ -49,19 +41,11 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
*/
|
*/
|
||||||
/* package */ final class HlsMediaChunk extends MediaChunk {
|
/* package */ final class HlsMediaChunk extends MediaChunk {
|
||||||
|
|
||||||
private static final AtomicInteger UID_SOURCE = new AtomicInteger();
|
|
||||||
|
|
||||||
private static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
private static final String PRIV_TIMESTAMP_FRAME_OWNER =
|
||||||
"com.apple.streaming.transportStreamTimestamp";
|
"com.apple.streaming.transportStreamTimestamp";
|
||||||
|
|
||||||
private static final String AAC_FILE_EXTENSION = ".aac";
|
private static final AtomicInteger uidSource = new AtomicInteger();
|
||||||
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";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A unique identifier for the chunk.
|
* A unique identifier for the chunk.
|
||||||
|
|
@ -83,26 +67,24 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
private final boolean isEncrypted;
|
private final boolean isEncrypted;
|
||||||
private final boolean isMasterTimestampSource;
|
private final boolean isMasterTimestampSource;
|
||||||
private final TimestampAdjuster timestampAdjuster;
|
private final TimestampAdjuster timestampAdjuster;
|
||||||
private final String lastPathSegment;
|
|
||||||
private final Extractor previousExtractor;
|
|
||||||
private final boolean shouldSpliceIn;
|
private final boolean shouldSpliceIn;
|
||||||
private final boolean needNewExtractor;
|
private final Extractor extractor;
|
||||||
private final List<Format> muxedCaptionFormats;
|
private final boolean isPackedAudioExtractor;
|
||||||
private final DrmInitData drmInitData;
|
private final boolean reusingExtractor;
|
||||||
|
|
||||||
private final boolean isPackedAudio;
|
|
||||||
private final Id3Decoder id3Decoder;
|
private final Id3Decoder id3Decoder;
|
||||||
private final ParsableByteArray id3Data;
|
private final ParsableByteArray id3Data;
|
||||||
|
|
||||||
private Extractor extractor;
|
private HlsSampleStreamWrapper output;
|
||||||
private int initSegmentBytesLoaded;
|
private int initSegmentBytesLoaded;
|
||||||
private int bytesLoaded;
|
private int bytesLoaded;
|
||||||
|
private boolean id3TimestampPeeked;
|
||||||
private boolean initLoadCompleted;
|
private boolean initLoadCompleted;
|
||||||
private HlsSampleStreamWrapper extractorOutput;
|
|
||||||
private volatile boolean loadCanceled;
|
private volatile boolean loadCanceled;
|
||||||
private volatile boolean loadCompleted;
|
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 dataSource The source from which the data should be loaded.
|
||||||
* @param dataSpec Defines the data to 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.
|
* @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
|
* @param encryptionIv The AES initialization vector, or null if the segment is not fully
|
||||||
* encrypted.
|
* encrypted.
|
||||||
*/
|
*/
|
||||||
public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec,
|
public HlsMediaChunk(HlsExtractorFactory extractorFactory, DataSource dataSource,
|
||||||
HlsUrl hlsUrl, List<Format> muxedCaptionFormats, int trackSelectionReason,
|
DataSpec dataSpec, DataSpec initDataSpec, HlsUrl hlsUrl, List<Format> muxedCaptionFormats,
|
||||||
Object trackSelectionData, long startTimeUs, long endTimeUs, int chunkIndex,
|
int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs,
|
||||||
int discontinuitySequenceNumber, boolean isMasterTimestampSource,
|
int chunkIndex, int discontinuitySequenceNumber, boolean isMasterTimestampSource,
|
||||||
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, DrmInitData drmInitData,
|
TimestampAdjuster timestampAdjuster, HlsMediaChunk previousChunk, DrmInitData drmInitData,
|
||||||
byte[] fullSegmentEncryptionKey, byte[] encryptionIv) {
|
byte[] fullSegmentEncryptionKey, byte[] encryptionIv) {
|
||||||
super(buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv), dataSpec,
|
super(buildDataSource(dataSource, fullSegmentEncryptionKey, encryptionIv), dataSpec,
|
||||||
|
|
@ -136,33 +118,34 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
|
this.discontinuitySequenceNumber = discontinuitySequenceNumber;
|
||||||
this.initDataSpec = initDataSpec;
|
this.initDataSpec = initDataSpec;
|
||||||
this.hlsUrl = hlsUrl;
|
this.hlsUrl = hlsUrl;
|
||||||
this.muxedCaptionFormats = muxedCaptionFormats;
|
|
||||||
this.isMasterTimestampSource = isMasterTimestampSource;
|
this.isMasterTimestampSource = isMasterTimestampSource;
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
// Note: this.dataSource and dataSource may be different.
|
// Note: this.dataSource and dataSource may be different.
|
||||||
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
|
this.isEncrypted = this.dataSource instanceof Aes128DataSource;
|
||||||
this.drmInitData = drmInitData;
|
Extractor previousExtractor = null;
|
||||||
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);
|
|
||||||
if (previousChunk != null) {
|
if (previousChunk != null) {
|
||||||
id3Decoder = previousChunk.id3Decoder;
|
|
||||||
id3Data = previousChunk.id3Data;
|
|
||||||
previousExtractor = previousChunk.extractor;
|
|
||||||
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl;
|
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl;
|
||||||
needNewExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|
previousExtractor = previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber
|
||||||
|| shouldSpliceIn;
|
|| shouldSpliceIn ? null : previousChunk.extractor;
|
||||||
} else {
|
} else {
|
||||||
id3Decoder = isPackedAudio ? new Id3Decoder() : null;
|
|
||||||
id3Data = isPackedAudio ? new ParsableByteArray(Id3Decoder.ID3_HEADER_LENGTH) : null;
|
|
||||||
previousExtractor = null;
|
|
||||||
shouldSpliceIn = false;
|
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;
|
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.
|
* @param output The output that will receive the loaded samples.
|
||||||
*/
|
*/
|
||||||
public void init(HlsSampleStreamWrapper output) {
|
public void init(HlsSampleStreamWrapper output) {
|
||||||
extractorOutput = output;
|
this.output = output;
|
||||||
output.init(uid, shouldSpliceIn);
|
output.init(uid, shouldSpliceIn);
|
||||||
|
if (!reusingExtractor) {
|
||||||
|
extractor.init(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -200,10 +186,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load() throws IOException, InterruptedException {
|
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();
|
maybeLoadInitData();
|
||||||
if (!loadCanceled) {
|
if (!loadCanceled) {
|
||||||
loadMedia();
|
loadMedia();
|
||||||
|
|
@ -213,8 +195,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
// Internal loading methods.
|
// Internal loading methods.
|
||||||
|
|
||||||
private void maybeLoadInitData() throws IOException, InterruptedException {
|
private void maybeLoadInitData() throws IOException, InterruptedException {
|
||||||
if (previousExtractor == extractor || initLoadCompleted || initDataSpec == null) {
|
if (initLoadCompleted || initDataSpec == null) {
|
||||||
// According to spec, for packed audio, initDataSpec is expected to be null.
|
// Note: The HLS spec forbids initialization segments for packed audio.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded);
|
DataSpec initSegmentDataSpec = initDataSpec.subrange(initSegmentBytesLoaded);
|
||||||
|
|
@ -258,10 +240,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
try {
|
try {
|
||||||
ExtractorInput input = new DefaultExtractorInput(dataSource,
|
ExtractorInput input = new DefaultExtractorInput(dataSource,
|
||||||
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
|
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
|
||||||
if (extractor == null) {
|
if (isPackedAudioExtractor && !id3TimestampPeeked) {
|
||||||
// Media segment format is packed audio.
|
|
||||||
long id3Timestamp = peekId3PrivTimestamp(input);
|
long id3Timestamp = peekId3PrivTimestamp(input);
|
||||||
extractor = buildPackedAudioExtractor(id3Timestamp != C.TIME_UNSET
|
id3TimestampPeeked = true;
|
||||||
|
output.setSampleOffsetUs(id3Timestamp != C.TIME_UNSET
|
||||||
? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
|
? timestampAdjuster.adjustTsTimestamp(id3Timestamp) : startTimeUs);
|
||||||
}
|
}
|
||||||
if (skipLoadedBytes) {
|
if (skipLoadedBytes) {
|
||||||
|
|
@ -345,68 +327,4 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
return dataSource;
|
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,
|
public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback,
|
||||||
HlsPlaylistTracker.PlaylistEventListener {
|
HlsPlaylistTracker.PlaylistEventListener {
|
||||||
|
|
||||||
|
private final HlsExtractorFactory extractorFactory;
|
||||||
private final HlsPlaylistTracker playlistTracker;
|
private final HlsPlaylistTracker playlistTracker;
|
||||||
private final HlsDataSourceFactory dataSourceFactory;
|
private final HlsDataSourceFactory dataSourceFactory;
|
||||||
private final int minLoadableRetryCount;
|
private final int minLoadableRetryCount;
|
||||||
|
|
@ -60,8 +61,10 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
|
private HlsSampleStreamWrapper[] enabledSampleStreamWrappers;
|
||||||
private CompositeSequenceableLoader sequenceableLoader;
|
private CompositeSequenceableLoader sequenceableLoader;
|
||||||
|
|
||||||
public HlsMediaPeriod(HlsPlaylistTracker playlistTracker, HlsDataSourceFactory dataSourceFactory,
|
public HlsMediaPeriod(HlsExtractorFactory extractorFactory, HlsPlaylistTracker playlistTracker,
|
||||||
int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator) {
|
HlsDataSourceFactory dataSourceFactory, int minLoadableRetryCount,
|
||||||
|
EventDispatcher eventDispatcher, Allocator allocator) {
|
||||||
|
this.extractorFactory = extractorFactory;
|
||||||
this.playlistTracker = playlistTracker;
|
this.playlistTracker = playlistTracker;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||||
|
|
@ -344,8 +347,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
||||||
|
|
||||||
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
|
private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants,
|
||||||
Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) {
|
Format muxedAudioFormat, List<Format> muxedCaptionFormats, long positionUs) {
|
||||||
HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants,
|
HlsChunkSource defaultChunkSource = new HlsChunkSource(extractorFactory, playlistTracker,
|
||||||
dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats);
|
variants, dataSourceFactory, timestampAdjusterProvider, muxedCaptionFormats);
|
||||||
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs,
|
return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, positionUs,
|
||||||
muxedAudioFormat, minLoadableRetryCount, eventDispatcher);
|
muxedAudioFormat, minLoadableRetryCount, eventDispatcher);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import android.os.Handler;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
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;
|
||||||
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
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;
|
public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3;
|
||||||
|
|
||||||
|
private final HlsExtractorFactory extractorFactory;
|
||||||
private final Uri manifestUri;
|
private final Uri manifestUri;
|
||||||
private final HlsDataSourceFactory dataSourceFactory;
|
private final HlsDataSourceFactory dataSourceFactory;
|
||||||
private final int minLoadableRetryCount;
|
private final int minLoadableRetryCount;
|
||||||
|
|
@ -60,32 +62,57 @@ public final class HlsMediaSource implements MediaSource,
|
||||||
private HlsPlaylistTracker playlistTracker;
|
private HlsPlaylistTracker playlistTracker;
|
||||||
private Listener sourceListener;
|
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,
|
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler,
|
||||||
AdaptiveMediaSourceEventListener eventListener) {
|
AdaptiveMediaSourceEventListener eventListener) {
|
||||||
this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
|
this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler,
|
||||||
eventListener);
|
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,
|
public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory,
|
||||||
int minLoadableRetryCount, Handler eventHandler,
|
int minLoadableRetryCount, Handler eventHandler,
|
||||||
AdaptiveMediaSourceEventListener eventListener) {
|
AdaptiveMediaSourceEventListener eventListener) {
|
||||||
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory), minLoadableRetryCount,
|
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory),
|
||||||
eventHandler, eventListener);
|
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,
|
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
|
||||||
int minLoadableRetryCount, Handler eventHandler,
|
HlsExtractorFactory extractorFactory, int minLoadableRetryCount, Handler eventHandler,
|
||||||
AdaptiveMediaSourceEventListener eventListener) {
|
|
||||||
this(manifestUri, dataSourceFactory, minLoadableRetryCount, eventHandler, eventListener,
|
|
||||||
new HlsPlaylistParser());
|
|
||||||
}
|
|
||||||
|
|
||||||
public HlsMediaSource(Uri manifestUri, HlsDataSourceFactory dataSourceFactory,
|
|
||||||
int minLoadableRetryCount, Handler eventHandler,
|
|
||||||
AdaptiveMediaSourceEventListener eventListener,
|
AdaptiveMediaSourceEventListener eventListener,
|
||||||
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
|
ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
|
||||||
this.manifestUri = manifestUri;
|
this.manifestUri = manifestUri;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
|
this.extractorFactory = extractorFactory;
|
||||||
this.minLoadableRetryCount = minLoadableRetryCount;
|
this.minLoadableRetryCount = minLoadableRetryCount;
|
||||||
this.playlistParser = playlistParser;
|
this.playlistParser = playlistParser;
|
||||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||||
|
|
@ -108,8 +135,8 @@ public final class HlsMediaSource implements MediaSource,
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
|
||||||
Assertions.checkArgument(id.periodIndex == 0);
|
Assertions.checkArgument(id.periodIndex == 0);
|
||||||
return new HlsMediaPeriod(playlistTracker, dataSourceFactory, minLoadableRetryCount,
|
return new HlsMediaPeriod(extractorFactory, playlistTracker, dataSourceFactory,
|
||||||
eventDispatcher, allocator);
|
minLoadableRetryCount, eventDispatcher, allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ import java.util.LinkedList;
|
||||||
private boolean[] trackGroupEnabledStates;
|
private boolean[] trackGroupEnabledStates;
|
||||||
private boolean[] trackGroupIsAudioVideoFlags;
|
private boolean[] trackGroupIsAudioVideoFlags;
|
||||||
|
|
||||||
|
private long sampleOffsetUs;
|
||||||
private long lastSeekPositionUs;
|
private long lastSeekPositionUs;
|
||||||
private long pendingResetPositionUs;
|
private long pendingResetPositionUs;
|
||||||
private boolean pendingResetUpstreamFormats;
|
private boolean pendingResetUpstreamFormats;
|
||||||
|
|
@ -369,16 +370,16 @@ import java.util.LinkedList;
|
||||||
|
|
||||||
// SampleStream implementation.
|
// SampleStream implementation.
|
||||||
|
|
||||||
/* package */ boolean isReady(int trackGroupIndex) {
|
public boolean isReady(int trackGroupIndex) {
|
||||||
return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample());
|
return loadingFinished || (!isPendingReset() && sampleQueues[trackGroupIndex].hasNextSample());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void maybeThrowError() throws IOException {
|
public void maybeThrowError() throws IOException {
|
||||||
loader.maybeThrowError();
|
loader.maybeThrowError();
|
||||||
chunkSource.maybeThrowError();
|
chunkSource.maybeThrowError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int readData(int trackGroupIndex, FormatHolder formatHolder,
|
public int readData(int trackGroupIndex, FormatHolder formatHolder,
|
||||||
DecoderInputBuffer buffer, boolean requireFormat) {
|
DecoderInputBuffer buffer, boolean requireFormat) {
|
||||||
if (isPendingReset()) {
|
if (isPendingReset()) {
|
||||||
return C.RESULT_NOTHING_READ;
|
return C.RESULT_NOTHING_READ;
|
||||||
|
|
@ -402,7 +403,7 @@ import java.util.LinkedList;
|
||||||
lastSeekPositionUs);
|
lastSeekPositionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ int skipData(int trackGroupIndex, long positionUs) {
|
public int skipData(int trackGroupIndex, long positionUs) {
|
||||||
SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
|
SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
|
||||||
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
|
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
|
||||||
return sampleQueue.advanceToEnd();
|
return sampleQueue.advanceToEnd();
|
||||||
|
|
@ -573,6 +574,7 @@ import java.util.LinkedList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SampleQueue trackOutput = new SampleQueue(allocator);
|
SampleQueue trackOutput = new SampleQueue(allocator);
|
||||||
|
trackOutput.setSampleOffsetUs(sampleOffsetUs);
|
||||||
trackOutput.setUpstreamFormatChangeListener(this);
|
trackOutput.setUpstreamFormatChangeListener(this);
|
||||||
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
|
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
|
||||||
sampleQueueTrackIds[trackCount] = id;
|
sampleQueueTrackIds[trackCount] = id;
|
||||||
|
|
@ -599,6 +601,15 @@ import java.util.LinkedList;
|
||||||
handler.post(maybeFinishPrepareRunnable);
|
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.
|
// Internal methods.
|
||||||
|
|
||||||
private void maybeFinishPrepare() {
|
private void maybeFinishPrepare() {
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,9 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
* playlist or a master playlist.
|
* playlist or a master playlist.
|
||||||
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
* @param dataSourceFactory A factory for {@link DataSource} instances.
|
||||||
* @param eventDispatcher A dispatcher to notify of events.
|
* @param eventDispatcher A dispatcher to notify of events.
|
||||||
* @param minRetryCount The minimum number of times the load must be retried before blacklisting a
|
* @param minRetryCount The minimum number of times loads must be retried before
|
||||||
* playlist.
|
* {@link #maybeThrowPlaylistRefreshError(HlsUrl)} and
|
||||||
|
* {@link #maybeThrowPrimaryPlaylistRefreshError()} propagate any loading errors.
|
||||||
* @param primaryPlaylistListener A callback for the primary playlist change events.
|
* @param primaryPlaylistListener A callback for the primary playlist change events.
|
||||||
*/
|
*/
|
||||||
public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory,
|
public HlsPlaylistTracker(Uri initialPlaylistUri, HlsDataSourceFactory dataSourceFactory,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue