Search in sources :

Example 1 with Variant

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);
}
Also used : Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment) HlsUrl(com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl) BehindLiveWindowException(com.google.android.exoplayer2.source.BehindLiveWindowException) DataSpec(com.google.android.exoplayer2.upstream.DataSpec) HlsMediaPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist) TimestampAdjuster(com.google.android.exoplayer2.util.TimestampAdjuster) Uri(android.net.Uri) Segment(com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment)

Example 2 with Variant

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);
}
Also used : Format(com.google.android.exoplayer2.Format) TrackGroup(com.google.android.exoplayer2.source.TrackGroup) TrackGroupArray(com.google.android.exoplayer2.source.TrackGroupArray)

Example 3 with Variant

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"));
}
Also used : Variant(com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant) Test(org.junit.Test)

Example 4 with Variant

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

Example 5 with Variant

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;
}
Also used : TrackGroup(com.google.android.exoplayer2.source.TrackGroup) TrackGroupArray(com.google.android.exoplayer2.source.TrackGroupArray) ArrayList(java.util.ArrayList) HlsMasterPlaylist(com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist) TrackSelection(com.google.android.exoplayer2.trackselection.TrackSelection) StreamKey(com.google.android.exoplayer2.offline.StreamKey)

Aggregations

Format (com.google.android.exoplayer2.Format)10 TrackGroup (com.google.android.exoplayer2.source.TrackGroup)8 ArrayList (java.util.ArrayList)7 Uri (android.net.Uri)5 Variant (com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist.Variant)5 Nullable (androidx.annotation.Nullable)3 Metadata (com.google.android.exoplayer2.metadata.Metadata)3 TrackGroupArray (com.google.android.exoplayer2.source.TrackGroupArray)3 Test (org.junit.Test)3 DrmInitData (com.google.android.exoplayer2.drm.DrmInitData)2 SchemeData (com.google.android.exoplayer2.drm.DrmInitData.SchemeData)2 StreamKey (com.google.android.exoplayer2.offline.StreamKey)2 HlsTrackMetadataEntry (com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry)2 VariantInfo (com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo)2 HlsMasterPlaylist (com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist)2 HlsUrl (com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl)2 Variant (com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant)2 ExoTrackSelection (com.google.android.exoplayer2.trackselection.ExoTrackSelection)2 HashMap (java.util.HashMap)2 HashSet (java.util.HashSet)2