mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Bring back multi-audio and VTT support for HLS.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117338865
This commit is contained in:
parent
1d4305cb91
commit
1eff6cf210
9 changed files with 171 additions and 105 deletions
|
|
@ -428,14 +428,14 @@ public class DemoPlayer implements ExoPlayer.Listener, DefaultTrackSelector.Even
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCues(List<Cue> cues) {
|
public void onCues(List<Cue> cues) {
|
||||||
if (captionListener != null && trackInfo.getTrackSelection(TYPE_TEXT) != null) {
|
if (captionListener != null) {
|
||||||
captionListener.onCues(cues);
|
captionListener.onCues(cues);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadata(List<Id3Frame> id3Frames) {
|
public void onMetadata(List<Id3Frame> id3Frames) {
|
||||||
if (id3MetadataListener != null && trackInfo.getTrackSelection(TYPE_METADATA) != null) {
|
if (id3MetadataListener != null) {
|
||||||
id3MetadataListener.onId3Metadata(id3Frames);
|
id3MetadataListener.onId3Metadata(id3Frames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ package com.google.android.exoplayer.demo.player;
|
||||||
|
|
||||||
import com.google.android.exoplayer.DefaultLoadControl;
|
import com.google.android.exoplayer.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer.LoadControl;
|
import com.google.android.exoplayer.LoadControl;
|
||||||
|
import com.google.android.exoplayer.MultiSampleSource;
|
||||||
import com.google.android.exoplayer.SampleSource;
|
import com.google.android.exoplayer.SampleSource;
|
||||||
|
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||||
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
import com.google.android.exoplayer.demo.player.DemoPlayer.SourceBuilder;
|
||||||
import com.google.android.exoplayer.hls.HlsChunkSource;
|
import com.google.android.exoplayer.hls.HlsChunkSource;
|
||||||
import com.google.android.exoplayer.hls.HlsPlaylist;
|
import com.google.android.exoplayer.hls.HlsPlaylist;
|
||||||
|
|
@ -40,7 +42,9 @@ import android.os.Handler;
|
||||||
public class HlsSourceBuilder implements SourceBuilder {
|
public class HlsSourceBuilder implements SourceBuilder {
|
||||||
|
|
||||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||||
private static final int MAIN_BUFFER_SEGMENTS = 256;
|
private static final int MAIN_BUFFER_SEGMENTS = 254;
|
||||||
|
private static final int AUDIO_BUFFER_SEGMENTS = 54;
|
||||||
|
private static final int TEXT_BUFFER_SEGMENTS = 2;
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final String userAgent;
|
private final String userAgent;
|
||||||
|
|
@ -64,11 +68,26 @@ public class HlsSourceBuilder implements SourceBuilder {
|
||||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||||
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
|
PtsTimestampAdjusterProvider timestampAdjusterProvider = new PtsTimestampAdjusterProvider();
|
||||||
|
|
||||||
DataSource dataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
DataSource defaultDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
HlsChunkSource chunkSource = new HlsChunkSource(manifestFetcher, HlsChunkSource.TYPE_DEFAULT,
|
HlsChunkSource defaultChunkSource = new HlsChunkSource(manifestFetcher,
|
||||||
dataSource, bandwidthMeter, timestampAdjusterProvider, HlsChunkSource.ADAPTIVE_MODE_SPLICE);
|
HlsChunkSource.TYPE_DEFAULT, defaultDataSource, timestampAdjusterProvider,
|
||||||
return new HlsSampleSource(chunkSource, loadControl,
|
new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter));
|
||||||
|
HlsSampleSource defaultSampleSource = new HlsSampleSource(defaultChunkSource, loadControl,
|
||||||
MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
MAIN_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_VIDEO);
|
||||||
|
|
||||||
|
DataSource audioDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
|
HlsChunkSource audioChunkSource = new HlsChunkSource(manifestFetcher, HlsChunkSource.TYPE_AUDIO,
|
||||||
|
audioDataSource, timestampAdjusterProvider, null);
|
||||||
|
HlsSampleSource audioSampleSource = new HlsSampleSource(audioChunkSource, loadControl,
|
||||||
|
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_AUDIO);
|
||||||
|
|
||||||
|
DataSource subtitleDataSource = new DefaultUriDataSource(context, bandwidthMeter, userAgent);
|
||||||
|
HlsChunkSource subtitleChunkSource = new HlsChunkSource(manifestFetcher,
|
||||||
|
HlsChunkSource.TYPE_SUBTITLE, subtitleDataSource, timestampAdjusterProvider, null);
|
||||||
|
HlsSampleSource subtitleSampleSource = new HlsSampleSource(subtitleChunkSource, loadControl,
|
||||||
|
TEXT_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, mainHandler, player, DemoPlayer.TYPE_TEXT);
|
||||||
|
|
||||||
|
return new MultiSampleSource(defaultSampleSource, audioSampleSource, subtitleSampleSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,10 +121,12 @@ public class TrackSelectionHelper implements View.OnClickListener, DialogInterfa
|
||||||
boolean haveSupportedTracks = false;
|
boolean haveSupportedTracks = false;
|
||||||
trackViews = new CheckedTextView[trackGroups.length][];
|
trackViews = new CheckedTextView[trackGroups.length][];
|
||||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||||
root.addView(inflater.inflate(R.layout.list_divider, root, false));
|
|
||||||
TrackGroup group = trackGroups.get(groupIndex);
|
TrackGroup group = trackGroups.get(groupIndex);
|
||||||
trackViews[groupIndex] = new CheckedTextView[group.length];
|
trackViews[groupIndex] = new CheckedTextView[group.length];
|
||||||
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
|
||||||
|
if (trackIndex == 0 || !group.adaptive) {
|
||||||
|
root.addView(inflater.inflate(R.layout.list_divider, root, false));
|
||||||
|
}
|
||||||
int trackViewLayoutId = group.length < 2 || !trackGroupsAdaptive[groupIndex]
|
int trackViewLayoutId = group.length < 2 || !trackGroupsAdaptive[groupIndex]
|
||||||
? android.R.layout.simple_list_item_single_choice
|
? android.R.layout.simple_list_item_single_choice
|
||||||
: android.R.layout.simple_list_item_multiple_choice;
|
: android.R.layout.simple_list_item_multiple_choice;
|
||||||
|
|
|
||||||
|
|
@ -558,6 +558,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The new selections are being activated.
|
||||||
|
trackSelector.onSelectionActivated(result.second);
|
||||||
enabledRenderers = new TrackRenderer[enabledRendererCount];
|
enabledRenderers = new TrackRenderer[enabledRendererCount];
|
||||||
enabledRendererCount = 0;
|
enabledRendererCount = 0;
|
||||||
|
|
||||||
|
|
@ -597,9 +599,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The new selections have been activated.
|
|
||||||
trackSelector.onSelectionActivated(result.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
private void reselectTracksInternal() throws ExoPlaybackException {
|
||||||
|
|
|
||||||
|
|
@ -295,7 +295,7 @@ public final class Format {
|
||||||
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
encoderDelay, encoderPadding, language, subsampleOffsetUs, initializationData,
|
||||||
requiresSecureDecryption);
|
requiresSecureDecryption);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return A {@link MediaFormat} representation of this format.
|
* @return A {@link MediaFormat} representation of this format.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
|
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
|
||||||
import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster;
|
import com.google.android.exoplayer.extractor.ts.PtsTimestampAdjuster;
|
||||||
import com.google.android.exoplayer.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer.extractor.ts.TsExtractor;
|
||||||
import com.google.android.exoplayer.upstream.BandwidthMeter;
|
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException;
|
import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException;
|
||||||
|
|
@ -64,41 +63,8 @@ public class HlsChunkSource {
|
||||||
public interface EventListener extends BaseChunkSampleSourceEventListener {}
|
public interface EventListener extends BaseChunkSampleSourceEventListener {}
|
||||||
|
|
||||||
public static final int TYPE_DEFAULT = 0;
|
public static final int TYPE_DEFAULT = 0;
|
||||||
public static final int TYPE_VTT = 1;
|
public static final int TYPE_AUDIO = 1;
|
||||||
|
public static final int TYPE_SUBTITLE = 2;
|
||||||
/**
|
|
||||||
* Adaptive switching is disabled.
|
|
||||||
* <p>
|
|
||||||
* The initially selected variant will be used throughout playback.
|
|
||||||
*/
|
|
||||||
public static final int ADAPTIVE_MODE_NONE = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adaptive switches splice overlapping segments of the old and new variants.
|
|
||||||
* <p>
|
|
||||||
* When performing a switch from one variant to another, overlapping segments will be requested
|
|
||||||
* from both the old and new variants. These segments will then be spliced together, allowing
|
|
||||||
* a seamless switch from one variant to another even if keyframes are misaligned or if keyframes
|
|
||||||
* are not positioned at the start of each segment.
|
|
||||||
* <p>
|
|
||||||
* Note that where it can be guaranteed that the source content has keyframes positioned at the
|
|
||||||
* start of each segment, {@link #ADAPTIVE_MODE_ABRUPT} should always be used in preference to
|
|
||||||
* this mode.
|
|
||||||
*/
|
|
||||||
public static final int ADAPTIVE_MODE_SPLICE = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adaptive switches are performed at segment boundaries.
|
|
||||||
* <p>
|
|
||||||
* For this mode to perform seamless switches, the source content is required to have keyframes
|
|
||||||
* positioned at the start of each segment. If this is not the case a visual discontinuity may
|
|
||||||
* be experienced when switching from one variant to another.
|
|
||||||
* <p>
|
|
||||||
* Note that where it can be guaranteed that the source content does have keyframes positioned at
|
|
||||||
* the start of each segment, this mode should always be used in preference to
|
|
||||||
* {@link #ADAPTIVE_MODE_SPLICE} because it requires fetching less data.
|
|
||||||
*/
|
|
||||||
public static final int ADAPTIVE_MODE_ABRUPT = 3;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default time for which a media playlist should be blacklisted.
|
* The default time for which a media playlist should be blacklisted.
|
||||||
|
|
@ -118,7 +84,6 @@ public class HlsChunkSource {
|
||||||
private final Evaluation evaluation;
|
private final Evaluation evaluation;
|
||||||
private final HlsPlaylistParser playlistParser;
|
private final HlsPlaylistParser playlistParser;
|
||||||
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
private final PtsTimestampAdjusterProvider timestampAdjusterProvider;
|
||||||
private final int adaptiveMode;
|
|
||||||
|
|
||||||
private boolean manifestFetcherEnabled;
|
private boolean manifestFetcherEnabled;
|
||||||
private byte[] scratchSpace;
|
private byte[] scratchSpace;
|
||||||
|
|
@ -146,26 +111,22 @@ public class HlsChunkSource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param manifestFetcher A fetcher for the playlist.
|
* @param manifestFetcher A fetcher for the playlist.
|
||||||
* @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT} and
|
* @param type The type of chunk provided by the source. One of {@link #TYPE_DEFAULT},
|
||||||
* {@link #TYPE_VTT}.
|
* {@link #TYPE_AUDIO} and {@link #TYPE_SUBTITLE}.
|
||||||
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
* @param dataSource A {@link DataSource} suitable for loading the media data.
|
||||||
* @param bandwidthMeter Provides an estimate of the currently available bandwidth.
|
|
||||||
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
* @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If
|
||||||
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
||||||
* same provider.
|
* same provider.
|
||||||
* @param adaptiveMode The mode for switching from one variant to another. One of
|
* @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats.
|
||||||
* {@link #ADAPTIVE_MODE_NONE}, {@link #ADAPTIVE_MODE_ABRUPT} and
|
|
||||||
* {@link #ADAPTIVE_MODE_SPLICE}.
|
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(ManifestFetcher<HlsPlaylist> manifestFetcher, int type,
|
public HlsChunkSource(ManifestFetcher<HlsPlaylist> manifestFetcher, int type,
|
||||||
DataSource dataSource, BandwidthMeter bandwidthMeter,
|
DataSource dataSource, PtsTimestampAdjusterProvider timestampAdjusterProvider,
|
||||||
PtsTimestampAdjusterProvider timestampAdjusterProvider, int adaptiveMode) {
|
FormatEvaluator adaptiveFormatEvaluator) {
|
||||||
this.manifestFetcher = manifestFetcher;
|
this.manifestFetcher = manifestFetcher;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.dataSource = dataSource;
|
this.dataSource = dataSource;
|
||||||
this.adaptiveFormatEvaluator = new FormatEvaluator.AdaptiveEvaluator(bandwidthMeter);
|
this.adaptiveFormatEvaluator = adaptiveFormatEvaluator;
|
||||||
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
this.timestampAdjusterProvider = timestampAdjusterProvider;
|
||||||
this.adaptiveMode = adaptiveMode;
|
|
||||||
playlistParser = new HlsPlaylistParser();
|
playlistParser = new HlsPlaylistParser();
|
||||||
evaluation = new Evaluation();
|
evaluation = new Evaluation();
|
||||||
}
|
}
|
||||||
|
|
@ -182,6 +143,15 @@ public class HlsChunkSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this source supports adaptation between its tracks.
|
||||||
|
*
|
||||||
|
* @return Whether this source supports adaptation between its tracks.
|
||||||
|
*/
|
||||||
|
public boolean isAdaptive() {
|
||||||
|
return adaptiveFormatEvaluator != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the source.
|
* Prepares the source.
|
||||||
*
|
*
|
||||||
|
|
@ -209,11 +179,13 @@ public class HlsChunkSource {
|
||||||
List<Variant> variants = new ArrayList<>();
|
List<Variant> variants = new ArrayList<>();
|
||||||
variants.add(new Variant(baseUri, format, null));
|
variants.add(new Variant(baseUri, format, null));
|
||||||
masterPlaylist = new HlsMasterPlaylist(baseUri, variants,
|
masterPlaylist = new HlsMasterPlaylist(baseUri, variants,
|
||||||
Collections.<Variant>emptyList());
|
Collections.<Variant>emptyList(), Collections.<Variant>emptyList(), null, null);
|
||||||
}
|
}
|
||||||
processMasterPlaylist(masterPlaylist);
|
processMasterPlaylist(masterPlaylist);
|
||||||
// TODO[REFACTOR]: Come up with a sane default here.
|
if (exposedVariants.length > 0) {
|
||||||
selectTracks(new int[] {0});
|
// TODO[REFACTOR]: Come up with a sane default here.
|
||||||
|
selectTracks(new int[] {0});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -264,6 +236,28 @@ public class HlsChunkSource {
|
||||||
return exposedVariants[index].format;
|
return exposedVariants[index].format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the format of the audio muxed into variants, or null if unknown.
|
||||||
|
* <p>
|
||||||
|
* This method should only be called after the source has been prepared.
|
||||||
|
*
|
||||||
|
* @return The format of the audio muxed into variants, or null if unknown.
|
||||||
|
*/
|
||||||
|
public Format getMuxedAudioFormat() {
|
||||||
|
return masterPlaylist.muxedAudioFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the format of the captions muxed into variants, or null if unknown.
|
||||||
|
* <p>
|
||||||
|
* This method should only be called after the source has been prepared.
|
||||||
|
*
|
||||||
|
* @return The format of the captions muxed into variants, or null if unknown.
|
||||||
|
*/
|
||||||
|
public Format getMuxedCaptionFormat() {
|
||||||
|
return masterPlaylist.muxedCaptionFormat;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects tracks for use.
|
* Selects tracks for use.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -333,17 +327,9 @@ public class HlsChunkSource {
|
||||||
*/
|
*/
|
||||||
public void getChunkOperation(TsChunk previousTsChunk, long playbackPositionUs,
|
public void getChunkOperation(TsChunk previousTsChunk, long playbackPositionUs,
|
||||||
ChunkOperationHolder out) {
|
ChunkOperationHolder out) {
|
||||||
int nextVariantIndex;
|
int nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
|
||||||
boolean switchingVariantSpliced;
|
boolean switchingVariant = previousTsChunk != null
|
||||||
if (adaptiveMode == ADAPTIVE_MODE_NONE) {
|
&& enabledVariants[nextVariantIndex].format != previousTsChunk.format;
|
||||||
nextVariantIndex = selectedVariantIndex;
|
|
||||||
switchingVariantSpliced = false;
|
|
||||||
} else {
|
|
||||||
nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
|
|
||||||
switchingVariantSpliced = previousTsChunk != null
|
|
||||||
&& enabledVariants[nextVariantIndex].format != previousTsChunk.format
|
|
||||||
&& adaptiveMode == ADAPTIVE_MODE_SPLICE;
|
|
||||||
}
|
|
||||||
|
|
||||||
HlsMediaPlaylist mediaPlaylist = enabledVariantPlaylists[nextVariantIndex];
|
HlsMediaPlaylist mediaPlaylist = enabledVariantPlaylists[nextVariantIndex];
|
||||||
if (mediaPlaylist == null) {
|
if (mediaPlaylist == null) {
|
||||||
|
|
@ -358,8 +344,8 @@ public class HlsChunkSource {
|
||||||
if (previousTsChunk == null) {
|
if (previousTsChunk == null) {
|
||||||
chunkMediaSequence = getLiveStartChunkMediaSequence(nextVariantIndex);
|
chunkMediaSequence = getLiveStartChunkMediaSequence(nextVariantIndex);
|
||||||
} else {
|
} else {
|
||||||
chunkMediaSequence = switchingVariantSpliced
|
chunkMediaSequence = switchingVariant ? previousTsChunk.chunkIndex
|
||||||
? previousTsChunk.chunkIndex : previousTsChunk.chunkIndex + 1;
|
: previousTsChunk.chunkIndex + 1;
|
||||||
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
|
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
|
||||||
fatalError = new BehindLiveWindowException();
|
fatalError = new BehindLiveWindowException();
|
||||||
return;
|
return;
|
||||||
|
|
@ -371,8 +357,8 @@ public class HlsChunkSource {
|
||||||
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs,
|
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs,
|
||||||
true, true) + mediaPlaylist.mediaSequence;
|
true, true) + mediaPlaylist.mediaSequence;
|
||||||
} else {
|
} else {
|
||||||
chunkMediaSequence = switchingVariantSpliced
|
chunkMediaSequence = switchingVariant ? previousTsChunk.chunkIndex
|
||||||
? previousTsChunk.chunkIndex : previousTsChunk.chunkIndex + 1;
|
: previousTsChunk.chunkIndex + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -413,7 +399,7 @@ public class HlsChunkSource {
|
||||||
if (live) {
|
if (live) {
|
||||||
if (previousTsChunk == null) {
|
if (previousTsChunk == null) {
|
||||||
startTimeUs = 0;
|
startTimeUs = 0;
|
||||||
} else if (switchingVariantSpliced) {
|
} else if (switchingVariant) {
|
||||||
startTimeUs = previousTsChunk.startTimeUs;
|
startTimeUs = previousTsChunk.startTimeUs;
|
||||||
} else {
|
} else {
|
||||||
startTimeUs = previousTsChunk.endTimeUs;
|
startTimeUs = previousTsChunk.endTimeUs;
|
||||||
|
|
@ -434,11 +420,11 @@ public class HlsChunkSource {
|
||||||
// case below.
|
// case below.
|
||||||
Extractor extractor = new AdtsExtractor(startTimeUs);
|
Extractor extractor = new AdtsExtractor(startTimeUs);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced);
|
switchingVariant);
|
||||||
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
|
} else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) {
|
||||||
Extractor extractor = new Mp3Extractor(startTimeUs);
|
Extractor extractor = new Mp3Extractor(startTimeUs);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced);
|
switchingVariant);
|
||||||
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|
} else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION)
|
||||||
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
|
|| lastPathSegment.endsWith(VTT_FILE_EXTENSION)) {
|
||||||
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(false,
|
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(false,
|
||||||
|
|
@ -451,7 +437,7 @@ public class HlsChunkSource {
|
||||||
}
|
}
|
||||||
Extractor extractor = new WebvttExtractor(format.language, timestampAdjuster);
|
Extractor extractor = new WebvttExtractor(format.language, timestampAdjuster);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced);
|
switchingVariant);
|
||||||
} else if (previousTsChunk == null
|
} else if (previousTsChunk == null
|
||||||
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
||||||
|| format != previousTsChunk.format) {
|
|| format != previousTsChunk.format) {
|
||||||
|
|
@ -468,16 +454,16 @@ public class HlsChunkSource {
|
||||||
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
|
// 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
|
// exist. If we know from the codec attribute that they don't exist, then we can explicitly
|
||||||
// ignore them even if they're declared.
|
// ignore them even if they're declared.
|
||||||
if (MimeTypes.getAudioMediaMimeType(codecs) != MimeTypes.AUDIO_AAC) {
|
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
|
||||||
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM;
|
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM;
|
||||||
}
|
}
|
||||||
if (MimeTypes.getVideoMediaMimeType(codecs) != MimeTypes.VIDEO_H264) {
|
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
|
||||||
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM;
|
workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Extractor extractor = new TsExtractor(timestampAdjuster, workaroundFlags);
|
Extractor extractor = new TsExtractor(timestampAdjuster, workaroundFlags);
|
||||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||||
switchingVariantSpliced);
|
switchingVariant);
|
||||||
} else {
|
} else {
|
||||||
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
||||||
extractorWrapper = previousTsChunk.extractorWrapper;
|
extractorWrapper = previousTsChunk.extractorWrapper;
|
||||||
|
|
@ -562,11 +548,11 @@ public class HlsChunkSource {
|
||||||
// Private methods.
|
// Private methods.
|
||||||
|
|
||||||
private void processMasterPlaylist(HlsMasterPlaylist playlist) {
|
private void processMasterPlaylist(HlsMasterPlaylist playlist) {
|
||||||
if (type == TYPE_VTT) {
|
if (type == TYPE_SUBTITLE || type == TYPE_AUDIO) {
|
||||||
List<Variant> subtitleVariants = playlist.subtitles;
|
List<Variant> variants = type == TYPE_AUDIO ? playlist.audios : playlist.subtitles;
|
||||||
if (subtitleVariants != null) {
|
if (variants != null && !variants.isEmpty()) {
|
||||||
exposedVariants = new Variant[subtitleVariants.size()];
|
exposedVariants = new Variant[variants.size()];
|
||||||
subtitleVariants.toArray(exposedVariants);
|
variants.toArray(exposedVariants);
|
||||||
} else {
|
} else {
|
||||||
exposedVariants = new Variant[0];
|
exposedVariants = new Variant[0];
|
||||||
}
|
}
|
||||||
|
|
@ -622,8 +608,7 @@ public class HlsChunkSource {
|
||||||
long switchingOverlapUs;
|
long switchingOverlapUs;
|
||||||
List<TsChunk> queue;
|
List<TsChunk> queue;
|
||||||
if (previousTsChunk != null) {
|
if (previousTsChunk != null) {
|
||||||
switchingOverlapUs = adaptiveMode == ADAPTIVE_MODE_SPLICE
|
switchingOverlapUs = previousTsChunk.endTimeUs - previousTsChunk.startTimeUs;
|
||||||
? previousTsChunk.endTimeUs - previousTsChunk.startTimeUs : 0;
|
|
||||||
queue = Collections.singletonList(previousTsChunk);
|
queue = Collections.singletonList(previousTsChunk);
|
||||||
} else {
|
} else {
|
||||||
switchingOverlapUs = 0;
|
switchingOverlapUs = 0;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.hls;
|
package com.google.android.exoplayer.hls;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.Format;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -24,12 +26,20 @@ import java.util.List;
|
||||||
public final class HlsMasterPlaylist extends HlsPlaylist {
|
public final class HlsMasterPlaylist extends HlsPlaylist {
|
||||||
|
|
||||||
public final List<Variant> variants;
|
public final List<Variant> variants;
|
||||||
|
public final List<Variant> audios;
|
||||||
public final List<Variant> subtitles;
|
public final List<Variant> subtitles;
|
||||||
|
|
||||||
public HlsMasterPlaylist(String baseUri, List<Variant> variants, List<Variant> subtitles) {
|
public final Format muxedAudioFormat;
|
||||||
|
public final Format muxedCaptionFormat;
|
||||||
|
|
||||||
|
public HlsMasterPlaylist(String baseUri, List<Variant> variants, List<Variant> audios,
|
||||||
|
List<Variant> subtitles, Format muxedAudioFormat, Format muxedCaptionFormat) {
|
||||||
super(baseUri, HlsPlaylist.TYPE_MASTER);
|
super(baseUri, HlsPlaylist.TYPE_MASTER);
|
||||||
this.variants = Collections.unmodifiableList(variants);
|
this.variants = Collections.unmodifiableList(variants);
|
||||||
|
this.audios = Collections.unmodifiableList(audios);
|
||||||
this.subtitles = Collections.unmodifiableList(subtitles);
|
this.subtitles = Collections.unmodifiableList(subtitles);
|
||||||
|
this.muxedAudioFormat = muxedAudioFormat;
|
||||||
|
this.muxedCaptionFormat = muxedCaptionFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|
||||||
private static final String METHOD_ATTR = "METHOD";
|
private static final String METHOD_ATTR = "METHOD";
|
||||||
private static final String URI_ATTR = "URI";
|
private static final String URI_ATTR = "URI";
|
||||||
private static final String IV_ATTR = "IV";
|
private static final String IV_ATTR = "IV";
|
||||||
|
private static final String INSTREAM_ID_ATTR = "INSTREAM-ID";
|
||||||
|
|
||||||
private static final String AUDIO_TYPE = "AUDIO";
|
private static final String AUDIO_TYPE = "AUDIO";
|
||||||
private static final String VIDEO_TYPE = "VIDEO";
|
private static final String VIDEO_TYPE = "VIDEO";
|
||||||
|
|
@ -98,6 +99,8 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|
||||||
Pattern.compile(LANGUAGE_ATTR + "=\"(.+?)\"");
|
Pattern.compile(LANGUAGE_ATTR + "=\"(.+?)\"");
|
||||||
private static final Pattern NAME_ATTR_REGEX =
|
private static final Pattern NAME_ATTR_REGEX =
|
||||||
Pattern.compile(NAME_ATTR + "=\"(.+?)\"");
|
Pattern.compile(NAME_ATTR + "=\"(.+?)\"");
|
||||||
|
private static final Pattern INSTREAM_ID_ATTR_REGEX =
|
||||||
|
Pattern.compile(INSTREAM_ID_ATTR + "=\"(.+?)\"");
|
||||||
// private static final Pattern AUTOSELECT_ATTR_REGEX =
|
// private static final Pattern AUTOSELECT_ATTR_REGEX =
|
||||||
// HlsParserUtil.compileBooleanAttrPattern(AUTOSELECT_ATTR);
|
// HlsParserUtil.compileBooleanAttrPattern(AUTOSELECT_ATTR);
|
||||||
// private static final Pattern DEFAULT_ATTR_REGEX =
|
// private static final Pattern DEFAULT_ATTR_REGEX =
|
||||||
|
|
@ -140,12 +143,15 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|
||||||
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
|
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
ArrayList<Variant> variants = new ArrayList<>();
|
ArrayList<Variant> variants = new ArrayList<>();
|
||||||
|
ArrayList<Variant> audios = new ArrayList<>();
|
||||||
ArrayList<Variant> subtitles = new ArrayList<>();
|
ArrayList<Variant> subtitles = new ArrayList<>();
|
||||||
int bitrate = 0;
|
int bitrate = 0;
|
||||||
String codecs = null;
|
String codecs = null;
|
||||||
int width = -1;
|
int width = -1;
|
||||||
int height = -1;
|
int height = -1;
|
||||||
String name = null;
|
String name = null;
|
||||||
|
Format muxedAudioFormat = null;
|
||||||
|
Format muxedCaptionFormat = null;
|
||||||
|
|
||||||
boolean expectingStreamInfUrl = false;
|
boolean expectingStreamInfUrl = false;
|
||||||
String line;
|
String line;
|
||||||
|
|
@ -153,16 +159,37 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|
||||||
line = iterator.next();
|
line = iterator.next();
|
||||||
if (line.startsWith(MEDIA_TAG)) {
|
if (line.startsWith(MEDIA_TAG)) {
|
||||||
String type = HlsParserUtil.parseStringAttr(line, TYPE_ATTR_REGEX, TYPE_ATTR);
|
String type = HlsParserUtil.parseStringAttr(line, TYPE_ATTR_REGEX, TYPE_ATTR);
|
||||||
if (SUBTITLES_TYPE.equals(type)) {
|
if (CLOSED_CAPTIONS_TYPE.equals(type)) {
|
||||||
|
String instreamId = HlsParserUtil.parseStringAttr(line, INSTREAM_ID_ATTR_REGEX,
|
||||||
|
INSTREAM_ID_ATTR);
|
||||||
|
if ("CC1".equals(instreamId)) {
|
||||||
|
// We assume all subtitles belong to the same group.
|
||||||
|
String captionName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
|
||||||
|
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
|
||||||
|
muxedCaptionFormat = Format.createTextContainerFormat(captionName,
|
||||||
|
MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_EIA608, -1, language);
|
||||||
|
}
|
||||||
|
} else if (SUBTITLES_TYPE.equals(type)) {
|
||||||
// We assume all subtitles belong to the same group.
|
// We assume all subtitles belong to the same group.
|
||||||
String subtitleName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
|
String subtitleName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
|
||||||
String uri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR);
|
String uri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR);
|
||||||
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
|
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
|
||||||
Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8,
|
Format format = Format.createTextContainerFormat(subtitleName, MimeTypes.APPLICATION_M3U8,
|
||||||
MimeTypes.TEXT_VTT, bitrate, language);
|
MimeTypes.TEXT_VTT, bitrate, language);
|
||||||
subtitles.add(new Variant(uri, format, null));
|
subtitles.add(new Variant(uri, format, codecs));
|
||||||
} else {
|
} else if (AUDIO_TYPE.equals(type)) {
|
||||||
// TODO: Support other types of media tag.
|
// We assume all audios belong to the same group.
|
||||||
|
String uri = HlsParserUtil.parseOptionalStringAttr(line, URI_ATTR_REGEX);
|
||||||
|
String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX);
|
||||||
|
String audioName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR);
|
||||||
|
int audioBitrate = uri != null ? bitrate : -1;
|
||||||
|
Format format = Format.createAudioContainerFormat(audioName, MimeTypes.APPLICATION_M3U8,
|
||||||
|
null, audioBitrate, -1, -1, null, language);
|
||||||
|
if (uri != null) {
|
||||||
|
audios.add(new Variant(uri, format, codecs));
|
||||||
|
} else {
|
||||||
|
muxedAudioFormat = format;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (line.startsWith(STREAM_INF_TAG)) {
|
} else if (line.startsWith(STREAM_INF_TAG)) {
|
||||||
bitrate = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR);
|
bitrate = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR);
|
||||||
|
|
@ -202,7 +229,8 @@ public final class HlsPlaylistParser implements UriLoadable.Parser<HlsPlaylist>
|
||||||
expectingStreamInfUrl = false;
|
expectingStreamInfUrl = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new HlsMasterPlaylist(baseUri, variants, subtitles);
|
return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat,
|
||||||
|
muxedCaptionFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
|
private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri)
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,9 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||||
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
|
private static final long NO_RESET_PENDING = Long.MIN_VALUE;
|
||||||
|
|
||||||
private static final int PRIMARY_TYPE_NONE = 0;
|
private static final int PRIMARY_TYPE_NONE = 0;
|
||||||
private static final int PRIMARY_TYPE_AUDIO = 1;
|
private static final int PRIMARY_TYPE_TEXT = 1;
|
||||||
private static final int PRIMARY_TYPE_VIDEO = 2;
|
private static final int PRIMARY_TYPE_AUDIO = 2;
|
||||||
|
private static final int PRIMARY_TYPE_VIDEO = 3;
|
||||||
|
|
||||||
private final HlsChunkSource chunkSource;
|
private final HlsChunkSource chunkSource;
|
||||||
private final LinkedList<HlsExtractorWrapper> extractors;
|
private final LinkedList<HlsExtractorWrapper> extractors;
|
||||||
|
|
@ -136,6 +137,10 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||||
return true;
|
return true;
|
||||||
} else if (!chunkSource.prepare()) {
|
} else if (!chunkSource.prepare()) {
|
||||||
return false;
|
return false;
|
||||||
|
} else if (chunkSource.getTrackCount() == 0) {
|
||||||
|
trackGroups = new TrackGroupArray();
|
||||||
|
prepared = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (!extractors.isEmpty()) {
|
if (!extractors.isEmpty()) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -368,15 +373,20 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||||
} else if (loadingFinished) {
|
} else if (loadingFinished) {
|
||||||
return C.END_OF_SOURCE_US;
|
return C.END_OF_SOURCE_US;
|
||||||
} else {
|
} else {
|
||||||
long largestParsedTimestampUs = extractors.getLast().getLargestParsedTimestampUs();
|
long bufferedPositionUs = extractors.getLast().getLargestParsedTimestampUs();
|
||||||
if (extractors.size() > 1) {
|
if (extractors.size() > 1) {
|
||||||
// When adapting from one format to the next, the penultimate extractor may have the largest
|
// When adapting from one format to the next, the penultimate extractor may have the largest
|
||||||
// parsed timestamp (e.g. if the last extractor hasn't parsed any timestamps yet).
|
// parsed timestamp (e.g. if the last extractor hasn't parsed any timestamps yet).
|
||||||
largestParsedTimestampUs = Math.max(largestParsedTimestampUs,
|
bufferedPositionUs = Math.max(bufferedPositionUs,
|
||||||
extractors.get(extractors.size() - 2).getLargestParsedTimestampUs());
|
extractors.get(extractors.size() - 2).getLargestParsedTimestampUs());
|
||||||
}
|
}
|
||||||
return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
|
if (previousTsLoadable != null) {
|
||||||
: largestParsedTimestampUs;
|
// Buffered position should be at least as large as the end time of the previously loaded
|
||||||
|
// chunk.
|
||||||
|
bufferedPositionUs = Math.max(previousTsLoadable.endTimeUs, bufferedPositionUs);
|
||||||
|
}
|
||||||
|
return bufferedPositionUs == Long.MIN_VALUE ? downstreamPositionUs
|
||||||
|
: bufferedPositionUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -489,6 +499,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||||
trackType = PRIMARY_TYPE_VIDEO;
|
trackType = PRIMARY_TYPE_VIDEO;
|
||||||
} else if (MimeTypes.isAudio(sampleMimeType)) {
|
} else if (MimeTypes.isAudio(sampleMimeType)) {
|
||||||
trackType = PRIMARY_TYPE_AUDIO;
|
trackType = PRIMARY_TYPE_AUDIO;
|
||||||
|
} else if (MimeTypes.isText(sampleMimeType)) {
|
||||||
|
trackType = PRIMARY_TYPE_TEXT;
|
||||||
} else {
|
} else {
|
||||||
trackType = PRIMARY_TYPE_NONE;
|
trackType = PRIMARY_TYPE_NONE;
|
||||||
}
|
}
|
||||||
|
|
@ -520,10 +532,18 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||||
for (int j = 0; j < chunkSourceTrackCount; j++) {
|
for (int j = 0; j < chunkSourceTrackCount; j++) {
|
||||||
formats[j] = getSampleFormat(chunkSource.getTrackFormat(j), sampleFormat);
|
formats[j] = getSampleFormat(chunkSource.getTrackFormat(j), sampleFormat);
|
||||||
}
|
}
|
||||||
trackGroups[i] = new TrackGroup(true, formats);
|
trackGroups[i] = new TrackGroup(chunkSource.isAdaptive(), formats);
|
||||||
primaryTrackGroupIndex = i;
|
primaryTrackGroupIndex = i;
|
||||||
} else {
|
} else {
|
||||||
trackGroups[i] = new TrackGroup(sampleFormat);
|
Format trackFormat = null;
|
||||||
|
if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) {
|
||||||
|
if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) {
|
||||||
|
trackFormat = chunkSource.getMuxedAudioFormat();
|
||||||
|
} else if (MimeTypes.APPLICATION_EIA608.equals(sampleFormat.sampleMimeType)) {
|
||||||
|
trackFormat = chunkSource.getMuxedCaptionFormat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackGroups[i] = new TrackGroup(getSampleFormat(trackFormat, sampleFormat));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.trackGroups = new TrackGroupArray(trackGroups);
|
this.trackGroups = new TrackGroupArray(trackGroups);
|
||||||
|
|
@ -550,6 +570,9 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
||||||
* @return The derived sample format.
|
* @return The derived sample format.
|
||||||
*/
|
*/
|
||||||
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
|
private static Format getSampleFormat(Format containerFormat, Format sampleFormat) {
|
||||||
|
if (containerFormat == null) {
|
||||||
|
return sampleFormat;
|
||||||
|
}
|
||||||
int width = containerFormat.width == -1 ? Format.NO_VALUE : containerFormat.width;
|
int width = containerFormat.width == -1 ? Format.NO_VALUE : containerFormat.width;
|
||||||
int height = containerFormat.height == -1 ? Format.NO_VALUE : containerFormat.height;
|
int height = containerFormat.height == -1 ? Format.NO_VALUE : containerFormat.height;
|
||||||
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate, width,
|
return sampleFormat.copyWithContainerInfo(containerFormat.id, containerFormat.bitrate, width,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue