use of com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo 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.HlsTrackMetadataEntry.VariantInfo in project ExoPlayer by google.
the class HlsTrackMetadataEntryTest method variantInfo_parcelRoundTrip_isEqual.
@Test
public void variantInfo_parcelRoundTrip_isEqual() {
VariantInfo variantInfoToParcel = new VariantInfo(/* averageBitrate= */
1024, /* peakBitrate= */
2048, "videoGroupId", "audioGroupId", "subtitleGroupId", "captionGroupId");
Parcel parcel = Parcel.obtain();
variantInfoToParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
VariantInfo variantInfoFromParcel = VariantInfo.CREATOR.createFromParcel(parcel);
assertThat(variantInfoFromParcel).isEqualTo(variantInfoToParcel);
parcel.recycle();
}
use of com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo in project Telegram-FOSS by Telegram-FOSS-Team.
the class HlsPlaylistParser method parseMasterPlaylist.
private static HlsMasterPlaylist parseMasterPlaylist(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);
}
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)) {
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
// TODO: Plumb this into Format.
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 = resolutionString.split("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);
if (!iterator.hasNext()) {
throw new ParserException("#EXT-X-STREAM-INF tag must be followed by another line");
}
line = replaceVariableReferences(iterator.next(), // #EXT-X-STREAM-INF's URI.
variableDefinitions);
Uri uri = UriUtil.resolveToUri(baseUri, line);
Format format = Format.createVideoContainerFormat(/* id= */
Integer.toString(variants.size()), /* label= */
null, /* containerMimeType= */
MimeTypes.APPLICATION_M3U8, /* sampleMimeType= */
null, codecs, /* metadata= */
null, bitrate, width, height, frameRate, /* initializationData= */
null, /* selectionFlags= */
0, /* roleFlags= */
0);
Variant variant = new Variant(uri, format, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId);
variants.add(variant);
ArrayList<VariantInfo> variantInfosForUrl = urlToVariantInfos.get(uri);
if (variantInfosForUrl == null) {
variantInfosForUrl = new ArrayList<>();
urlToVariantInfos.put(uri, variantInfosForUrl);
}
variantInfosForUrl.add(new VariantInfo(bitrate, 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, Assertions.checkNotNull(urlToVariantInfos.get(variant.url)));
deduplicatedVariants.add(variant.copyWithFormat(variant.format.copyWithMetadata(new Metadata(hlsMetadataEntry))));
}
}
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);
String referenceUri = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions);
Uri uri = referenceUri == null ? null : UriUtil.resolveToUri(baseUri, referenceUri);
String language = parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions);
@C.SelectionFlags int selectionFlags = parseSelectionFlags(line);
@C.RoleFlags int roleFlags = parseRoleFlags(line, variableDefinitions);
String formatId = groupId + ":" + name;
Format format;
Metadata metadata = new Metadata(new HlsTrackMetadataEntry(groupId, name, Collections.emptyList()));
switch(parseStringAttr(line, REGEX_TYPE, variableDefinitions)) {
case TYPE_VIDEO:
Variant variant = getVariantWithVideoGroup(variants, groupId);
String codecs = null;
int width = Format.NO_VALUE;
int height = Format.NO_VALUE;
float frameRate = Format.NO_VALUE;
if (variant != null) {
Format variantFormat = variant.format;
codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO);
width = variantFormat.width;
height = variantFormat.height;
frameRate = variantFormat.frameRate;
}
String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null;
format = Format.createVideoContainerFormat(/* id= */
formatId, /* label= */
name, /* containerMimeType= */
MimeTypes.APPLICATION_M3U8, sampleMimeType, codecs, /* metadata= */
null, /* bitrate= */
Format.NO_VALUE, width, height, frameRate, /* initializationData= */
null, selectionFlags, roleFlags).copyWithMetadata(metadata);
if (uri == null) {
// TODO: Remove this case and add a Rendition with a null uri to videos.
} else {
videos.add(new Rendition(uri, format, groupId, name));
}
break;
case TYPE_AUDIO:
variant = getVariantWithAudioGroup(variants, groupId);
codecs = variant != null ? Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_AUDIO) : null;
sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null;
String channelsString = parseOptionalStringAttr(line, REGEX_CHANNELS, variableDefinitions);
int channelCount = Format.NO_VALUE;
if (channelsString != null) {
channelCount = Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0]);
if (MimeTypes.AUDIO_E_AC3.equals(sampleMimeType) && channelsString.endsWith("/JOC")) {
sampleMimeType = MimeTypes.AUDIO_E_AC3_JOC;
}
}
format = Format.createAudioContainerFormat(/* id= */
formatId, /* label= */
name, /* containerMimeType= */
MimeTypes.APPLICATION_M3U8, sampleMimeType, codecs, /* metadata= */
null, /* bitrate= */
Format.NO_VALUE, channelCount, /* sampleRate= */
Format.NO_VALUE, /* initializationData= */
null, selectionFlags, roleFlags, language);
if (uri == null) {
// TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios.
muxedAudioFormat = format;
} else {
audios.add(new Rendition(uri, format.copyWithMetadata(metadata), groupId, name));
}
break;
case TYPE_SUBTITLES:
codecs = null;
sampleMimeType = null;
variant = getVariantWithSubtitleGroup(variants, groupId);
if (variant != null) {
codecs = Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_TEXT);
sampleMimeType = MimeTypes.getMediaMimeType(codecs);
}
if (sampleMimeType == null) {
sampleMimeType = MimeTypes.TEXT_VTT;
}
format = Format.createTextContainerFormat(/* id= */
formatId, /* label= */
name, /* containerMimeType= */
MimeTypes.APPLICATION_M3U8, sampleMimeType, codecs, /* bitrate= */
Format.NO_VALUE, selectionFlags, roleFlags, language).copyWithMetadata(metadata);
subtitles.add(new Rendition(uri, format, groupId, name));
break;
case TYPE_CLOSED_CAPTIONS:
String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID, variableDefinitions);
String mimeType;
int accessibilityChannel;
if (instreamId.startsWith("CC")) {
mimeType = MimeTypes.APPLICATION_CEA608;
accessibilityChannel = Integer.parseInt(instreamId.substring(2));
} else /* starts with SERVICE */
{
mimeType = MimeTypes.APPLICATION_CEA708;
accessibilityChannel = Integer.parseInt(instreamId.substring(7));
}
if (muxedCaptionFormats == null) {
muxedCaptionFormats = new ArrayList<>();
}
muxedCaptionFormats.add(Format.createTextContainerFormat(/* id= */
formatId, /* label= */
name, /* containerMimeType= */
null, /* sampleMimeType= */
mimeType, /* codecs= */
null, /* bitrate= */
Format.NO_VALUE, selectionFlags, roleFlags, language, accessibilityChannel));
// 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 HlsMasterPlaylist(baseUri, tags, deduplicatedVariants, videos, audios, subtitles, closedCaptions, muxedAudioFormat, muxedCaptionFormats, hasIndependentSegmentsTag, variableDefinitions, sessionKeyDrmInitData);
}
Aggregations