use of com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant in project ExoPlayer by google.
the class HlsChunkSource method getNextChunk.
/**
* Returns the next chunk to load.
* <p>
* If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream has
* been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available but
* the end of the stream has not been reached, {@link HlsChunkHolder#playlist} is set to
* contain the {@link HlsUrl} that refers to the playlist that needs refreshing.
*
* @param previous The most recently loaded media chunk.
* @param playbackPositionUs The current playback position. If {@code previous} is null then this
* parameter is the position from which playback is expected to start (or restart) and hence
* should be interpreted as a seek position.
* @param out A holder to populate.
*/
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) {
int oldVariantIndex = previous == null ? C.INDEX_UNSET : trackGroup.indexOf(previous.trackFormat);
// Use start time of the previous chunk rather than its end time because switching format will
// require downloading overlapping segments.
long bufferedDurationUs = previous == null ? 0 : Math.max(0, previous.startTimeUs - playbackPositionUs);
// Select the variant.
trackSelection.updateSelectedTrack(bufferedDurationUs);
int selectedVariantIndex = trackSelection.getSelectedIndexInTrackGroup();
boolean switchingVariant = oldVariantIndex != selectedVariantIndex;
HlsUrl selectedUrl = variants[selectedVariantIndex];
if (!playlistTracker.isSnapshotValid(selectedUrl)) {
out.playlist = selectedUrl;
// Retry when playlist is refreshed.
return;
}
HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
// Select the chunk.
int chunkMediaSequence;
if (previous == null || switchingVariant) {
long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs;
if (!mediaPlaylist.hasEndTag && targetPositionUs > mediaPlaylist.getEndTimeUs()) {
// If the playlist is too old to contain the chunk, we need to refresh it.
chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();
} else {
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, targetPositionUs - mediaPlaylist.startTimeUs, true, !playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence;
if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) {
// We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window.
selectedVariantIndex = oldVariantIndex;
selectedUrl = variants[selectedVariantIndex];
mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
chunkMediaSequence = previous.getNextChunkIndex();
}
}
} else {
chunkMediaSequence = previous.getNextChunkIndex();
}
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
fatalError = new BehindLiveWindowException();
return;
}
int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence;
if (chunkIndex >= mediaPlaylist.segments.size()) {
if (mediaPlaylist.hasEndTag) {
out.endOfStream = true;
} else /* Live */
{
out.playlist = selectedUrl;
}
return;
}
// Handle encryption.
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
// Check if encryption is specified.
if (segment.isEncrypted) {
Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri);
if (!keyUri.equals(encryptionKeyUri)) {
// Encryption is specified and the key has changed.
out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex, trackSelection.getSelectionReason(), trackSelection.getSelectionData());
return;
}
if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) {
setEncryptionData(keyUri, segment.encryptionIV, encryptionKey);
}
} else {
clearEncryptionData();
}
DataSpec initDataSpec = null;
Segment initSegment = mediaPlaylist.initializationSegment;
if (initSegment != null) {
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset, initSegment.byterangeLength, null);
}
// Compute start time of the next chunk.
long startTimeUs = mediaPlaylist.startTimeUs + segment.relativeStartTimeUs;
int discontinuitySequence = mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence;
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(discontinuitySequence);
// Configure the data source and spec for the chunk.
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, null);
out.chunk = new HlsMediaChunk(mediaDataSource, dataSpec, initDataSpec, selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs, chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, encryptionIv);
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant in project ExoPlayer by google.
the class HlsSampleStreamWrapper method buildTracks.
/**
* Builds tracks that are exposed by this {@link HlsSampleStreamWrapper} instance, as well as
* internal data-structures required for operation.
* <p>
* Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each
* variant stream typically contains muxed video, audio and (possibly) additional audio, metadata
* and caption tracks. We wish to allow the user to select between an adaptive track that spans
* all variants, as well as each individual variant. If multiple audio tracks are present within
* each variant then we wish to allow the user to select between those also.
* <p>
* To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1) tracks,
* where N is the number of variants defined in the HLS master playlist. These consist of one
* adaptive track defined to span all variants and a track for each individual variant. The
* adaptive track is initially selected. The extractor is then prepared to discover the tracks
* inside of each variant stream. The two sets of tracks are then combined by this method to
* create a third set, which is the set exposed by this {@link HlsSampleStreamWrapper}:
* <ul>
* <li>The extractor tracks are inspected to infer a "primary" track type. If a video track is
* present then it is always the primary type. If not, audio is the primary type if present.
* Else text is the primary type if present. Else there is no primary type.</li>
* <li>If there is exactly one extractor track of the primary type, it's expanded into (N+1)
* exposed tracks, all of which correspond to the primary extractor track and each of which
* corresponds to a different chunk source track. Selecting one of these tracks has the effect
* of switching the selected track on the chunk source.</li>
* <li>All other extractor tracks are exposed directly. Selecting one of these tracks has the
* effect of selecting an extractor track, leaving the selected track on the chunk source
* unchanged.</li>
* </ul>
*/
private void buildTracks() {
// Iterate through the extractor tracks to discover the "primary" track type, and the index
// of the single track of this type.
int primaryExtractorTrackType = PRIMARY_TYPE_NONE;
int primaryExtractorTrackIndex = C.INDEX_UNSET;
int extractorTrackCount = sampleQueues.size();
for (int i = 0; i < extractorTrackCount; i++) {
String sampleMimeType = sampleQueues.valueAt(i).getUpstreamFormat().sampleMimeType;
int trackType;
if (MimeTypes.isVideo(sampleMimeType)) {
trackType = PRIMARY_TYPE_VIDEO;
} else if (MimeTypes.isAudio(sampleMimeType)) {
trackType = PRIMARY_TYPE_AUDIO;
} else if (MimeTypes.isText(sampleMimeType)) {
trackType = PRIMARY_TYPE_TEXT;
} else {
trackType = PRIMARY_TYPE_NONE;
}
if (trackType > primaryExtractorTrackType) {
primaryExtractorTrackType = trackType;
primaryExtractorTrackIndex = i;
} else if (trackType == primaryExtractorTrackType && primaryExtractorTrackIndex != C.INDEX_UNSET) {
// We have multiple tracks of the primary type. We only want an index if there only exists a
// single track of the primary type, so unset the index again.
primaryExtractorTrackIndex = C.INDEX_UNSET;
}
}
TrackGroup chunkSourceTrackGroup = chunkSource.getTrackGroup();
int chunkSourceTrackCount = chunkSourceTrackGroup.length;
// Instantiate the necessary internal data-structures.
primaryTrackGroupIndex = C.INDEX_UNSET;
groupEnabledStates = new boolean[extractorTrackCount];
// Construct the set of exposed track groups.
TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
for (int i = 0; i < extractorTrackCount; i++) {
Format sampleFormat = sampleQueues.valueAt(i).getUpstreamFormat();
if (i == primaryExtractorTrackIndex) {
Format[] formats = new Format[chunkSourceTrackCount];
for (int j = 0; j < chunkSourceTrackCount; j++) {
formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat);
}
trackGroups[i] = new TrackGroup(formats);
primaryTrackGroupIndex = i;
} else {
Format trackFormat = primaryExtractorTrackType == PRIMARY_TYPE_VIDEO && MimeTypes.isAudio(sampleFormat.sampleMimeType) ? muxedAudioFormat : null;
trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat));
}
}
this.trackGroups = new TrackGroupArray(trackGroups);
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant in project ExoPlayer by google.
the class HlsMultivariantPlaylistParserTest method parseMultivariantPlaylist_withVariableSubstitution_success.
@Test
public void parseMultivariantPlaylist_withVariableSubstitution_success() throws IOException {
HlsMultivariantPlaylist playlistWithSubstitutions = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_VARIABLE_SUBSTITUTION);
HlsMultivariantPlaylist.Variant variant = playlistWithSubstitutions.variants.get(0);
assertThat(variant.format.codecs).isEqualTo("mp4a.40.5");
assertThat(variant.url).isEqualTo(Uri.parse("http://example.com/This/{$nested}/reference/shouldnt/work"));
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant in project ExoPlayer by google.
the class HlsPlaylistParser method parseMultivariantPlaylist.
private static HlsMultivariantPlaylist parseMultivariantPlaylist(LineIterator iterator, String baseUri) throws IOException {
HashMap<Uri, ArrayList<VariantInfo>> urlToVariantInfos = new HashMap<>();
HashMap<String, String> variableDefinitions = new HashMap<>();
ArrayList<Variant> variants = new ArrayList<>();
ArrayList<Rendition> videos = new ArrayList<>();
ArrayList<Rendition> audios = new ArrayList<>();
ArrayList<Rendition> subtitles = new ArrayList<>();
ArrayList<Rendition> closedCaptions = new ArrayList<>();
ArrayList<String> mediaTags = new ArrayList<>();
ArrayList<DrmInitData> sessionKeyDrmInitData = new ArrayList<>();
ArrayList<String> tags = new ArrayList<>();
Format muxedAudioFormat = null;
List<Format> muxedCaptionFormats = null;
boolean noClosedCaptions = false;
boolean hasIndependentSegmentsTag = false;
String line;
while (iterator.hasNext()) {
line = iterator.next();
if (line.startsWith(TAG_PREFIX)) {
// We expose all tags through the playlist.
tags.add(line);
}
boolean isIFrameOnlyVariant = line.startsWith(TAG_I_FRAME_STREAM_INF);
if (line.startsWith(TAG_DEFINE)) {
variableDefinitions.put(/* key= */
parseStringAttr(line, REGEX_NAME, variableDefinitions), /* value= */
parseStringAttr(line, REGEX_VALUE, variableDefinitions));
} else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) {
hasIndependentSegmentsTag = true;
} else if (line.startsWith(TAG_MEDIA)) {
// Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF
// tags.
mediaTags.add(line);
} else if (line.startsWith(TAG_SESSION_KEY)) {
String keyFormat = parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions);
SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions);
if (schemeData != null) {
String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions);
String scheme = parseEncryptionScheme(method);
sessionKeyDrmInitData.add(new DrmInitData(scheme, schemeData));
}
} else if (line.startsWith(TAG_STREAM_INF) || isIFrameOnlyVariant) {
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
int roleFlags = isIFrameOnlyVariant ? C.ROLE_FLAG_TRICK_PLAY : 0;
int peakBitrate = parseIntAttr(line, REGEX_BANDWIDTH);
int averageBitrate = parseOptionalIntAttr(line, REGEX_AVERAGE_BANDWIDTH, -1);
String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions);
String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION, variableDefinitions);
int width;
int height;
if (resolutionString != null) {
String[] widthAndHeight = Util.split(resolutionString, "x");
width = Integer.parseInt(widthAndHeight[0]);
height = Integer.parseInt(widthAndHeight[1]);
if (width <= 0 || height <= 0) {
// Resolution string is invalid.
width = Format.NO_VALUE;
height = Format.NO_VALUE;
}
} else {
width = Format.NO_VALUE;
height = Format.NO_VALUE;
}
float frameRate = Format.NO_VALUE;
String frameRateString = parseOptionalStringAttr(line, REGEX_FRAME_RATE, variableDefinitions);
if (frameRateString != null) {
frameRate = Float.parseFloat(frameRateString);
}
String videoGroupId = parseOptionalStringAttr(line, REGEX_VIDEO, variableDefinitions);
String audioGroupId = parseOptionalStringAttr(line, REGEX_AUDIO, variableDefinitions);
String subtitlesGroupId = parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions);
String closedCaptionsGroupId = parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions);
Uri uri;
if (isIFrameOnlyVariant) {
uri = UriUtil.resolveToUri(baseUri, parseStringAttr(line, REGEX_URI, variableDefinitions));
} else if (!iterator.hasNext()) {
throw ParserException.createForMalformedManifest("#EXT-X-STREAM-INF must be followed by another line", /* cause= */
null);
} else {
// The following line contains #EXT-X-STREAM-INF's URI.
line = replaceVariableReferences(iterator.next(), variableDefinitions);
uri = UriUtil.resolveToUri(baseUri, line);
}
Format format = new Format.Builder().setId(variants.size()).setContainerMimeType(MimeTypes.APPLICATION_M3U8).setCodecs(codecs).setAverageBitrate(averageBitrate).setPeakBitrate(peakBitrate).setWidth(width).setHeight(height).setFrameRate(frameRate).setRoleFlags(roleFlags).build();
Variant variant = new Variant(uri, format, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId);
variants.add(variant);
@Nullable ArrayList<VariantInfo> variantInfosForUrl = urlToVariantInfos.get(uri);
if (variantInfosForUrl == null) {
variantInfosForUrl = new ArrayList<>();
urlToVariantInfos.put(uri, variantInfosForUrl);
}
variantInfosForUrl.add(new VariantInfo(averageBitrate, peakBitrate, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId));
}
}
// TODO: Don't deduplicate variants by URL.
ArrayList<Variant> deduplicatedVariants = new ArrayList<>();
HashSet<Uri> urlsInDeduplicatedVariants = new HashSet<>();
for (int i = 0; i < variants.size(); i++) {
Variant variant = variants.get(i);
if (urlsInDeduplicatedVariants.add(variant.url)) {
Assertions.checkState(variant.format.metadata == null);
HlsTrackMetadataEntry hlsMetadataEntry = new HlsTrackMetadataEntry(/* groupId= */
null, /* name= */
null, checkNotNull(urlToVariantInfos.get(variant.url)));
Metadata metadata = new Metadata(hlsMetadataEntry);
Format format = variant.format.buildUpon().setMetadata(metadata).build();
deduplicatedVariants.add(variant.copyWithFormat(format));
}
}
for (int i = 0; i < mediaTags.size(); i++) {
line = mediaTags.get(i);
String groupId = parseStringAttr(line, REGEX_GROUP_ID, variableDefinitions);
String name = parseStringAttr(line, REGEX_NAME, variableDefinitions);
Format.Builder formatBuilder = new Format.Builder().setId(groupId + ":" + name).setLabel(name).setContainerMimeType(MimeTypes.APPLICATION_M3U8).setSelectionFlags(parseSelectionFlags(line)).setRoleFlags(parseRoleFlags(line, variableDefinitions)).setLanguage(parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions));
@Nullable String referenceUri = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions);
@Nullable Uri uri = referenceUri == null ? null : UriUtil.resolveToUri(baseUri, referenceUri);
Metadata metadata = new Metadata(new HlsTrackMetadataEntry(groupId, name, Collections.emptyList()));
switch(parseStringAttr(line, REGEX_TYPE, variableDefinitions)) {
case TYPE_VIDEO:
@Nullable Variant variant = getVariantWithVideoGroup(variants, groupId);
if (variant != null) {
Format variantFormat = variant.format;
@Nullable String codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
formatBuilder.setCodecs(codecs).setSampleMimeType(MimeTypes.getMediaMimeType(codecs)).setWidth(variantFormat.width).setHeight(variantFormat.height).setFrameRate(variantFormat.frameRate);
}
if (uri == null) {
// TODO: Remove this case and add a Rendition with a null uri to videos.
} else {
formatBuilder.setMetadata(metadata);
videos.add(new Rendition(uri, formatBuilder.build(), groupId, name));
}
break;
case TYPE_AUDIO:
@Nullable String sampleMimeType = null;
variant = getVariantWithAudioGroup(variants, groupId);
if (variant != null) {
@Nullable String codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_AUDIO);
formatBuilder.setCodecs(codecs);
sampleMimeType = MimeTypes.getMediaMimeType(codecs);
}
@Nullable String channelsString = parseOptionalStringAttr(line, REGEX_CHANNELS, variableDefinitions);
if (channelsString != null) {
int channelCount = Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0]);
formatBuilder.setChannelCount(channelCount);
if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType) && channelsString.endsWith("/JOC")) {
sampleMimeType = MimeTypes.AUDIO_E_AC3_JOC;
formatBuilder.setCodecs(MimeTypes.CODEC_E_AC3_JOC);
}
}
formatBuilder.setSampleMimeType(sampleMimeType);
if (uri != null) {
formatBuilder.setMetadata(metadata);
audios.add(new Rendition(uri, formatBuilder.build(), groupId, name));
} else if (variant != null) {
// TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios.
muxedAudioFormat = formatBuilder.build();
}
break;
case TYPE_SUBTITLES:
sampleMimeType = null;
variant = getVariantWithSubtitleGroup(variants, groupId);
if (variant != null) {
@Nullable String codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_TEXT);
formatBuilder.setCodecs(codecs);
sampleMimeType = MimeTypes.getMediaMimeType(codecs);
}
if (sampleMimeType == null) {
sampleMimeType = MimeTypes.TEXT_VTT;
}
formatBuilder.setSampleMimeType(sampleMimeType).setMetadata(metadata);
if (uri != null) {
subtitles.add(new Rendition(uri, formatBuilder.build(), groupId, name));
} else {
Log.w(LOG_TAG, "EXT-X-MEDIA tag with missing mandatory URI attribute: skipping");
}
break;
case TYPE_CLOSED_CAPTIONS:
String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID, variableDefinitions);
int accessibilityChannel;
if (instreamId.startsWith("CC")) {
sampleMimeType = MimeTypes.APPLICATION_CEA608;
accessibilityChannel = Integer.parseInt(instreamId.substring(2));
} else /* starts with SERVICE */
{
sampleMimeType = MimeTypes.APPLICATION_CEA708;
accessibilityChannel = Integer.parseInt(instreamId.substring(7));
}
if (muxedCaptionFormats == null) {
muxedCaptionFormats = new ArrayList<>();
}
formatBuilder.setSampleMimeType(sampleMimeType).setAccessibilityChannel(accessibilityChannel);
muxedCaptionFormats.add(formatBuilder.build());
// TODO: Remove muxedCaptionFormats and add a Rendition with a null uri to closedCaptions.
break;
default:
// Do nothing.
break;
}
}
if (noClosedCaptions) {
muxedCaptionFormats = Collections.emptyList();
}
return new HlsMultivariantPlaylist(baseUri, tags, deduplicatedVariants, videos, audios, subtitles, closedCaptions, muxedAudioFormat, muxedCaptionFormats, hasIndependentSegmentsTag, variableDefinitions, sessionKeyDrmInitData);
}
use of com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant in project Telegram-FOSS by Telegram-FOSS-Team.
the class HlsMediaPeriod method getStreamKeys.
// TODO: When the master playlist does not de-duplicate variants by URL and allows Renditions with
// null URLs, this method must be updated to calculate stream keys that are compatible with those
// that may already be persisted for offline.
@Override
public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
// See HlsMasterPlaylist.copy for interpretation of StreamKeys.
HlsMasterPlaylist masterPlaylist = Assertions.checkNotNull(playlistTracker.getMasterPlaylist());
boolean hasVariants = !masterPlaylist.variants.isEmpty();
int audioWrapperOffset = hasVariants ? 1 : 0;
// Subtitle sample stream wrappers are held last.
int subtitleWrapperOffset = sampleStreamWrappers.length - masterPlaylist.subtitles.size();
TrackGroupArray mainWrapperTrackGroups;
int mainWrapperPrimaryGroupIndex;
int[] mainWrapperVariantIndices;
if (hasVariants) {
HlsSampleStreamWrapper mainWrapper = sampleStreamWrappers[0];
mainWrapperVariantIndices = manifestUrlIndicesPerWrapper[0];
mainWrapperTrackGroups = mainWrapper.getTrackGroups();
mainWrapperPrimaryGroupIndex = mainWrapper.getPrimaryTrackGroupIndex();
} else {
mainWrapperVariantIndices = new int[0];
mainWrapperTrackGroups = TrackGroupArray.EMPTY;
mainWrapperPrimaryGroupIndex = 0;
}
List<StreamKey> streamKeys = new ArrayList<>();
boolean needsPrimaryTrackGroupSelection = false;
boolean hasPrimaryTrackGroupSelection = false;
for (TrackSelection trackSelection : trackSelections) {
TrackGroup trackSelectionGroup = trackSelection.getTrackGroup();
int mainWrapperTrackGroupIndex = mainWrapperTrackGroups.indexOf(trackSelectionGroup);
if (mainWrapperTrackGroupIndex != C.INDEX_UNSET) {
if (mainWrapperTrackGroupIndex == mainWrapperPrimaryGroupIndex) {
// Primary group in main wrapper.
hasPrimaryTrackGroupSelection = true;
for (int i = 0; i < trackSelection.length(); i++) {
int variantIndex = mainWrapperVariantIndices[trackSelection.getIndexInTrackGroup(i)];
streamKeys.add(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, variantIndex));
}
} else {
// Embedded group in main wrapper.
needsPrimaryTrackGroupSelection = true;
}
} else {
// Audio or subtitle group.
for (int i = audioWrapperOffset; i < sampleStreamWrappers.length; i++) {
TrackGroupArray wrapperTrackGroups = sampleStreamWrappers[i].getTrackGroups();
int selectedTrackGroupIndex = wrapperTrackGroups.indexOf(trackSelectionGroup);
if (selectedTrackGroupIndex != C.INDEX_UNSET) {
int groupIndexType = i < subtitleWrapperOffset ? HlsMasterPlaylist.GROUP_INDEX_AUDIO : HlsMasterPlaylist.GROUP_INDEX_SUBTITLE;
int[] selectedWrapperUrlIndices = manifestUrlIndicesPerWrapper[i];
for (int trackIndex = 0; trackIndex < trackSelection.length(); trackIndex++) {
int renditionIndex = selectedWrapperUrlIndices[trackSelection.getIndexInTrackGroup(trackIndex)];
streamKeys.add(new StreamKey(groupIndexType, renditionIndex));
}
break;
}
}
}
}
if (needsPrimaryTrackGroupSelection && !hasPrimaryTrackGroupSelection) {
// A track selection includes a variant-embedded track, but no variant is added yet. We use
// the valid variant with the lowest bitrate to reduce overhead.
int lowestBitrateIndex = mainWrapperVariantIndices[0];
int lowestBitrate = masterPlaylist.variants.get(mainWrapperVariantIndices[0]).format.bitrate;
for (int i = 1; i < mainWrapperVariantIndices.length; i++) {
int variantBitrate = masterPlaylist.variants.get(mainWrapperVariantIndices[i]).format.bitrate;
if (variantBitrate < lowestBitrate) {
lowestBitrate = variantBitrate;
lowestBitrateIndex = mainWrapperVariantIndices[i];
}
}
streamKeys.add(new StreamKey(HlsMasterPlaylist.GROUP_INDEX_VARIANT, lowestBitrateIndex));
}
return streamKeys;
}
Aggregations