Search in sources :

Example 1 with Variant

use of androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant in project media by androidx.

the class HlsSampleStreamWrapper method buildTracksFromSampleStreams.

/**
 * 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 multivariant 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 multivariant 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>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>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.
 * </ul>
 */
@EnsuresNonNull({ "trackGroups", "optionalTrackGroups", "trackGroupToSampleQueueIndex" })
private void buildTracksFromSampleStreams() {
    // Iterate through the extractor tracks to discover the "primary" track type, and the index
    // of the single track of this type.
    int primaryExtractorTrackType = C.TRACK_TYPE_NONE;
    int primaryExtractorTrackIndex = C.INDEX_UNSET;
    int extractorTrackCount = sampleQueues.length;
    for (int i = 0; i < extractorTrackCount; i++) {
        @Nullable String sampleMimeType = Assertions.checkStateNotNull(sampleQueues[i].getUpstreamFormat()).sampleMimeType;
        int trackType;
        if (MimeTypes.isVideo(sampleMimeType)) {
            trackType = C.TRACK_TYPE_VIDEO;
        } else if (MimeTypes.isAudio(sampleMimeType)) {
            trackType = C.TRACK_TYPE_AUDIO;
        } else if (MimeTypes.isText(sampleMimeType)) {
            trackType = C.TRACK_TYPE_TEXT;
        } else {
            trackType = C.TRACK_TYPE_NONE;
        }
        if (getTrackTypeScore(trackType) > getTrackTypeScore(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;
    trackGroupToSampleQueueIndex = new int[extractorTrackCount];
    for (int i = 0; i < extractorTrackCount; i++) {
        trackGroupToSampleQueueIndex[i] = i;
    }
    // Construct the set of exposed track groups.
    TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount];
    for (int i = 0; i < extractorTrackCount; i++) {
        Format sampleFormat = Assertions.checkStateNotNull(sampleQueues[i].getUpstreamFormat());
        if (i == primaryExtractorTrackIndex) {
            Format[] formats = new Format[chunkSourceTrackCount];
            for (int j = 0; j < chunkSourceTrackCount; j++) {
                Format playlistFormat = chunkSourceTrackGroup.getFormat(j);
                if (primaryExtractorTrackType == C.TRACK_TYPE_AUDIO && muxedAudioFormat != null) {
                    playlistFormat = playlistFormat.withManifestFormatInfo(muxedAudioFormat);
                }
                // If there's only a single variant (chunkSourceTrackCount == 1) then we can safely
                // retain all fields from sampleFormat. Else we need to use deriveFormat to retain only
                // the fields that will be the same for all variants.
                formats[j] = chunkSourceTrackCount == 1 ? sampleFormat.withManifestFormatInfo(playlistFormat) : deriveFormat(playlistFormat, sampleFormat, /* propagateBitrates= */
                true);
            }
            trackGroups[i] = new TrackGroup(uid, formats);
            primaryTrackGroupIndex = i;
        } else {
            @Nullable Format playlistFormat = primaryExtractorTrackType == C.TRACK_TYPE_VIDEO && MimeTypes.isAudio(sampleFormat.sampleMimeType) ? muxedAudioFormat : null;
            String muxedTrackGroupId = uid + ":muxed:" + (i < primaryExtractorTrackIndex ? i : i - 1);
            trackGroups[i] = new TrackGroup(muxedTrackGroupId, deriveFormat(playlistFormat, sampleFormat, /* propagateBitrates= */
            false));
        }
    }
    this.trackGroups = createTrackGroupArrayWithDrmInfo(trackGroups);
    Assertions.checkState(optionalTrackGroups == null);
    optionalTrackGroups = Collections.emptySet();
}
Also used : Format(androidx.media3.common.Format) TrackGroup(androidx.media3.common.TrackGroup) Nullable(androidx.annotation.Nullable) EnsuresNonNull(org.checkerframework.checker.nullness.qual.EnsuresNonNull)

Example 2 with Variant

use of androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant 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 3 with Variant

use of androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant in project media by androidx.

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"));
}
Also used : Variant(androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant) Test(org.junit.Test)

Example 4 with Variant

use of androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant in project media by androidx.

the class HlsMultivariantPlaylistParserTest method parseMultivariantPlaylist_withTtmlSubtitle.

@Test
public void parseMultivariantPlaylist_withTtmlSubtitle() throws IOException {
    HlsMultivariantPlaylist playlistWithTtmlSubtitle = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_TTML_SUBTITLE);
    HlsMultivariantPlaylist.Variant variant = playlistWithTtmlSubtitle.variants.get(0);
    Format firstTextFormat = playlistWithTtmlSubtitle.subtitles.get(0).format;
    assertThat(firstTextFormat.id).isEqualTo("sub1:English");
    assertThat(firstTextFormat.containerMimeType).isEqualTo(MimeTypes.APPLICATION_M3U8);
    assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_TTML);
    assertThat(variant.format.codecs).isEqualTo("stpp.ttml.im1t,mp4a.40.2,avc1.66.30");
}
Also used : Format(androidx.media3.common.Format) Variant(androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant) Test(org.junit.Test)

Example 5 with Variant

use of androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant in project media by androidx.

the class HlsMediaPeriod method buildAndPrepareMainSampleStreamWrapper.

/**
 * This method creates and starts preparation of the main {@link HlsSampleStreamWrapper}.
 *
 * <p>The main sample stream wrapper is the first element of {@link #sampleStreamWrappers}. It
 * provides {@link SampleStream}s for the variant urls in the multivariant playlist. It may be
 * adaptive and may contain multiple muxed tracks.
 *
 * <p>If chunkless preparation is allowed, the media period will try preparation without segment
 * downloads. This is only possible if variants contain the CODECS attribute. If not, traditional
 * preparation with segment downloads will take place. The following points apply to chunkless
 * preparation:
 *
 * <ul>
 *   <li>A muxed audio track will be exposed if the codecs list contain an audio entry and the
 *       multivariant playlist either contains an EXT-X-MEDIA tag without the URI attribute or
 *       does not contain any EXT-X-MEDIA tag.
 *   <li>Closed captions will only be exposed if they are declared by the multivariant playlist.
 *   <li>An ID3 track is exposed preemptively, in case the segments contain an ID3 track.
 * </ul>
 *
 * @param multivariantPlaylist The HLS multivariant playlist.
 * @param positionUs If preparation requires any chunk downloads, the position in microseconds at
 *     which downloading should start. Ignored otherwise.
 * @param sampleStreamWrappers List to which the built main sample stream wrapper should be added.
 * @param manifestUrlIndicesPerWrapper List to which the selected variant indices should be added.
 * @param overridingDrmInitData Overriding {@link DrmInitData}, keyed by protection scheme type
 *     (i.e. {@link DrmInitData#schemeType}).
 */
private void buildAndPrepareMainSampleStreamWrapper(HlsMultivariantPlaylist multivariantPlaylist, long positionUs, List<HlsSampleStreamWrapper> sampleStreamWrappers, List<int[]> manifestUrlIndicesPerWrapper, Map<String, DrmInitData> overridingDrmInitData) {
    int[] variantTypes = new int[multivariantPlaylist.variants.size()];
    int videoVariantCount = 0;
    int audioVariantCount = 0;
    for (int i = 0; i < multivariantPlaylist.variants.size(); i++) {
        Variant variant = multivariantPlaylist.variants.get(i);
        Format format = variant.format;
        if (format.height > 0 || Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_VIDEO) != null) {
            variantTypes[i] = C.TRACK_TYPE_VIDEO;
            videoVariantCount++;
        } else if (Util.getCodecsOfType(format.codecs, C.TRACK_TYPE_AUDIO) != null) {
            variantTypes[i] = C.TRACK_TYPE_AUDIO;
            audioVariantCount++;
        } else {
            variantTypes[i] = C.TRACK_TYPE_UNKNOWN;
        }
    }
    boolean useVideoVariantsOnly = false;
    boolean useNonAudioVariantsOnly = false;
    int selectedVariantsCount = variantTypes.length;
    if (videoVariantCount > 0) {
        // We've identified some variants as definitely containing video. Assume variants within the
        // multivariant playlist are marked consistently, and hence that we have the full set. Filter
        // out any other variants, which are likely to be audio only.
        useVideoVariantsOnly = true;
        selectedVariantsCount = videoVariantCount;
    } else if (audioVariantCount < variantTypes.length) {
        // We've identified some variants, but not all, as being audio only. Filter them out to leave
        // the remaining variants, which are likely to contain video.
        useNonAudioVariantsOnly = true;
        selectedVariantsCount = variantTypes.length - audioVariantCount;
    }
    Uri[] selectedPlaylistUrls = new Uri[selectedVariantsCount];
    Format[] selectedPlaylistFormats = new Format[selectedVariantsCount];
    int[] selectedVariantIndices = new int[selectedVariantsCount];
    int outIndex = 0;
    for (int i = 0; i < multivariantPlaylist.variants.size(); i++) {
        if ((!useVideoVariantsOnly || variantTypes[i] == C.TRACK_TYPE_VIDEO) && (!useNonAudioVariantsOnly || variantTypes[i] != C.TRACK_TYPE_AUDIO)) {
            Variant variant = multivariantPlaylist.variants.get(i);
            selectedPlaylistUrls[outIndex] = variant.url;
            selectedPlaylistFormats[outIndex] = variant.format;
            selectedVariantIndices[outIndex++] = i;
        }
    }
    String codecs = selectedPlaylistFormats[0].codecs;
    int numberOfVideoCodecs = Util.getCodecCountOfType(codecs, C.TRACK_TYPE_VIDEO);
    int numberOfAudioCodecs = Util.getCodecCountOfType(codecs, C.TRACK_TYPE_AUDIO);
    boolean codecsStringAllowsChunklessPreparation = numberOfAudioCodecs <= 1 && numberOfVideoCodecs <= 1 && numberOfAudioCodecs + numberOfVideoCodecs > 0;
    @C.TrackType int trackType = !useVideoVariantsOnly && numberOfAudioCodecs > 0 ? C.TRACK_TYPE_AUDIO : C.TRACK_TYPE_DEFAULT;
    String sampleStreamWrapperUid = "main";
    HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(sampleStreamWrapperUid, trackType, selectedPlaylistUrls, selectedPlaylistFormats, multivariantPlaylist.muxedAudioFormat, multivariantPlaylist.muxedCaptionFormats, overridingDrmInitData, positionUs);
    sampleStreamWrappers.add(sampleStreamWrapper);
    manifestUrlIndicesPerWrapper.add(selectedVariantIndices);
    if (allowChunklessPreparation && codecsStringAllowsChunklessPreparation) {
        List<TrackGroup> muxedTrackGroups = new ArrayList<>();
        if (numberOfVideoCodecs > 0) {
            Format[] videoFormats = new Format[selectedVariantsCount];
            for (int i = 0; i < videoFormats.length; i++) {
                videoFormats[i] = deriveVideoFormat(selectedPlaylistFormats[i]);
            }
            muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, videoFormats));
            if (numberOfAudioCodecs > 0 && (multivariantPlaylist.muxedAudioFormat != null || multivariantPlaylist.audios.isEmpty())) {
                muxedTrackGroups.add(new TrackGroup(/* id= */
                sampleStreamWrapperUid + ":audio", deriveAudioFormat(selectedPlaylistFormats[0], multivariantPlaylist.muxedAudioFormat, /* isPrimaryTrackInVariant= */
                false)));
            }
            List<Format> ccFormats = multivariantPlaylist.muxedCaptionFormats;
            if (ccFormats != null) {
                for (int i = 0; i < ccFormats.size(); i++) {
                    String ccId = sampleStreamWrapperUid + ":cc:" + i;
                    muxedTrackGroups.add(new TrackGroup(ccId, ccFormats.get(i)));
                }
            }
        } else /* numberOfAudioCodecs > 0 */
        {
            // Variants only contain audio.
            Format[] audioFormats = new Format[selectedVariantsCount];
            for (int i = 0; i < audioFormats.length; i++) {
                audioFormats[i] = deriveAudioFormat(/* variantFormat= */
                selectedPlaylistFormats[i], multivariantPlaylist.muxedAudioFormat, /* isPrimaryTrackInVariant= */
                true);
            }
            muxedTrackGroups.add(new TrackGroup(sampleStreamWrapperUid, audioFormats));
        }
        TrackGroup id3TrackGroup = new TrackGroup(/* id= */
        sampleStreamWrapperUid + ":id3", new Format.Builder().setId("ID3").setSampleMimeType(MimeTypes.APPLICATION_ID3).build());
        muxedTrackGroups.add(id3TrackGroup);
        sampleStreamWrapper.prepareWithMultivariantPlaylistInfo(muxedTrackGroups.toArray(new TrackGroup[0]), /* primaryTrackGroupIndex= */
        0, /* optionalTrackGroupsIndices...= */
        muxedTrackGroups.indexOf(id3TrackGroup));
    }
}
Also used : ArrayList(java.util.ArrayList) Uri(android.net.Uri) Variant(androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant) Format(androidx.media3.common.Format) TrackGroup(androidx.media3.common.TrackGroup)

Aggregations

Format (androidx.media3.common.Format)5 Variant (androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Variant)5 TrackGroup (androidx.media3.common.TrackGroup)4 Nullable (androidx.annotation.Nullable)3 ArrayList (java.util.ArrayList)3 Test (org.junit.Test)3 Uri (android.net.Uri)2 Metadata (androidx.media3.common.Metadata)2 ExoTrackSelection (androidx.media3.exoplayer.trackselection.ExoTrackSelection)2 DrmInitData (androidx.media3.common.DrmInitData)1 SchemeData (androidx.media3.common.DrmInitData.SchemeData)1 StreamKey (androidx.media3.common.StreamKey)1 TrackGroupArray (androidx.media3.common.TrackGroupArray)1 HlsTrackMetadataEntry (androidx.media3.exoplayer.hls.HlsTrackMetadataEntry)1 VariantInfo (androidx.media3.exoplayer.hls.HlsTrackMetadataEntry.VariantInfo)1 HlsMultivariantPlaylist (androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist)1 Rendition (androidx.media3.exoplayer.hls.playlist.HlsMultivariantPlaylist.Rendition)1 SampleStream (androidx.media3.exoplayer.source.SampleStream)1 HashMap (java.util.HashMap)1 HashSet (java.util.HashSet)1