Search in sources :

Example 1 with VariantInfo

use of androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo in project media by androidx.

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);
}
Also used : VariantInfo(androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo) HashMap(java.util.HashMap) Rendition(androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Rendition) ArrayList(java.util.ArrayList) Metadata(androidx.media3.common.Metadata) SchemeData(androidx.media3.common.DrmInitData.SchemeData) Uri(android.net.Uri) DrmInitData(androidx.media3.common.DrmInitData) Format(androidx.media3.common.Format) HashSet(java.util.HashSet) HlsTrackMetadataEntry(androidx.media3.exoplayer.hls.HlsTrackMetadataEntry) Variant(androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant) Nullable(androidx.annotation.Nullable)

Example 2 with VariantInfo

use of androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo in project media by androidx.

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();
}
Also used : VariantInfo(androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo) Parcel(android.os.Parcel) Test(org.junit.Test)

Aggregations

VariantInfo (androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo)2 Uri (android.net.Uri)1 Parcel (android.os.Parcel)1 Nullable (androidx.annotation.Nullable)1 DrmInitData (androidx.media3.common.DrmInitData)1 SchemeData (androidx.media3.common.DrmInitData.SchemeData)1 Format (androidx.media3.common.Format)1 Metadata (androidx.media3.common.Metadata)1 HlsTrackMetadataEntry (androidx.media3.exoplayer.hls.HlsTrackMetadataEntry)1 Rendition (androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Rendition)1 Variant (androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant)1 ArrayList (java.util.ArrayList)1 HashMap (java.util.HashMap)1 HashSet (java.util.HashSet)1 Test (org.junit.Test)1