Search in sources :

Example 11 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

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(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant) Format(com.google.android.exoplayer2.Format) TrackGroup(com.google.android.exoplayer2.source.TrackGroup)

Example 12 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class HlsMediaPeriod method getStreamKeys.

// TODO: When the multivariant 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<ExoTrackSelection> trackSelections) {
    // See HlsMultivariantPlaylist.copy for interpretation of StreamKeys.
    HlsMultivariantPlaylist multivariantPlaylist = Assertions.checkNotNull(playlistTracker.getMultivariantPlaylist());
    boolean hasVariants = !multivariantPlaylist.variants.isEmpty();
    int audioWrapperOffset = hasVariants ? 1 : 0;
    // Subtitle sample stream wrappers are held last.
    int subtitleWrapperOffset = sampleStreamWrappers.length - multivariantPlaylist.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 (ExoTrackSelection 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(HlsMultivariantPlaylist.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 ? HlsMultivariantPlaylist.GROUP_INDEX_AUDIO : HlsMultivariantPlaylist.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 = multivariantPlaylist.variants.get(mainWrapperVariantIndices[0]).format.bitrate;
        for (int i = 1; i < mainWrapperVariantIndices.length; i++) {
            int variantBitrate = multivariantPlaylist.variants.get(mainWrapperVariantIndices[i]).format.bitrate;
            if (variantBitrate < lowestBitrate) {
                lowestBitrate = variantBitrate;
                lowestBitrateIndex = mainWrapperVariantIndices[i];
            }
        }
        streamKeys.add(new StreamKey(HlsMultivariantPlaylist.GROUP_INDEX_VARIANT, lowestBitrateIndex));
    }
    return streamKeys;
}
Also used : ExoTrackSelection(com.google.android.exoplayer2.trackselection.ExoTrackSelection) TrackGroup(com.google.android.exoplayer2.source.TrackGroup) TrackGroupArray(com.google.android.exoplayer2.source.TrackGroupArray) ArrayList(java.util.ArrayList) HlsMultivariantPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist) StreamKey(com.google.android.exoplayer2.offline.StreamKey)

Example 13 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

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(com.google.android.exoplayer2.Format) Variant(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant) Test(org.junit.Test)

Example 14 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class HlsMultivariantPlaylistParserTest method parseMultivariantPlaylist_withClosedCaption_success.

@Test
public void parseMultivariantPlaylist_withClosedCaption_success() throws IOException {
    HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
    assertThat(playlist.muxedCaptionFormats).hasSize(1);
    Format closedCaptionFormat = playlist.muxedCaptionFormats.get(0);
    assertThat(closedCaptionFormat.sampleMimeType).isEqualTo(MimeTypes.APPLICATION_CEA708);
    assertThat(closedCaptionFormat.accessibilityChannel).isEqualTo(4);
    assertThat(closedCaptionFormat.language).isEqualTo("es");
}
Also used : Format(com.google.android.exoplayer2.Format) Test(org.junit.Test)

Example 15 with HlsMultivariantPlaylist

use of com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist in project ExoPlayer by google.

the class HlsMultivariantPlaylistParserTest method parseMultivariantPlaylist_withSubtitles_subtitlesIdPropagated.

@Test
public void parseMultivariantPlaylist_withSubtitles_subtitlesIdPropagated() throws IOException {
    HlsMultivariantPlaylist playlist = parseMultivariantPlaylist(PLAYLIST_URI, PLAYLIST_WITH_SUBTITLES);
    Format firstTextFormat = playlist.subtitles.get(0).format;
    assertThat(firstTextFormat.id).isEqualTo("sub1:Eng");
    assertThat(firstTextFormat.sampleMimeType).isEqualTo(MimeTypes.TEXT_VTT);
}
Also used : Format(com.google.android.exoplayer2.Format) Test(org.junit.Test)

Aggregations

Format (com.google.android.exoplayer2.Format)9 Test (org.junit.Test)9 Variant (com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant)6 ArrayList (java.util.ArrayList)6 Uri (android.net.Uri)5 HlsMultivariantPlaylist (com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist)4 Nullable (androidx.annotation.Nullable)3 DrmInitData (com.google.android.exoplayer2.drm.DrmInitData)3 TrackGroup (com.google.android.exoplayer2.source.TrackGroup)3 Rendition (com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Rendition)3 SchemeData (com.google.android.exoplayer2.drm.DrmInitData.SchemeData)2 HashMap (java.util.HashMap)2 HashSet (java.util.HashSet)2 AndroidJUnit4 (androidx.test.ext.junit.runners.AndroidJUnit4)1 PlayerId (com.google.android.exoplayer2.analytics.PlayerId)1 DrmSessionEventListener (com.google.android.exoplayer2.drm.DrmSessionEventListener)1 DrmSessionManager (com.google.android.exoplayer2.drm.DrmSessionManager)1 Metadata (com.google.android.exoplayer2.metadata.Metadata)1 StreamKey (com.google.android.exoplayer2.offline.StreamKey)1 CompositeSequenceableLoaderFactory (com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory)1